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.
Files changed (2) hide show
  1. package/dist/index.mjs +122 -18
  2. 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) if (file.path.endsWith(".md")) {
587
- const resource = this.parseSingleFileResource(file, type);
588
- if (resource) resources.push(resource);
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) => file.path.endsWith(".md")) || null;
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) if (file.path.endsWith(".md")) {
831
- const resource = this.parseSingleFileResource(file, type);
832
- if (resource) resources.push(resource);
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
- const filteredResources = availableResources.filter((r) => types.includes(r.type));
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
- const filteredResources = availableResources.filter((r) => types.includes(r.type));
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "add-ai-tools",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Universal AI agent resource installer CLI",
5
5
  "type": "module",
6
6
  "bin": {