add-ai-tools 1.2.1 → 1.2.3
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.mjs +122 -18
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -214,8 +214,10 @@ const BITBUCKET_URL_REGEX = /^https?:\/\/(?:[^@]+@)?(?:www\.)?bitbucket\.org\/([
|
|
|
214
214
|
* GitHub shorthand 매칭 정규식
|
|
215
215
|
* 예: owner/repo
|
|
216
216
|
* vercel-labs/agent-skills
|
|
217
|
+
* khw1031/core/skills
|
|
218
|
+
* owner/repo/deep/nested/path
|
|
217
219
|
*/
|
|
218
|
-
const GITHUB_SHORTHAND_REGEX = /^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)
|
|
220
|
+
const GITHUB_SHORTHAND_REGEX = /^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)(?:\/(.+))?$/;
|
|
219
221
|
/**
|
|
220
222
|
* Git URL 매칭 정규식 (SSH 또는 git:// 프로토콜)
|
|
221
223
|
* 예: git@github.com:owner/repo.git
|
|
@@ -308,12 +310,13 @@ function parseSource(input) {
|
|
|
308
310
|
}
|
|
309
311
|
const shorthandMatch = trimmed.match(GITHUB_SHORTHAND_REGEX);
|
|
310
312
|
if (shorthandMatch) {
|
|
311
|
-
const [, owner, repo] = shorthandMatch;
|
|
313
|
+
const [, owner, repo, subpath] = shorthandMatch;
|
|
312
314
|
return {
|
|
313
315
|
type: "github",
|
|
314
316
|
url: `https://github.com/${owner}/${repo}`,
|
|
315
317
|
owner,
|
|
316
318
|
repo,
|
|
319
|
+
subpath: subpath || void 0,
|
|
317
320
|
raw: input
|
|
318
321
|
};
|
|
319
322
|
}
|
|
@@ -465,6 +468,22 @@ var GitHubRateLimitError = class extends GitHubApiError {
|
|
|
465
468
|
* - API 호출: 2회 (branch SHA + tree)
|
|
466
469
|
* - 파일 fetch: raw.githubusercontent.com (Rate Limit 영향 없음)
|
|
467
470
|
*/
|
|
471
|
+
/**
|
|
472
|
+
* 리소스 파일이 아닌 일반 문서 파일 목록
|
|
473
|
+
* findMainResourceFile 폴백 및 루트 레벨 처리에서 제외됩니다.
|
|
474
|
+
*/
|
|
475
|
+
const NON_RESOURCE_FILES$1 = [
|
|
476
|
+
"README.md",
|
|
477
|
+
"readme.md",
|
|
478
|
+
"CHANGELOG.md",
|
|
479
|
+
"changelog.md",
|
|
480
|
+
"LICENSE.md",
|
|
481
|
+
"license.md",
|
|
482
|
+
"CONTRIBUTING.md",
|
|
483
|
+
"contributing.md",
|
|
484
|
+
"CLAUDE.md",
|
|
485
|
+
"AGENTS.md"
|
|
486
|
+
];
|
|
468
487
|
var GitHubFetcher = class {
|
|
469
488
|
parser;
|
|
470
489
|
baseApiUrl = "https://api.github.com";
|
|
@@ -583,9 +602,12 @@ var GitHubFetcher = class {
|
|
|
583
602
|
const resource = this.parseSubdirectoryResource(subdir, files, type);
|
|
584
603
|
if (resource) resources.push(resource);
|
|
585
604
|
}
|
|
586
|
-
for (const file of structure.rootFiles)
|
|
587
|
-
const
|
|
588
|
-
if (
|
|
605
|
+
for (const file of structure.rootFiles) {
|
|
606
|
+
const fileName = this.getFileName(file.path);
|
|
607
|
+
if (file.path.endsWith(".md") && !NON_RESOURCE_FILES$1.some((nr) => fileName.toLowerCase() === nr.toLowerCase())) {
|
|
608
|
+
const resource = this.parseSingleFileResource(file, type);
|
|
609
|
+
if (resource) resources.push(resource);
|
|
610
|
+
}
|
|
589
611
|
}
|
|
590
612
|
return resources;
|
|
591
613
|
}
|
|
@@ -692,8 +714,7 @@ var GitHubFetcher = class {
|
|
|
692
714
|
"RULE.md",
|
|
693
715
|
"RULES.md",
|
|
694
716
|
"rule.md",
|
|
695
|
-
"rules.md"
|
|
696
|
-
"README.md"
|
|
717
|
+
"rules.md"
|
|
697
718
|
],
|
|
698
719
|
agents: ["AGENT.md", "agent.md"]
|
|
699
720
|
}[type] || [];
|
|
@@ -703,7 +724,10 @@ var GitHubFetcher = class {
|
|
|
703
724
|
});
|
|
704
725
|
if (found) return found;
|
|
705
726
|
}
|
|
706
|
-
return files.find((file) =>
|
|
727
|
+
return files.find((file) => {
|
|
728
|
+
const fileName = this.getFileName(file.path);
|
|
729
|
+
return file.path.endsWith(".md") && !NON_RESOURCE_FILES$1.some((nr) => fileName.toLowerCase() === nr.toLowerCase());
|
|
730
|
+
}) || null;
|
|
707
731
|
}
|
|
708
732
|
};
|
|
709
733
|
const githubFetcher = new GitHubFetcher();
|
|
@@ -728,6 +752,22 @@ var BitbucketApiError = class extends Error {
|
|
|
728
752
|
* git archive --remote 명령을 사용하여 SSH 키 인증을 활용합니다.
|
|
729
753
|
* 최적화: 단일 SSH 호출로 전체 디렉토리를 가져와 캐시합니다.
|
|
730
754
|
*/
|
|
755
|
+
/**
|
|
756
|
+
* 리소스 파일이 아닌 일반 문서 파일 목록
|
|
757
|
+
* findMainResourceFile 폴백 및 루트 레벨 처리에서 제외됩니다.
|
|
758
|
+
*/
|
|
759
|
+
const NON_RESOURCE_FILES = [
|
|
760
|
+
"README.md",
|
|
761
|
+
"readme.md",
|
|
762
|
+
"CHANGELOG.md",
|
|
763
|
+
"changelog.md",
|
|
764
|
+
"LICENSE.md",
|
|
765
|
+
"license.md",
|
|
766
|
+
"CONTRIBUTING.md",
|
|
767
|
+
"contributing.md",
|
|
768
|
+
"CLAUDE.md",
|
|
769
|
+
"AGENTS.md"
|
|
770
|
+
];
|
|
731
771
|
var BitbucketFetcher = class {
|
|
732
772
|
parser;
|
|
733
773
|
fileCache = /* @__PURE__ */ new Map();
|
|
@@ -827,9 +867,12 @@ var BitbucketFetcher = class {
|
|
|
827
867
|
const resource = this.parseSubdirectoryResource(subdir, files, type);
|
|
828
868
|
if (resource) resources.push(resource);
|
|
829
869
|
}
|
|
830
|
-
for (const file of structure.rootFiles)
|
|
831
|
-
const
|
|
832
|
-
if (
|
|
870
|
+
for (const file of structure.rootFiles) {
|
|
871
|
+
const fileName = this.getFileName(file.path);
|
|
872
|
+
if (file.path.endsWith(".md") && !NON_RESOURCE_FILES.some((nr) => fileName.toLowerCase() === nr.toLowerCase())) {
|
|
873
|
+
const resource = this.parseSingleFileResource(file, type);
|
|
874
|
+
if (resource) resources.push(resource);
|
|
875
|
+
}
|
|
833
876
|
}
|
|
834
877
|
return resources;
|
|
835
878
|
}
|
|
@@ -926,8 +969,7 @@ var BitbucketFetcher = class {
|
|
|
926
969
|
"RULE.md",
|
|
927
970
|
"RULES.md",
|
|
928
971
|
"rule.md",
|
|
929
|
-
"rules.md"
|
|
930
|
-
"README.md"
|
|
972
|
+
"rules.md"
|
|
931
973
|
],
|
|
932
974
|
agents: ["AGENT.md", "agent.md"]
|
|
933
975
|
}[type] || [];
|
|
@@ -940,7 +982,7 @@ var BitbucketFetcher = class {
|
|
|
940
982
|
}
|
|
941
983
|
return entries.find((entry) => {
|
|
942
984
|
const fileName = this.getFileName(entry.path);
|
|
943
|
-
return entry.type === "file" && fileName.endsWith(".md");
|
|
985
|
+
return entry.type === "file" && fileName.endsWith(".md") && !NON_RESOURCE_FILES.some((nr) => fileName.toLowerCase() === nr.toLowerCase());
|
|
944
986
|
}) || null;
|
|
945
987
|
}
|
|
946
988
|
};
|
|
@@ -948,12 +990,22 @@ const bitbucketFetcher = new BitbucketFetcher();
|
|
|
948
990
|
|
|
949
991
|
//#endregion
|
|
950
992
|
//#region src/prompts/InteractivePrompt.ts
|
|
993
|
+
const FILTER_THRESHOLD$1 = 15;
|
|
951
994
|
/**
|
|
952
995
|
* InteractivePrompt - 외부 소스 기반 설치 플로우
|
|
953
996
|
*
|
|
954
997
|
* 플로우: Agent → Source URL → Type 선택 → Resources 선택 → Scope → Confirm
|
|
955
998
|
*/
|
|
956
|
-
var InteractivePrompt = class {
|
|
999
|
+
var InteractivePrompt = class InteractivePrompt {
|
|
1000
|
+
/**
|
|
1001
|
+
* 키워드로 리소스 필터링 (name, type, description, metadata.category 대상, 대소문자 무시)
|
|
1002
|
+
*/
|
|
1003
|
+
static filterByKeyword(resources, keyword) {
|
|
1004
|
+
const lower = keyword.toLowerCase();
|
|
1005
|
+
return resources.filter((r) => {
|
|
1006
|
+
return r.name.toLowerCase().includes(lower) || r.type.toLowerCase().includes(lower) || r.description.toLowerCase().includes(lower) || r.metadata.category && r.metadata.category.toLowerCase().includes(lower);
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
957
1009
|
/**
|
|
958
1010
|
* Interactive 플로우 실행
|
|
959
1011
|
*/
|
|
@@ -1074,14 +1126,35 @@ var InteractivePrompt = class {
|
|
|
1074
1126
|
return firstLine;
|
|
1075
1127
|
}
|
|
1076
1128
|
/**
|
|
1129
|
+
* 리소스가 많을 때 키워드 필터링 프롬프트 표시
|
|
1130
|
+
*/
|
|
1131
|
+
async filterResourcesIfNeeded(resources) {
|
|
1132
|
+
if (resources.length <= FILTER_THRESHOLD$1) return resources;
|
|
1133
|
+
const { keyword } = await inquirer.prompt([{
|
|
1134
|
+
type: "input",
|
|
1135
|
+
name: "keyword",
|
|
1136
|
+
message: `${resources.length} resources found. Enter keyword to filter (press Enter to show all):`
|
|
1137
|
+
}]);
|
|
1138
|
+
const trimmed = keyword.trim();
|
|
1139
|
+
if (!trimmed) return resources;
|
|
1140
|
+
const filtered = InteractivePrompt.filterByKeyword(resources, trimmed);
|
|
1141
|
+
if (filtered.length === 0) {
|
|
1142
|
+
console.log(`No resources matched "${trimmed}". Showing all resources.`);
|
|
1143
|
+
return resources;
|
|
1144
|
+
}
|
|
1145
|
+
console.log(`Filtered to ${filtered.length} resource(s).`);
|
|
1146
|
+
return filtered;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1077
1149
|
* 리소스 선택 (가져온 리소스 목록에서)
|
|
1078
1150
|
*/
|
|
1079
1151
|
async selectResources(availableResources, types) {
|
|
1080
|
-
|
|
1152
|
+
let filteredResources = availableResources.filter((r) => types.includes(r.type));
|
|
1081
1153
|
if (filteredResources.length === 0) {
|
|
1082
1154
|
console.log("\nNo resources found for selected types.");
|
|
1083
1155
|
return [];
|
|
1084
1156
|
}
|
|
1157
|
+
filteredResources = await this.filterResourcesIfNeeded(filteredResources);
|
|
1085
1158
|
const { resources } = await inquirer.prompt([{
|
|
1086
1159
|
type: "checkbox",
|
|
1087
1160
|
name: "resources",
|
|
@@ -1848,12 +1921,13 @@ const commandHandler = new CommandHandler();
|
|
|
1848
1921
|
|
|
1849
1922
|
//#endregion
|
|
1850
1923
|
//#region src/prompts/ZipPrompt.ts
|
|
1924
|
+
const FILTER_THRESHOLD = 15;
|
|
1851
1925
|
/**
|
|
1852
1926
|
* ZipPrompt - ZIP 내보내기용 선택 플로우
|
|
1853
1927
|
*
|
|
1854
1928
|
* 플로우: Types → Resources → Confirm
|
|
1855
1929
|
*/
|
|
1856
|
-
var ZipPrompt = class {
|
|
1930
|
+
var ZipPrompt = class ZipPrompt {
|
|
1857
1931
|
/**
|
|
1858
1932
|
* ZIP 플로우 실행
|
|
1859
1933
|
*/
|
|
@@ -1898,14 +1972,44 @@ var ZipPrompt = class {
|
|
|
1898
1972
|
return selected;
|
|
1899
1973
|
}
|
|
1900
1974
|
/**
|
|
1975
|
+
* 키워드로 리소스 필터링 (name, type, description, metadata.category 대상, 대소문자 무시)
|
|
1976
|
+
*/
|
|
1977
|
+
static filterByKeyword(resources, keyword) {
|
|
1978
|
+
const lower = keyword.toLowerCase();
|
|
1979
|
+
return resources.filter((r) => {
|
|
1980
|
+
return r.name.toLowerCase().includes(lower) || r.type.toLowerCase().includes(lower) || r.description.toLowerCase().includes(lower) || r.metadata.category && r.metadata.category.toLowerCase().includes(lower);
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
/**
|
|
1984
|
+
* 리소스가 많을 때 키워드 필터링 프롬프트 표시
|
|
1985
|
+
*/
|
|
1986
|
+
async filterResourcesIfNeeded(resources) {
|
|
1987
|
+
if (resources.length <= FILTER_THRESHOLD) return resources;
|
|
1988
|
+
const { keyword } = await inquirer.prompt([{
|
|
1989
|
+
type: "input",
|
|
1990
|
+
name: "keyword",
|
|
1991
|
+
message: `${resources.length} resources found. Enter keyword to filter (press Enter to show all):`
|
|
1992
|
+
}]);
|
|
1993
|
+
const trimmed = keyword.trim();
|
|
1994
|
+
if (!trimmed) return resources;
|
|
1995
|
+
const filtered = ZipPrompt.filterByKeyword(resources, trimmed);
|
|
1996
|
+
if (filtered.length === 0) {
|
|
1997
|
+
console.log(`No resources matched "${trimmed}". Showing all resources.`);
|
|
1998
|
+
return resources;
|
|
1999
|
+
}
|
|
2000
|
+
console.log(`Filtered to ${filtered.length} resource(s).`);
|
|
2001
|
+
return filtered;
|
|
2002
|
+
}
|
|
2003
|
+
/**
|
|
1901
2004
|
* 리소스 복수 선택
|
|
1902
2005
|
*/
|
|
1903
2006
|
async selectResources(availableResources, types) {
|
|
1904
|
-
|
|
2007
|
+
let filteredResources = availableResources.filter((r) => types.includes(r.type));
|
|
1905
2008
|
if (filteredResources.length === 0) {
|
|
1906
2009
|
console.log("\nNo resources found for selected types.");
|
|
1907
2010
|
return [];
|
|
1908
2011
|
}
|
|
2012
|
+
filteredResources = await this.filterResourcesIfNeeded(filteredResources);
|
|
1909
2013
|
const { selected } = await inquirer.prompt([{
|
|
1910
2014
|
type: "checkbox",
|
|
1911
2015
|
name: "selected",
|