oh-my-agent 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of oh-my-agent might be problematic. Click here for more details.

Files changed (36) hide show
  1. package/README.md +48 -57
  2. package/dist/application/OrganizeSkillsUseCase.d.ts +24 -0
  3. package/dist/application/OrganizeSkillsUseCase.d.ts.map +1 -0
  4. package/dist/application/OrganizeSkillsUseCase.js +57 -0
  5. package/dist/application/OrganizeSkillsUseCase.js.map +1 -0
  6. package/dist/domain/skill/Skill.d.ts +12 -0
  7. package/dist/domain/skill/Skill.d.ts.map +1 -0
  8. package/dist/domain/skill/Skill.js +2 -0
  9. package/dist/domain/skill/Skill.js.map +1 -0
  10. package/dist/domain/skill/SkillClassifier.d.ts +12 -0
  11. package/dist/domain/skill/SkillClassifier.d.ts.map +1 -0
  12. package/dist/domain/skill/SkillClassifier.js +60 -0
  13. package/dist/domain/skill/SkillClassifier.js.map +1 -0
  14. package/dist/domain/skill/SkillDomain.d.ts +12 -0
  15. package/dist/domain/skill/SkillDomain.d.ts.map +1 -0
  16. package/dist/domain/skill/SkillDomain.js +142 -0
  17. package/dist/domain/skill/SkillDomain.js.map +1 -0
  18. package/dist/index.js +58 -18
  19. package/dist/index.js.map +1 -1
  20. package/dist/infrastructure/config/PathResolver.d.ts +8 -0
  21. package/dist/infrastructure/config/PathResolver.d.ts.map +1 -0
  22. package/dist/infrastructure/config/PathResolver.js +24 -0
  23. package/dist/infrastructure/config/PathResolver.js.map +1 -0
  24. package/dist/infrastructure/fs/LibraryScanner.d.ts +19 -0
  25. package/dist/infrastructure/fs/LibraryScanner.d.ts.map +1 -0
  26. package/dist/infrastructure/fs/LibraryScanner.js +94 -0
  27. package/dist/infrastructure/fs/LibraryScanner.js.map +1 -0
  28. package/dist/infrastructure/fs/SymlinkManager.d.ts +12 -0
  29. package/dist/infrastructure/fs/SymlinkManager.d.ts.map +1 -0
  30. package/dist/infrastructure/fs/SymlinkManager.js +111 -0
  31. package/dist/infrastructure/fs/SymlinkManager.js.map +1 -0
  32. package/dist/infrastructure/skills/SkillsRegistry.d.ts +15 -0
  33. package/dist/infrastructure/skills/SkillsRegistry.d.ts.map +1 -0
  34. package/dist/infrastructure/skills/SkillsRegistry.js +102 -0
  35. package/dist/infrastructure/skills/SkillsRegistry.js.map +1 -0
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -1,18 +1,19 @@
1
1
  # oh-my-agent
2
2
 
3
- > AI 스킬을 자동으로 진단·배포·관리하는 MCP 서버
3
+ > AI 스킬을 자동으로 진단·설치·관리하는 MCP 서버
4
4
 
5
- 프로젝트의 기술 스택을 분석해 적합한 AI 스킬을 추천하고, `~/oh-my-agent-library`에서 `.claude/skills/`로 심볼릭 링크를 생성해 Claude Code에서 즉시 활용할 있게 합니다.
5
+ 프로젝트의 기술 스택을 분석해 [skills.sh](https://skills.sh) 에서 최적의 스킬을 찾고 설치합니다.
6
+ 설치 수·배포자 신뢰도·보안을 종합한 점수제로 가장 적합한 스킬을 선택합니다.
6
7
 
7
- ## 설치 및 사용
8
+ ## 설치
8
9
 
9
- ### Claude Code 전역 등록
10
+ ### Claude Code 전역 등록
10
11
 
11
12
  ```bash
12
13
  claude mcp add oh-my-agent npx -y oh-my-agent
13
14
  ```
14
15
 
15
- ### 또는 프로젝트별 `.mcp.json` 설정
16
+ ### 또는 프로젝트별 `.mcp.json`
16
17
 
17
18
  ```json
18
19
  {
@@ -25,89 +26,79 @@ claude mcp add oh-my-agent npx -y oh-my-agent
25
26
  }
26
27
  ```
27
28
 
28
- ## 라이브러리 구조 준비
29
+ ## 사용법
29
30
 
30
- 스킬 파일(.md)을 아래 구조로 배치합니다:
31
+ MCP 등록 Claude Code에서 자연어로 요청합니다.
32
+
33
+ ### 스킬 자동 설치 (가장 자주 씀)
31
34
 
32
35
  ```
33
- ~/oh-my-agent-library/
34
- ├── frontend/ ← next-best-practices.md, vercel-react-best-practices.md ...
35
- ├── testing/ ← vitest-practices.md, jest-practices.md ...
36
- ├── security/ ← jwt-auth-practices.md ...
37
- ├── backend/ ← prisma-best-practices.md, express-api-patterns.md ...
38
- ├── devops/ ← docker-deployment.md ...
39
- └── manager/ ← agile-practices.md ...
36
+ 이 프로젝트에 맞는 AI 스킬 자동으로 설치해줘.
40
37
  ```
41
38
 
42
- ## 제공 도구 (MCP Tools)
39
+ 1. `package.json` 분석 기술 스택 감지
40
+ 2. skills.sh에서 각 스택별 스킬 검색
41
+ 3. 인기도·신뢰도·보안 점수화 → 최고 스킬 선택
42
+ 4. `npx skills add` 로 `.claude/skills/` 에 설치
43
+ 5. `CLAUDE.md` 자동 동기화
43
44
 
44
- ### `diagnose_project`
45
+ ---
45
46
 
46
- 프로젝트의 `package.json`을 분석해 기술 스택을 감지하고 추천 스킬 목록을 반환합니다.
47
+ ### 진단만 (설치 없이 미리보기)
47
48
 
48
49
  ```
49
- 입력: projectPath?, includeDevDeps?
50
- 출력: { detectedStack, recommendedSkills[], summary }
50
+ 프로젝트 기술 스택 분석하고 어떤 스킬 추천하는지 보여줘.
51
51
  ```
52
52
 
53
- ### `setup_skills`
53
+ ---
54
54
 
55
- 라이브러리의 스킬을 프로젝트 `.claude/skills/`에 심볼릭 링크로 배포하고 `CLAUDE.md`를 업데이트합니다.
55
+ ### 특정 스킬 지정 설치
56
56
 
57
57
  ```
58
- 입력: projectPath, skills[{domain, skillName}], updateClaudeMd?, dryRun?
59
- 출력: { results[], claudeMdUpdated, summary }
58
+ vercel-labs/next-skills@next-best-practices 스킬 설치해줘.
60
59
  ```
61
60
 
62
- ### `organize_skills`
61
+ ---
63
62
 
64
- 라이브러리 스킬 파일을 분석해 오분류된 스킬을 감지하고 자동으로 올바른 도메인으로 이동합니다.
63
+ ### 키워드로 스킬 탐색
65
64
 
66
65
  ```
67
- 입력: libraryPath?, autoFix?, targetDomain?
68
- 출력: { misplaced[], correctlyPlaced, autoFixed, summary }
66
+ prisma 관련 스킬 찾아줘.
69
67
  ```
70
68
 
71
- ### `sync_claude_md`
69
+ ---
72
70
 
73
- `.claude/skills/`를 스캔해 깨진 링크를 감지하고 `CLAUDE.md`의 스킬 섹션을 동기화합니다.
71
+ ### CLAUDE.md 재동기화
74
72
 
75
73
  ```
76
- 입력: projectPath, createIfMissing?, templateStyle?
77
- 출력: { action, installedSkills[], addedToMd[], removedFromMd[], brokenLinks[] }
74
+ .claude/skills/ 폴더 기준으로 CLAUDE.md 다시 sync해줘.
78
75
  ```
79
76
 
80
- ## CLAUDE.md 자동 관리
77
+ ## 제공 도구 (MCP Tools)
81
78
 
82
- `sync_claude_md` 실행 아래 마커 구간이 자동으로 생성·업데이트됩니다:
79
+ | 도구 | 설명 |
80
+ |------|------|
81
+ | `diagnose_project` | 기술 스택 분석 + skills.sh 검색 + 점수화된 추천 목록 반환 |
82
+ | `setup_project` | 진단 후 최고 점수 스킬 자동 설치 + CLAUDE.md 동기화 |
83
+ | `find_skills` | 키워드로 skills.sh 검색 |
84
+ | `sync_claude_md` | `.claude/skills/` 실제 파일 기준 CLAUDE.md 동기화 |
83
85
 
84
- ```markdown
85
- <!-- oh-my-agent:skills:start -->
86
- ## Active Skills
86
+ ## 점수 체계
87
87
 
88
- - **frontend/next-best-practices** Next.js 15 모범 사례
89
- - **testing/vitest-practices** — Vitest 단위 테스트
88
+ skills.sh에서 검색된 후보 스킬을 아래 기준으로 점수화합니다.
90
89
 
91
- > 섹션은 oh-my-agent가 자동 관리합니다.
92
- > 마지막 동기화: 2026-02-23T00:00:00.000Z
93
- <!-- oh-my-agent:skills:end -->
94
- ```
90
+ | 항목 | 가중치 | 기준 |
91
+ |------|--------|------|
92
+ | 인기도 | 50% | skills.sh 설치 수 |
93
+ | 배포자 신뢰도 | 35% | 공식 조직·검증 개발자 우선 (`anthropics`, `vercel-labs`, `microsoft` 등) |
94
+ | 보안 | 15% | 신뢰도 대리 지표 |
95
+
96
+ ## 감지 가능한 기술 스택
95
97
 
96
- ## 지원 기술 스택
97
-
98
- | 패키지 | 도메인 | 스킬 |
99
- |--------|--------|------|
100
- | next, nextjs | frontend | next-best-practices |
101
- | react | frontend | vercel-react-best-practices |
102
- | react-native | frontend | vercel-react-native-skills |
103
- | vitest | testing | vitest-practices |
104
- | jest | testing | jest-practices |
105
- | cypress | testing | e2e-testing |
106
- | playwright | testing | playwright-testing |
107
- | prisma | backend | prisma-best-practices |
108
- | express | backend | express-api-patterns |
109
- | jsonwebtoken, jose | security | jwt-auth-practices |
110
- | docker | devops | docker-deployment |
98
+ Next.js · React · React Native · Vue · Nuxt · Svelte · Angular
99
+ Vitest · Jest · Playwright · Cypress
100
+ Prisma · Express · Fastify · tRPC · GraphQL
101
+ Docker · Kubernetes
111
102
 
112
103
  ## 요구사항
113
104
 
@@ -0,0 +1,24 @@
1
+ import type { Domain } from "../domain/skill/SkillDomain.js";
2
+ export interface OrganizeSkillsInput {
3
+ libraryPath?: string;
4
+ autoFix?: boolean;
5
+ targetDomain?: string;
6
+ }
7
+ export interface MisplacedSkill {
8
+ skillName: string;
9
+ currentDomain: string;
10
+ predictedDomain: Domain | "unknown";
11
+ confidence: number;
12
+ filePath: string;
13
+ }
14
+ export interface OrganizeSkillsOutput {
15
+ misplaced: MisplacedSkill[];
16
+ correctlyPlaced: number;
17
+ autoFixed: number;
18
+ summary: string;
19
+ }
20
+ export declare class OrganizeSkillsUseCase {
21
+ private classifier;
22
+ execute(input: OrganizeSkillsInput): Promise<OrganizeSkillsOutput>;
23
+ }
24
+ //# sourceMappingURL=OrganizeSkillsUseCase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrganizeSkillsUseCase.d.ts","sourceRoot":"","sources":["../../src/application/OrganizeSkillsUseCase.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;AAE7D,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,qBAAqB;IAChC,OAAO,CAAC,UAAU,CAAyB;IAErC,OAAO,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CA0DzE"}
@@ -0,0 +1,57 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import fse from "fs-extra";
4
+ import { SkillClassifier } from "../domain/skill/SkillClassifier.js";
5
+ import { LibraryScanner } from "../infrastructure/fs/LibraryScanner.js";
6
+ import { PathResolver } from "../infrastructure/config/PathResolver.js";
7
+ export class OrganizeSkillsUseCase {
8
+ classifier = new SkillClassifier();
9
+ async execute(input) {
10
+ const libraryPath = input.libraryPath
11
+ ? PathResolver.resolveHome(input.libraryPath)
12
+ : PathResolver.getLibraryPath();
13
+ const autoFix = input.autoFix ?? false;
14
+ const targetDomain = input.targetDomain;
15
+ const scanner = new LibraryScanner(libraryPath);
16
+ const allSkills = await scanner.scanAll(true);
17
+ const filtered = targetDomain
18
+ ? allSkills.filter((s) => s.domain === targetDomain)
19
+ : allSkills;
20
+ const misplaced = [];
21
+ let correctlyPlaced = 0;
22
+ let autoFixed = 0;
23
+ for (const skill of filtered) {
24
+ const result = this.classifier.classify(skill.skillName, skill.content);
25
+ if (result.predictedDomain === "unknown" ||
26
+ result.predictedDomain === skill.domain) {
27
+ correctlyPlaced++;
28
+ continue;
29
+ }
30
+ misplaced.push({
31
+ skillName: skill.skillName,
32
+ currentDomain: skill.domain,
33
+ predictedDomain: result.predictedDomain,
34
+ confidence: result.confidence,
35
+ filePath: skill.filePath,
36
+ });
37
+ if (autoFix) {
38
+ const targetDir = path.join(libraryPath, result.predictedDomain);
39
+ const targetPath = path.join(targetDir, `${skill.skillName}.md`);
40
+ try {
41
+ await fse.ensureDir(targetDir);
42
+ await fs.rename(skill.filePath, targetPath);
43
+ autoFixed++;
44
+ }
45
+ catch {
46
+ // 이동 실패 → 무시
47
+ }
48
+ }
49
+ }
50
+ const summary = `총 ${filtered.length}개 스킬 분석 | ` +
51
+ `올바른 위치: ${correctlyPlaced}개 | ` +
52
+ `잘못된 위치: ${misplaced.length}개` +
53
+ (autoFix ? ` | 자동 수정: ${autoFixed}개` : "");
54
+ return { misplaced, correctlyPlaced, autoFixed, summary };
55
+ }
56
+ }
57
+ //# sourceMappingURL=OrganizeSkillsUseCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrganizeSkillsUseCase.js","sourceRoot":"","sources":["../../src/application/OrganizeSkillsUseCase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface Skill {
2
+ domain: string;
3
+ skillName: string;
4
+ filePath: string;
5
+ description?: string;
6
+ confidence?: number;
7
+ }
8
+ export interface SkillReference {
9
+ domain: string;
10
+ skillName: string;
11
+ }
12
+ //# sourceMappingURL=Skill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Skill.d.ts","sourceRoot":"","sources":["../../../src/domain/skill/Skill.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Skill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Skill.js","sourceRoot":"","sources":["../../../src/domain/skill/Skill.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import { type Domain } from "./SkillDomain.js";
2
+ export interface ClassificationResult {
3
+ predictedDomain: Domain | "unknown";
4
+ confidence: number;
5
+ scores: Record<string, number>;
6
+ }
7
+ export declare class SkillClassifier {
8
+ classify(skillName: string, content?: string): ClassificationResult;
9
+ private tokenize;
10
+ private countMatches;
11
+ }
12
+ //# sourceMappingURL=SkillClassifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkillClassifier.d.ts","sourceRoot":"","sources":["../../../src/domain/skill/SkillClassifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,qBAAa,eAAe;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,oBAAoB;IAsDnE,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,YAAY;CAOrB"}
@@ -0,0 +1,60 @@
1
+ import { DOMAINS, DOMAIN_KEYWORDS } from "./SkillDomain.js";
2
+ export class SkillClassifier {
3
+ classify(skillName, content) {
4
+ const tokens = this.tokenize(skillName);
5
+ // frontmatter description 추출 (1.5× 가중치 적용)
6
+ let descriptionTokens = [];
7
+ if (content) {
8
+ const descMatch = content.match(/^description:\s*(.+)$/m);
9
+ if (descMatch) {
10
+ descriptionTokens = this.tokenize(descMatch[1]);
11
+ }
12
+ // 나머지 content 토큰 추가
13
+ tokens.push(...this.tokenize(content));
14
+ }
15
+ const scores = {};
16
+ let totalScore = 0;
17
+ for (const domain of DOMAINS) {
18
+ const keywords = DOMAIN_KEYWORDS[domain];
19
+ let score = 0;
20
+ for (const { keyword, weight } of keywords) {
21
+ const keyTokens = this.tokenize(keyword);
22
+ // 일반 토큰에서 매칭
23
+ const nameMatches = this.countMatches(tokens, keyTokens);
24
+ score += nameMatches * weight;
25
+ // description 토큰에서 매칭 (1.5× 가중치)
26
+ const descMatches = this.countMatches(descriptionTokens, keyTokens);
27
+ score += descMatches * weight * 1.5;
28
+ }
29
+ scores[domain] = score;
30
+ totalScore += score;
31
+ }
32
+ if (totalScore === 0) {
33
+ return { predictedDomain: "unknown", confidence: 0, scores };
34
+ }
35
+ const bestDomain = (Object.entries(scores).sort(([, a], [, b]) => b - a)[0][0]);
36
+ const bestScore = scores[bestDomain];
37
+ const confidence = bestScore / totalScore;
38
+ return {
39
+ predictedDomain: confidence >= 0.4 ? bestDomain : "unknown",
40
+ confidence,
41
+ scores,
42
+ };
43
+ }
44
+ tokenize(text) {
45
+ return text
46
+ .toLowerCase()
47
+ .replace(/[_\-./\\]/g, " ")
48
+ .split(/\s+/)
49
+ .filter((t) => t.length > 0);
50
+ }
51
+ countMatches(tokens, keywords) {
52
+ let count = 0;
53
+ for (const keyword of keywords) {
54
+ if (tokens.includes(keyword))
55
+ count++;
56
+ }
57
+ return count;
58
+ }
59
+ }
60
+ //# sourceMappingURL=SkillClassifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkillClassifier.js","sourceRoot":"","sources":["../../../src/domain/skill/SkillClassifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export declare const DOMAINS: readonly ["frontend", "testing", "security", "backend", "devops", "manager"];
2
+ export type Domain = (typeof DOMAINS)[number];
3
+ export interface KeywordEntry {
4
+ keyword: string;
5
+ weight: 1 | 2 | 3;
6
+ }
7
+ export declare const DOMAIN_KEYWORDS: Record<Domain, KeywordEntry[]>;
8
+ export declare const STACK_TO_SKILL: Record<string, {
9
+ domain: string;
10
+ skillName: string;
11
+ }>;
12
+ //# sourceMappingURL=SkillDomain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkillDomain.d.ts","sourceRoot":"","sources":["../../../src/domain/skill/SkillDomain.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,8EAOV,CAAC;AAEX,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CACnB;AAED,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAgH1D,CAAC;AAGF,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAkBhF,CAAC"}
@@ -0,0 +1,142 @@
1
+ export const DOMAINS = [
2
+ "frontend",
3
+ "testing",
4
+ "security",
5
+ "backend",
6
+ "devops",
7
+ "manager",
8
+ ];
9
+ export const DOMAIN_KEYWORDS = {
10
+ frontend: [
11
+ // weight 3 — 프레임워크 정확 매칭
12
+ { keyword: "next", weight: 3 },
13
+ { keyword: "nextjs", weight: 3 },
14
+ { keyword: "react", weight: 3 },
15
+ { keyword: "vue", weight: 3 },
16
+ { keyword: "nuxt", weight: 3 },
17
+ { keyword: "svelte", weight: 3 },
18
+ { keyword: "angular", weight: 3 },
19
+ { keyword: "vite", weight: 3 },
20
+ // weight 2 — 도구/개념
21
+ { keyword: "tailwind", weight: 2 },
22
+ { keyword: "css", weight: 2 },
23
+ { keyword: "ui", weight: 2 },
24
+ { keyword: "component", weight: 2 },
25
+ { keyword: "storybook", weight: 2 },
26
+ { keyword: "webpack", weight: 2 },
27
+ { keyword: "typescript", weight: 2 },
28
+ // weight 1 — 일반 용어
29
+ { keyword: "web", weight: 1 },
30
+ { keyword: "html", weight: 1 },
31
+ { keyword: "spa", weight: 1 },
32
+ { keyword: "ssr", weight: 1 },
33
+ { keyword: "client", weight: 1 },
34
+ ],
35
+ testing: [
36
+ { keyword: "vitest", weight: 3 },
37
+ { keyword: "jest", weight: 3 },
38
+ { keyword: "cypress", weight: 3 },
39
+ { keyword: "playwright", weight: 3 },
40
+ { keyword: "testing-library", weight: 3 },
41
+ { keyword: "mocha", weight: 3 },
42
+ { keyword: "e2e", weight: 2 },
43
+ { keyword: "unit", weight: 2 },
44
+ { keyword: "mock", weight: 2 },
45
+ { keyword: "coverage", weight: 2 },
46
+ { keyword: "assertion", weight: 2 },
47
+ { keyword: "test", weight: 1 },
48
+ { keyword: "spec", weight: 1 },
49
+ { keyword: "qa", weight: 1 },
50
+ ],
51
+ security: [
52
+ { keyword: "owasp", weight: 3 },
53
+ { keyword: "jwt", weight: 3 },
54
+ { keyword: "jose", weight: 3 },
55
+ { keyword: "oauth", weight: 3 },
56
+ { keyword: "xss", weight: 3 },
57
+ { keyword: "csrf", weight: 3 },
58
+ { keyword: "cors", weight: 2 },
59
+ { keyword: "auth", weight: 2 },
60
+ { keyword: "authentication", weight: 2 },
61
+ { keyword: "authorization", weight: 2 },
62
+ { keyword: "ssl", weight: 2 },
63
+ { keyword: "tls", weight: 2 },
64
+ { keyword: "encryption", weight: 2 },
65
+ { keyword: "security", weight: 1 },
66
+ { keyword: "permission", weight: 1 },
67
+ { keyword: "role", weight: 1 },
68
+ ],
69
+ backend: [
70
+ { keyword: "prisma", weight: 3 },
71
+ { keyword: "express", weight: 3 },
72
+ { keyword: "trpc", weight: 3 },
73
+ { keyword: "fastify", weight: 3 },
74
+ { keyword: "nestjs", weight: 3 },
75
+ { keyword: "graphql", weight: 3 },
76
+ { keyword: "rest", weight: 2 },
77
+ { keyword: "api", weight: 2 },
78
+ { keyword: "database", weight: 2 },
79
+ { keyword: "postgresql", weight: 2 },
80
+ { keyword: "mysql", weight: 2 },
81
+ { keyword: "mongodb", weight: 2 },
82
+ { keyword: "orm", weight: 2 },
83
+ { keyword: "server", weight: 1 },
84
+ { keyword: "endpoint", weight: 1 },
85
+ { keyword: "http", weight: 1 },
86
+ { keyword: "middleware", weight: 1 },
87
+ ],
88
+ devops: [
89
+ { keyword: "docker", weight: 3 },
90
+ { keyword: "kubernetes", weight: 3 },
91
+ { keyword: "k8s", weight: 3 },
92
+ { keyword: "terraform", weight: 3 },
93
+ { keyword: "github-actions", weight: 3 },
94
+ { keyword: "ci", weight: 2 },
95
+ { keyword: "cd", weight: 2 },
96
+ { keyword: "pipeline", weight: 2 },
97
+ { keyword: "deploy", weight: 2 },
98
+ { keyword: "container", weight: 2 },
99
+ { keyword: "helm", weight: 2 },
100
+ { keyword: "aws", weight: 2 },
101
+ { keyword: "gcp", weight: 2 },
102
+ { keyword: "azure", weight: 2 },
103
+ { keyword: "infra", weight: 1 },
104
+ { keyword: "devops", weight: 1 },
105
+ { keyword: "monitoring", weight: 1 },
106
+ ],
107
+ manager: [
108
+ { keyword: "agile", weight: 3 },
109
+ { keyword: "scrum", weight: 3 },
110
+ { keyword: "kanban", weight: 3 },
111
+ { keyword: "sprint", weight: 2 },
112
+ { keyword: "roadmap", weight: 2 },
113
+ { keyword: "backlog", weight: 2 },
114
+ { keyword: "milestone", weight: 2 },
115
+ { keyword: "jira", weight: 2 },
116
+ { keyword: "project", weight: 1 },
117
+ { keyword: "planning", weight: 1 },
118
+ { keyword: "meeting", weight: 1 },
119
+ { keyword: "standup", weight: 1 },
120
+ ],
121
+ };
122
+ // 기술 스택 → 스킬 매핑 테이블
123
+ export const STACK_TO_SKILL = {
124
+ next: { domain: "frontend", skillName: "next-best-practices" },
125
+ nextjs: { domain: "frontend", skillName: "next-best-practices" },
126
+ react: { domain: "frontend", skillName: "vercel-react-best-practices" },
127
+ "react-native": { domain: "frontend", skillName: "vercel-react-native-skills" },
128
+ vue: { domain: "frontend", skillName: "vue-best-practices" },
129
+ vitest: { domain: "testing", skillName: "vitest-practices" },
130
+ jest: { domain: "testing", skillName: "jest-practices" },
131
+ cypress: { domain: "testing", skillName: "e2e-testing" },
132
+ playwright: { domain: "testing", skillName: "playwright-testing" },
133
+ prisma: { domain: "backend", skillName: "prisma-best-practices" },
134
+ express: { domain: "backend", skillName: "express-api-patterns" },
135
+ trpc: { domain: "backend", skillName: "trpc-patterns" },
136
+ graphql: { domain: "backend", skillName: "graphql-api-patterns" },
137
+ jsonwebtoken: { domain: "security", skillName: "jwt-auth-practices" },
138
+ jose: { domain: "security", skillName: "jwt-auth-practices" },
139
+ docker: { domain: "devops", skillName: "docker-deployment" },
140
+ kubernetes: { domain: "devops", skillName: "kubernetes-deployment" },
141
+ };
142
+ //# sourceMappingURL=SkillDomain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkillDomain.js","sourceRoot":"","sources":["../../../src/domain/skill/SkillDomain.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,17 +2,55 @@
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
+ import fs from "fs";
6
+ import path from "path";
5
7
  import { DiagnoseProjectUseCase } from "./application/DiagnoseProjectUseCase.js";
6
8
  import { SetupSkillsUseCase } from "./application/SetupSkillsUseCase.js";
7
9
  import { SyncClaudeMdUseCase } from "./application/SyncClaudeMdUseCase.js";
8
- import { SkillsCliRunner } from "./infrastructure/skills/SkillsCliRunner.js";
10
+ import { SkillsShClient } from "./infrastructure/skills/SkillsShClient.js";
11
+ import { SkillsScorer } from "./infrastructure/skills/SkillsScorer.js";
12
+ // ─── CLI 서브커맨드 처리 ──────────────────────────────────────────────────────
13
+ const subcommand = process.argv[2];
14
+ if (subcommand === "init") {
15
+ const cwd = process.cwd();
16
+ const mcpJsonPath = path.join(cwd, ".mcp.json");
17
+ const entry = {
18
+ command: "npx",
19
+ args: ["-y", "oh-my-agent"],
20
+ };
21
+ let config = { mcpServers: {} };
22
+ if (fs.existsSync(mcpJsonPath)) {
23
+ try {
24
+ const existing = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
25
+ if (existing && typeof existing === "object") {
26
+ config = existing;
27
+ if (!config.mcpServers)
28
+ config.mcpServers = {};
29
+ }
30
+ }
31
+ catch {
32
+ // 파싱 실패 시 새로 덮어씀
33
+ }
34
+ }
35
+ const alreadyExists = "oh-my-agent" in config.mcpServers;
36
+ config.mcpServers["oh-my-agent"] = entry;
37
+ fs.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
38
+ if (alreadyExists) {
39
+ console.log(`✔ .mcp.json already had oh-my-agent — updated: ${mcpJsonPath}`);
40
+ }
41
+ else {
42
+ console.log(`✔ .mcp.json created: ${mcpJsonPath}`);
43
+ }
44
+ console.log("Reload Claude Code to activate the MCP server.");
45
+ process.exit(0);
46
+ }
9
47
  const server = new McpServer({
10
48
  name: "oh-my-agent",
11
49
  version: "1.0.0",
12
50
  });
13
51
  // ─── diagnose_project ────────────────────────────────────────────────────────
14
- server.tool("diagnose_project", `프로젝트의 package.json을 분석해 기술 스택을 감지하고 skills.sh 기반 추천 스킬 목록을 반환합니다.
15
- setup_project를 호출하기 전에 먼저 실행해 어떤 스킬이 설치될지 미리 확인할 수 있습니다.`, {
52
+ server.tool("diagnose_project", `프로젝트의 기술 스택을 감지하고 설치 가능한 추천 스킬 목록을 반환합니다.
53
+ 스킬을 실제로 설치하려면 setup_project 도구를 사용하세요.`, {
16
54
  projectPath: z
17
55
  .string()
18
56
  .optional()
@@ -29,26 +67,26 @@ setup_project를 호출하기 전에 먼저 실행해 어떤 스킬이 설치될
29
67
  };
30
68
  });
31
69
  // ─── setup_project ───────────────────────────────────────────────────────────
32
- server.tool("setup_project", `프로젝트 기술 스택을 자동으로 진단한 뒤, skills.sh에서 적합한 AI 스킬을 설치합니다.
33
- npx skills add 통해 .claude/skills/ 에 스킬 파일을 생성하고 CLAUDE.md를 업데이트합니다.
34
- find-skills, skill-creator 메타 스킬도 함께 설치됩니다.`, {
70
+ server.tool("setup_project", `프로젝트에 AI 스킬을 자동으로 설치합니다.
71
+ 기술 스택을 감지하고 skills.sh에서 최적의 스킬을 찾아 .claude/skills/ 에 설치한 CLAUDE.md를 업데이트합니다.
72
+ 스킬 설치가 필요할 도구를 직접 호출하세요. 외부 CLI를 별도로 실행하지 마세요.`, {
35
73
  projectPath: z.string().describe("세팅할 프로젝트 경로"),
36
74
  skillRefs: z
37
75
  .array(z.string())
38
76
  .optional()
39
- .describe('직접 지정할 스킬 레퍼런스 목록 (예: ["vercel-labs/next-skills@next-best-practices"]). 미입력 자동 감지.'),
77
+ .describe('설치할 스킬을 직접 지정 (예: ["vercel-labs/next-skills@next-best-practices"]). 생략하면 자동 감지.'),
40
78
  includeMetaSkills: z
41
79
  .boolean()
42
80
  .optional()
43
- .describe("find-skills, skill-creator 메타 스킬 포함 여부 (기본값: true)"),
81
+ .describe("메타 스킬(find-skills, skill-creator) 포함 여부 (기본값: true)"),
44
82
  syncClaudeMd: z
45
83
  .boolean()
46
84
  .optional()
47
- .describe("설치 후 실제 파일 기준으로 CLAUDE.md 동기화 여부 (기본값: true)"),
85
+ .describe("설치 후 CLAUDE.md 동기화 여부 (기본값: true)"),
48
86
  dryRun: z
49
87
  .boolean()
50
88
  .optional()
51
- .describe("실제 설치 없이 설치될 스킬 목록만 미리보기 (기본값: false)"),
89
+ .describe("실제 설치 없이 설치 예정 목록만 반환 (기본값: false)"),
52
90
  }, async ({ projectPath, skillRefs, includeMetaSkills, syncClaudeMd, dryRun }) => {
53
91
  const useCase = new SetupSkillsUseCase();
54
92
  const result = await useCase.execute({
@@ -63,30 +101,32 @@ find-skills, skill-creator 메타 스킬도 함께 설치됩니다.`, {
63
101
  };
64
102
  });
65
103
  // ─── find_skills ─────────────────────────────────────────────────────────────
66
- server.tool("find_skills", `skills.sh에서 특정 키워드로 스킬을 검색합니다.
67
- 유저가 특정 기능이나 기술에 맞는 스킬을 찾고 싶을 사용합니다.`, {
68
- query: z.string().describe("검색할 키워드 (예: 'nextjs', 'testing', 'docker')"),
104
+ server.tool("find_skills", `키워드로 skills.sh에서 스킬을 검색하고 점수화된 후보 목록을 반환합니다.
105
+ 검색 결과를 보고 설치하려면 setup_project 도구에 skillRefs를 전달하세요. CLI를 직접 실행하지 마세요.`, {
106
+ query: z.string().describe("검색 키워드 (예: 'nextjs', 'testing', 'docker')"),
69
107
  projectPath: z
70
108
  .string()
71
109
  .optional()
72
110
  .describe("실행 기준 프로젝트 경로 (기본값: 현재 디렉토리)"),
73
111
  }, async ({ query, projectPath }) => {
74
- const runner = new SkillsCliRunner();
112
+ const client = new SkillsShClient();
113
+ const scorer = new SkillsScorer();
75
114
  const cwd = projectPath ?? process.cwd();
76
115
  try {
77
- const output = await runner.find(query, cwd);
116
+ const candidates = await client.searchSkills(query, cwd);
117
+ const scored = scorer.score(candidates);
78
118
  return {
79
- content: [{ type: "text", text: output }],
119
+ content: [{ type: "text", text: JSON.stringify(scored, null, 2) }],
80
120
  };
81
121
  }
82
122
  catch (err) {
83
123
  return {
84
- content: [{ type: "text", text: `검색 실패: ${String(err)}` }],
124
+ content: [{ type: "text", text: JSON.stringify({ error: String(err) }) }],
85
125
  };
86
126
  }
87
127
  });
88
128
  // ─── sync_claude_md ───────────────────────────────────────────────────────────
89
- server.tool("sync_claude_md", `.claude/skills/ 디렉토리를 스캔해 broken 링크를 감지하고 CLAUDE.md oh-my-agent 마커 섹션을 동기화합니다.`, {
129
+ server.tool("sync_claude_md", `.claude/skills/ 실제 설치된 파일을 기준으로 CLAUDE.md 동기화합니다.`, {
90
130
  projectPath: z.string().describe("대상 프로젝트 경로"),
91
131
  createIfMissing: z
92
132
  .boolean()
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare class PathResolver {
2
+ static resolveHome(inputPath: string): string;
3
+ static getLibraryPath(): string;
4
+ static getSkillsPath(projectPath: string): string;
5
+ static getClaudeMdPath(projectPath: string): string;
6
+ static normalize(inputPath: string): string;
7
+ }
8
+ //# sourceMappingURL=PathResolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PathResolver.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/config/PathResolver.ts"],"names":[],"mappings":"AAGA,qBAAa,YAAY;IACvB,MAAM,CAAC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAQ7C,MAAM,CAAC,cAAc,IAAI,MAAM;IAI/B,MAAM,CAAC,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAIjD,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAInD,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAG5C"}
@@ -0,0 +1,24 @@
1
+ import os from "os";
2
+ import path from "path";
3
+ export class PathResolver {
4
+ static resolveHome(inputPath) {
5
+ if (inputPath.startsWith("~/") || inputPath === "~") {
6
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
7
+ return path.join(home, inputPath.slice(2));
8
+ }
9
+ return inputPath;
10
+ }
11
+ static getLibraryPath() {
12
+ return this.resolveHome("~/oh-my-agent-library");
13
+ }
14
+ static getSkillsPath(projectPath) {
15
+ return path.join(projectPath, ".claude", "skills");
16
+ }
17
+ static getClaudeMdPath(projectPath) {
18
+ return path.join(projectPath, "CLAUDE.md");
19
+ }
20
+ static normalize(inputPath) {
21
+ return path.normalize(inputPath).replace(/\\/g, "/");
22
+ }
23
+ }
24
+ //# sourceMappingURL=PathResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PathResolver.js","sourceRoot":"","sources":["../../../src/infrastructure/config/PathResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,OAAO,YAAY;IACvB,MAAM,CAAC,WAAW,CAAC,SAAiB;QAClC,IAAI,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACpD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;YACzE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,cAAc;QACnB,OAAO,IAAI,CAAC,WAAW,CAAC,uBAAuB,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,CAAC,aAAa,CAAC,WAAmB;QACtC,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,CAAC,eAAe,CAAC,WAAmB;QACxC,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,SAAiB;QAChC,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;CACF"}
@@ -0,0 +1,19 @@
1
+ export interface SkillFile {
2
+ domain: string;
3
+ skillName: string;
4
+ filePath: string;
5
+ content?: string;
6
+ }
7
+ export declare class LibraryScanner {
8
+ private libraryPath;
9
+ constructor(libraryPath?: string);
10
+ exists(): Promise<boolean>;
11
+ getDomains(): Promise<string[]>;
12
+ scanAll(includeContent?: boolean): Promise<SkillFile[]>;
13
+ scanDomain(domain: string, includeContent?: boolean): Promise<SkillFile[]>;
14
+ getSkillPath(domain: string, skillName: string): Promise<string>;
15
+ skillExists(domain: string, skillName: string): Promise<boolean>;
16
+ readSkillContent(domain: string, skillName: string): Promise<string | null>;
17
+ getLibraryPath(): string;
18
+ }
19
+ //# sourceMappingURL=LibraryScanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LibraryScanner.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/fs/LibraryScanner.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,CAAC,EAAE,MAAM;IAI1B,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAI1B,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAY/B,OAAO,CAAC,cAAc,UAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IA2BrD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,UAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAwBxE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKhE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKhE,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASjF,cAAc,IAAI,MAAM;CAGzB"}
@@ -0,0 +1,94 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import fse from "fs-extra";
4
+ import { PathResolver } from "../config/PathResolver.js";
5
+ export class LibraryScanner {
6
+ libraryPath;
7
+ constructor(libraryPath) {
8
+ this.libraryPath = libraryPath ?? PathResolver.getLibraryPath();
9
+ }
10
+ async exists() {
11
+ return fse.pathExists(this.libraryPath);
12
+ }
13
+ async getDomains() {
14
+ if (!(await this.exists()))
15
+ return [];
16
+ try {
17
+ const entries = await fs.readdir(this.libraryPath, { withFileTypes: true });
18
+ return entries
19
+ .filter((e) => e.isDirectory())
20
+ .map((e) => e.name);
21
+ }
22
+ catch {
23
+ return [];
24
+ }
25
+ }
26
+ async scanAll(includeContent = false) {
27
+ const domains = await this.getDomains();
28
+ const skills = [];
29
+ for (const domain of domains) {
30
+ const domainPath = path.join(this.libraryPath, domain);
31
+ try {
32
+ const entries = await fs.readdir(domainPath, { withFileTypes: true });
33
+ for (const entry of entries) {
34
+ if (entry.isFile() && entry.name.endsWith(".md")) {
35
+ const filePath = path.join(domainPath, entry.name);
36
+ const skillName = entry.name.replace(/\.md$/, "");
37
+ let content;
38
+ if (includeContent) {
39
+ content = await fs.readFile(filePath, "utf-8");
40
+ }
41
+ skills.push({ domain, skillName, filePath, content });
42
+ }
43
+ }
44
+ }
45
+ catch {
46
+ // 도메인 폴더 읽기 실패 → 스킵
47
+ }
48
+ }
49
+ return skills;
50
+ }
51
+ async scanDomain(domain, includeContent = false) {
52
+ const domainPath = path.join(this.libraryPath, domain);
53
+ const skills = [];
54
+ try {
55
+ const entries = await fs.readdir(domainPath, { withFileTypes: true });
56
+ for (const entry of entries) {
57
+ if (entry.isFile() && entry.name.endsWith(".md")) {
58
+ const filePath = path.join(domainPath, entry.name);
59
+ const skillName = entry.name.replace(/\.md$/, "");
60
+ let content;
61
+ if (includeContent) {
62
+ content = await fs.readFile(filePath, "utf-8");
63
+ }
64
+ skills.push({ domain, skillName, filePath, content });
65
+ }
66
+ }
67
+ }
68
+ catch {
69
+ // 도메인 없음
70
+ }
71
+ return skills;
72
+ }
73
+ async getSkillPath(domain, skillName) {
74
+ const fileName = skillName.endsWith(".md") ? skillName : `${skillName}.md`;
75
+ return path.join(this.libraryPath, domain, fileName);
76
+ }
77
+ async skillExists(domain, skillName) {
78
+ const skillPath = await this.getSkillPath(domain, skillName);
79
+ return fse.pathExists(skillPath);
80
+ }
81
+ async readSkillContent(domain, skillName) {
82
+ const skillPath = await this.getSkillPath(domain, skillName);
83
+ try {
84
+ return await fs.readFile(skillPath, "utf-8");
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ getLibraryPath() {
91
+ return this.libraryPath;
92
+ }
93
+ }
94
+ //# sourceMappingURL=LibraryScanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LibraryScanner.js","sourceRoot":"","sources":["../../../src/infrastructure/fs/LibraryScanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface SymlinkResult {
2
+ success: boolean;
3
+ method: "symlink" | "junction" | "copy" | "already_exists";
4
+ error?: string;
5
+ }
6
+ export declare class SymlinkManager {
7
+ createSymlink(target: string, linkPath: string): Promise<SymlinkResult>;
8
+ removeSymlink(linkPath: string): Promise<boolean>;
9
+ isValidSymlink(linkPath: string): Promise<boolean>;
10
+ listBrokenLinks(dir: string): Promise<string[]>;
11
+ }
12
+ //# sourceMappingURL=SymlinkManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SymlinkManager.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/fs/SymlinkManager.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,gBAAgB,CAAC;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,cAAc;IACnB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA+DvE,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAajD,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWlD,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAgBtD"}
@@ -0,0 +1,111 @@
1
+ import fs from "fs/promises";
2
+ import fse from "fs-extra";
3
+ import path from "path";
4
+ export class SymlinkManager {
5
+ async createSymlink(target, linkPath) {
6
+ // 링크 디렉토리 생성
7
+ await fse.ensureDir(path.dirname(linkPath));
8
+ // 이미 존재하는 경우 확인
9
+ try {
10
+ const stat = await fs.lstat(linkPath);
11
+ if (stat.isSymbolicLink()) {
12
+ const existing = await fs.readlink(linkPath);
13
+ if (existing === target) {
14
+ return { success: true, method: "already_exists" };
15
+ }
16
+ // 다른 타겟 → 교체
17
+ await fs.unlink(linkPath);
18
+ }
19
+ else {
20
+ // 일반 파일/디렉토리 → 건드리지 않음
21
+ return {
22
+ success: false,
23
+ method: "symlink",
24
+ error: `링크 경로에 이미 일반 파일/디렉토리가 존재합니다: ${linkPath}`,
25
+ };
26
+ }
27
+ }
28
+ catch {
29
+ // 파일이 없으면 정상 진행
30
+ }
31
+ // 타겟 존재 확인
32
+ const targetExists = await fse.pathExists(target);
33
+ if (!targetExists) {
34
+ return {
35
+ success: false,
36
+ method: "symlink",
37
+ error: `타겟 경로가 존재하지 않습니다: ${target}`,
38
+ };
39
+ }
40
+ // POSIX 심볼릭 링크 시도
41
+ try {
42
+ await fs.symlink(target, linkPath);
43
+ return { success: true, method: "symlink" };
44
+ }
45
+ catch {
46
+ // junction 시도 (Windows)
47
+ }
48
+ try {
49
+ await fs.symlink(target, linkPath, "junction");
50
+ return { success: true, method: "junction" };
51
+ }
52
+ catch {
53
+ // 폴백: 복사
54
+ }
55
+ try {
56
+ await fse.copy(target, linkPath);
57
+ return { success: true, method: "copy" };
58
+ }
59
+ catch (err) {
60
+ return {
61
+ success: false,
62
+ method: "copy",
63
+ error: String(err),
64
+ };
65
+ }
66
+ }
67
+ async removeSymlink(linkPath) {
68
+ try {
69
+ const stat = await fs.lstat(linkPath);
70
+ if (stat.isSymbolicLink()) {
71
+ await fs.unlink(linkPath);
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ async isValidSymlink(linkPath) {
81
+ try {
82
+ const stat = await fs.lstat(linkPath);
83
+ if (!stat.isSymbolicLink())
84
+ return false;
85
+ const target = await fs.readlink(linkPath);
86
+ return await fse.pathExists(target);
87
+ }
88
+ catch {
89
+ return false;
90
+ }
91
+ }
92
+ async listBrokenLinks(dir) {
93
+ const broken = [];
94
+ try {
95
+ const entries = await fs.readdir(dir, { withFileTypes: true });
96
+ for (const entry of entries) {
97
+ const fullPath = path.join(dir, entry.name);
98
+ if (entry.isSymbolicLink()) {
99
+ const valid = await this.isValidSymlink(fullPath);
100
+ if (!valid)
101
+ broken.push(fullPath);
102
+ }
103
+ }
104
+ }
105
+ catch {
106
+ // 디렉토리가 없으면 빈 배열
107
+ }
108
+ return broken;
109
+ }
110
+ }
111
+ //# sourceMappingURL=SymlinkManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SymlinkManager.js","sourceRoot":"","sources":["../../../src/infrastructure/fs/SymlinkManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 기술 스택 키워드 → skills.sh 스킬 레퍼런스 매핑
3
+ * 형식: "owner/repo@skill-name"
4
+ */
5
+ export interface SkillRef {
6
+ ref: string;
7
+ domain: string;
8
+ description: string;
9
+ }
10
+ export declare const STACK_TO_SKILL_REF: Record<string, SkillRef>;
11
+ /**
12
+ * 항상 설치하는 메타 스킬 (스킬 탐색 및 생성 지원)
13
+ */
14
+ export declare const META_SKILLS: SkillRef[];
15
+ //# sourceMappingURL=SkillsRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkillsRegistry.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/skills/SkillsRegistry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAwFvD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,QAAQ,EAWjC,CAAC"}
@@ -0,0 +1,102 @@
1
+ export const STACK_TO_SKILL_REF = {
2
+ // Frontend
3
+ next: {
4
+ ref: "vercel-labs/next-skills@next-best-practices",
5
+ domain: "frontend",
6
+ description: "Next.js 모범 사례",
7
+ },
8
+ nextjs: {
9
+ ref: "vercel-labs/next-skills@next-best-practices",
10
+ domain: "frontend",
11
+ description: "Next.js 모범 사례",
12
+ },
13
+ react: {
14
+ ref: "vercel-labs/agent-skills@vercel-react-best-practices",
15
+ domain: "frontend",
16
+ description: "React 모범 사례",
17
+ },
18
+ "react-native": {
19
+ ref: "callstackincubator/agent-skills@react-native-best-practices",
20
+ domain: "frontend",
21
+ description: "React Native 모범 사례",
22
+ },
23
+ vue: {
24
+ ref: "antfu/skills@vue-testing-best-practices",
25
+ domain: "frontend",
26
+ description: "Vue 테스트 모범 사례",
27
+ },
28
+ // Testing
29
+ vitest: {
30
+ ref: "antfu/skills@vitest",
31
+ domain: "testing",
32
+ description: "Vitest 단위 테스트",
33
+ },
34
+ jest: {
35
+ ref: "wshobson/agents@javascript-testing-patterns",
36
+ domain: "testing",
37
+ description: "JavaScript 테스트 패턴",
38
+ },
39
+ playwright: {
40
+ ref: "anthropics/skills@webapp-testing",
41
+ domain: "testing",
42
+ description: "Playwright 웹 앱 테스팅",
43
+ },
44
+ cypress: {
45
+ ref: "wshobson/agents@e2e-testing-patterns",
46
+ domain: "testing",
47
+ description: "E2E 테스트 패턴",
48
+ },
49
+ // Backend
50
+ prisma: {
51
+ ref: "sickn33/antigravity-awesome-skills@prisma-expert",
52
+ domain: "backend",
53
+ description: "Prisma ORM 전문가",
54
+ },
55
+ express: {
56
+ ref: "wshobson/agents@nodejs-backend-patterns",
57
+ domain: "backend",
58
+ description: "Node.js 백엔드 패턴",
59
+ },
60
+ fastify: {
61
+ ref: "wshobson/agents@nodejs-backend-patterns",
62
+ domain: "backend",
63
+ description: "Node.js 백엔드 패턴",
64
+ },
65
+ graphql: {
66
+ ref: "wshobson/agents@nodejs-backend-patterns",
67
+ domain: "backend",
68
+ description: "Node.js 백엔드 패턴",
69
+ },
70
+ // DevOps
71
+ docker: {
72
+ ref: "sickn33/antigravity-awesome-skills@docker-expert",
73
+ domain: "devops",
74
+ description: "Docker 전문가",
75
+ },
76
+ kubernetes: {
77
+ ref: "wshobson/agents@k8s-manifest-generator",
78
+ domain: "devops",
79
+ description: "Kubernetes 매니페스트 생성",
80
+ },
81
+ "github-actions": {
82
+ ref: "wshobson/agents@github-actions-templates",
83
+ domain: "devops",
84
+ description: "GitHub Actions 템플릿",
85
+ },
86
+ };
87
+ /**
88
+ * 항상 설치하는 메타 스킬 (스킬 탐색 및 생성 지원)
89
+ */
90
+ export const META_SKILLS = [
91
+ {
92
+ ref: "vercel-labs/skills@find-skills",
93
+ domain: "meta",
94
+ description: "skills.sh에서 스킬 탐색 및 설치",
95
+ },
96
+ {
97
+ ref: "anthropics/skills@skill-creator",
98
+ domain: "meta",
99
+ description: "새로운 스킬 생성 가이드",
100
+ },
101
+ ];
102
+ //# sourceMappingURL=SkillsRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkillsRegistry.js","sourceRoot":"","sources":["../../../src/infrastructure/skills/SkillsRegistry.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,kBAAkB,GAA6B;IAC1D,WAAW;IACX,IAAI,EAAE;QACJ,GAAG,EAAE,6CAA6C;QAClD,MAAM,EAAE,UAAU;QAClB,WAAW,EAAE,eAAe;KAC7B;IACD,MAAM,EAAE;QACN,GAAG,EAAE,6CAA6C;QAClD,MAAM,EAAE,UAAU;QAClB,WAAW,EAAE,eAAe;KAC7B;IACD,KAAK,EAAE;QACL,GAAG,EAAE,sDAAsD;QAC3D,MAAM,EAAE,UAAU;QAClB,WAAW,EAAE,aAAa;KAC3B;IACD,cAAc,EAAE;QACd,GAAG,EAAE,6DAA6D;QAClE,MAAM,EAAE,UAAU;QAClB,WAAW,EAAE,oBAAoB;KAClC;IACD,GAAG,EAAE;QACH,GAAG,EAAE,yCAAyC;QAC9C,MAAM,EAAE,UAAU;QAClB,WAAW,EAAE,eAAe;KAC7B;IAED,UAAU;IACV,MAAM,EAAE;QACN,GAAG,EAAE,qBAAqB;QAC1B,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,eAAe;KAC7B;IACD,IAAI,EAAE;QACJ,GAAG,EAAE,6CAA6C;QAClD,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,mBAAmB;KACjC;IACD,UAAU,EAAE;QACV,GAAG,EAAE,kCAAkC;QACvC,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,oBAAoB;KAClC;IACD,OAAO,EAAE;QACP,GAAG,EAAE,sCAAsC;QAC3C,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,YAAY;KAC1B;IAED,UAAU;IACV,MAAM,EAAE;QACN,GAAG,EAAE,kDAAkD;QACvD,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,gBAAgB;KAC9B;IACD,OAAO,EAAE;QACP,GAAG,EAAE,yCAAyC;QAC9C,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,gBAAgB;KAC9B;IACD,OAAO,EAAE;QACP,GAAG,EAAE,yCAAyC;QAC9C,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,gBAAgB;KAC9B;IACD,OAAO,EAAE;QACP,GAAG,EAAE,yCAAyC;QAC9C,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,gBAAgB;KAC9B;IAED,SAAS;IACT,MAAM,EAAE;QACN,GAAG,EAAE,kDAAkD;QACvD,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,YAAY;KAC1B;IACD,UAAU,EAAE;QACV,GAAG,EAAE,wCAAwC;QAC7C,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,qBAAqB;KACnC;IACD,gBAAgB,EAAE;QAChB,GAAG,EAAE,0CAA0C;QAC/C,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,oBAAoB;KAClC;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAe;IACrC;QACE,GAAG,EAAE,gCAAgC;QACrC,MAAM,EAAE,MAAM;QACd,WAAW,EAAE,wBAAwB;KACtC;IACD;QACE,GAAG,EAAE,iCAAiC;QACtC,MAAM,EAAE,MAAM;QACd,WAAW,EAAE,eAAe;KAC7B;CACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-agent",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "AI 스킬을 자동으로 진단·배포·관리하는 MCP 서버. 프로젝트 기술 스택을 분석해 적합한 AI 스킬을 추천·설치합니다.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",