itismyskillmarket 1.3.24 → 1.3.26

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
@@ -135,11 +135,30 @@ var SKILL_SCOPES = process.env.SKM_NPM_SCOPES ? process.env.SKM_NPM_SCOPES.split
135
135
  var SKM_URL = process.env.SKM_URL || fileConfig.skmUrl || `https://www.npmjs.com/package/${NPM_SCOPE}`;
136
136
 
137
137
  // src/commands/npm.ts
138
+ var npmCache = /* @__PURE__ */ new Map();
139
+ function getCached(key) {
140
+ const entry = npmCache.get(key);
141
+ if (!entry) return null;
142
+ if (Date.now() > entry.expiry) {
143
+ npmCache.delete(key);
144
+ return null;
145
+ }
146
+ return entry.data;
147
+ }
148
+ function setCache(key, data, ttlMs = 3e4) {
149
+ npmCache.set(key, { data, expiry: Date.now() + ttlMs });
150
+ }
138
151
  async function fetchNpmPackage(packageName, retries = 1) {
152
+ const cacheKey = `pkg:${packageName}`;
153
+ const cached = getCached(cacheKey);
154
+ if (cached) return cached;
139
155
  for (let attempt = 0; attempt <= retries; attempt++) {
140
156
  try {
141
157
  const result = await fetchNpmPackageOnce(packageName);
142
- if (result !== null) return result;
158
+ if (result !== null) {
159
+ setCache(cacheKey, result);
160
+ return result;
161
+ }
143
162
  } catch {
144
163
  if (attempt === retries) return null;
145
164
  }
@@ -452,8 +471,8 @@ Status: Not installed (use skm install ${skillId} to install)`);
452
471
  }
453
472
 
454
473
  // src/commands/install.ts
455
- import fs7 from "fs-extra";
456
- import path6 from "path";
474
+ import fs9 from "fs-extra";
475
+ import path8 from "path";
457
476
  import { exec } from "child_process";
458
477
  import { promisify } from "util";
459
478
 
@@ -599,52 +618,48 @@ var VSCodeAdapter = class extends BaseAdapter {
599
618
  };
600
619
 
601
620
  // src/adapters/openclaw.ts
602
- import { readdirSync, existsSync as existsSync2, cpSync, rmSync } from "fs";
603
- import { join as join2 } from "path";
604
- import { homedir } from "os";
605
- import { ensureDirSync } from "fs-extra";
606
- var OpenClawAdapter = class {
621
+ import path6 from "path";
622
+ import os6 from "os";
623
+ import fs7 from "fs-extra";
624
+ var OpenClawAdapter = class extends BaseAdapter {
607
625
  id = "openclaw";
608
626
  name = "OpenClaw";
609
- skillDir = join2(homedir(), ".openclaw", "skills");
627
+ skillDir = path6.join(os6.homedir(), ".openclaw", "skills");
610
628
  async isAvailable() {
611
629
  try {
612
- return existsSync2(join2(homedir(), ".openclaw"));
630
+ return await fs7.pathExists(path6.join(os6.homedir(), ".openclaw"));
613
631
  } catch {
614
632
  return false;
615
633
  }
616
634
  }
617
635
  async isInstalled(skillId) {
618
636
  try {
619
- const skillPath = join2(this.skillDir, skillId);
620
- return existsSync2(skillPath);
637
+ return await fs7.pathExists(path6.join(this.skillDir, skillId));
621
638
  } catch {
622
639
  return false;
623
640
  }
624
641
  }
625
642
  async install(skillId, sourceDir) {
626
- ensureDirSync(this.skillDir);
627
- const targetDir = join2(this.skillDir, skillId);
628
- if (existsSync2(targetDir)) {
629
- rmSync(targetDir, { recursive: true, force: true });
643
+ await fs7.ensureDir(this.skillDir);
644
+ const targetDir = path6.join(this.skillDir, skillId);
645
+ if (await fs7.pathExists(targetDir)) {
646
+ await fs7.remove(targetDir);
630
647
  }
631
- cpSync(sourceDir, targetDir, { recursive: true });
648
+ await fs7.copy(sourceDir, targetDir, { recursive: true });
632
649
  }
633
650
  async uninstall(skillId) {
634
- const targetDir = join2(this.skillDir, skillId);
635
- if (existsSync2(targetDir)) {
636
- rmSync(targetDir, { recursive: true, force: true });
651
+ const targetDir = path6.join(this.skillDir, skillId);
652
+ if (await fs7.pathExists(targetDir)) {
653
+ await fs7.remove(targetDir);
637
654
  }
638
655
  }
639
656
  async listInstalled() {
640
657
  try {
641
- if (!existsSync2(this.skillDir)) {
658
+ if (!await fs7.pathExists(this.skillDir)) {
642
659
  return [];
643
660
  }
644
- return readdirSync(this.skillDir).filter((name) => {
645
- const fullPath = join2(this.skillDir, name);
646
- return existsSync2(fullPath) && name !== ".";
647
- });
661
+ const entries = await fs7.readdir(this.skillDir, { withFileTypes: true });
662
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
648
663
  } catch {
649
664
  return [];
650
665
  }
@@ -652,55 +667,48 @@ var OpenClawAdapter = class {
652
667
  };
653
668
 
654
669
  // src/adapters/hermes.ts
655
- import { readdirSync as readdirSync2, existsSync as existsSync3, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
656
- import { join as join3 } from "path";
657
- import { homedir as homedir2 } from "os";
658
- import { ensureDirSync as ensureDirSync2 } from "fs-extra";
659
- var HermesAdapter = class {
670
+ import path7 from "path";
671
+ import os7 from "os";
672
+ import fs8 from "fs-extra";
673
+ var HermesAdapter = class extends BaseAdapter {
660
674
  id = "hermes";
661
675
  name = "Hermes Agent";
662
- skillDir = join3(homedir2(), ".hermes", "skills");
676
+ skillDir = path7.join(os7.homedir(), ".hermes", "skills");
663
677
  async isAvailable() {
664
678
  try {
665
- if (existsSync3(join3(homedir2(), ".hermes"))) {
666
- return true;
667
- }
668
- return false;
679
+ return await fs8.pathExists(path7.join(os7.homedir(), ".hermes"));
669
680
  } catch {
670
681
  return false;
671
682
  }
672
683
  }
673
684
  async isInstalled(skillId) {
674
685
  try {
675
- const skillPath = join3(this.skillDir, skillId);
676
- return existsSync3(skillPath);
686
+ return await fs8.pathExists(path7.join(this.skillDir, skillId));
677
687
  } catch {
678
688
  return false;
679
689
  }
680
690
  }
681
691
  async install(skillId, sourceDir) {
682
- ensureDirSync2(this.skillDir);
683
- const targetDir = join3(this.skillDir, skillId);
684
- if (existsSync3(targetDir)) {
685
- rmSync2(targetDir, { recursive: true, force: true });
692
+ await fs8.ensureDir(this.skillDir);
693
+ const targetDir = path7.join(this.skillDir, skillId);
694
+ if (await fs8.pathExists(targetDir)) {
695
+ await fs8.remove(targetDir);
686
696
  }
687
- cpSync2(sourceDir, targetDir, { recursive: true });
697
+ await fs8.copy(sourceDir, targetDir, { recursive: true });
688
698
  }
689
699
  async uninstall(skillId) {
690
- const targetDir = join3(this.skillDir, skillId);
691
- if (existsSync3(targetDir)) {
692
- rmSync2(targetDir, { recursive: true, force: true });
700
+ const targetDir = path7.join(this.skillDir, skillId);
701
+ if (await fs8.pathExists(targetDir)) {
702
+ await fs8.remove(targetDir);
693
703
  }
694
704
  }
695
705
  async listInstalled() {
696
706
  try {
697
- if (!existsSync3(this.skillDir)) {
707
+ if (!await fs8.pathExists(this.skillDir)) {
698
708
  return [];
699
709
  }
700
- return readdirSync2(this.skillDir).filter((name) => {
701
- const fullPath = join3(this.skillDir, name);
702
- return existsSync3(fullPath) && name !== ".";
703
- });
710
+ const entries = await fs8.readdir(this.skillDir, { withFileTypes: true });
711
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
704
712
  } catch {
705
713
  return [];
706
714
  }
@@ -766,52 +774,52 @@ async function installSkill(skillId, version, options) {
766
774
  throw new Error(`No version found for ${packageName}`);
767
775
  }
768
776
  const cacheDir = getCacheDir();
769
- const targetDir = path6.join(cacheDir, `${packageName}@${targetVersion}`);
770
- if (!await fs7.pathExists(targetDir)) {
777
+ const targetDir = path8.join(cacheDir, `${packageName}@${targetVersion}`);
778
+ if (!await fs9.pathExists(targetDir)) {
771
779
  console.log("Downloading package...");
772
- await fs7.ensureDir(cacheDir);
780
+ await fs9.ensureDir(cacheDir);
773
781
  try {
774
782
  await execAsync(`npm pack ${packageName}@${targetVersion} --pack-destination ${cacheDir}`);
775
- const files = await fs7.readdir(cacheDir);
783
+ const files = await fs9.readdir(cacheDir);
776
784
  const tarball = files.find(
777
785
  (f) => f.endsWith(".tgz") && f.includes(packageName.replace(/^@/, "").replace("/", "-"))
778
786
  );
779
787
  if (tarball) {
780
- await execAsync(`tar -xzf "${path6.join(cacheDir, tarball)}" -C "${cacheDir}"`);
781
- await fs7.remove(path6.join(cacheDir, tarball));
782
- const extractedDir = path6.join(cacheDir, "package");
788
+ await execAsync(`tar -xzf "${path8.join(cacheDir, tarball)}" -C "${cacheDir}"`);
789
+ await fs9.remove(path8.join(cacheDir, tarball));
790
+ const extractedDir = path8.join(cacheDir, "package");
783
791
  const finalDir = targetDir;
784
- await fs7.move(extractedDir, finalDir, { overwrite: true });
792
+ await fs9.move(extractedDir, finalDir, { overwrite: true });
785
793
  }
786
794
  } catch (err) {
787
795
  throw new Error(`Failed to download package: ${err}`);
788
796
  }
789
797
  }
790
798
  const skillsDir = getSkillsDir();
791
- const skillVersionDir = path6.join(skillsDir, `${skillId}@${targetVersion}`);
799
+ const skillVersionDir = path8.join(skillsDir, `${skillId}@${targetVersion}`);
792
800
  console.log("Setting up skill...");
793
- await fs7.ensureDir(skillVersionDir);
801
+ await fs9.ensureDir(skillVersionDir);
794
802
  const pkgRoot = targetDir;
795
- if (await fs7.pathExists(path6.join(pkgRoot, "SKILL.md"))) {
796
- await fs7.copy(
797
- path6.join(pkgRoot, "SKILL.md"),
798
- path6.join(skillVersionDir, "SKILL.md")
803
+ if (await fs9.pathExists(path8.join(pkgRoot, "SKILL.md"))) {
804
+ await fs9.copy(
805
+ path8.join(pkgRoot, "SKILL.md"),
806
+ path8.join(skillVersionDir, "SKILL.md")
799
807
  );
800
808
  }
801
- if (await fs7.pathExists(path6.join(pkgRoot, "metadata.json"))) {
802
- await fs7.copy(
803
- path6.join(pkgRoot, "metadata.json"),
804
- path6.join(skillVersionDir, "metadata.json")
809
+ if (await fs9.pathExists(path8.join(pkgRoot, "metadata.json"))) {
810
+ await fs9.copy(
811
+ path8.join(pkgRoot, "metadata.json"),
812
+ path8.join(skillVersionDir, "metadata.json")
805
813
  );
806
814
  }
807
- const skillDir = path6.join(skillsDir, skillId);
808
- await fs7.ensureDir(skillDir);
809
- const latestLink = path6.join(skillDir, LATEST_LINK);
815
+ const skillDir = path8.join(skillsDir, skillId);
816
+ await fs9.ensureDir(skillDir);
817
+ const latestLink = path8.join(skillDir, LATEST_LINK);
810
818
  try {
811
- await fs7.remove(latestLink);
812
- await fs7.symlink(skillVersionDir, latestLink, "junction");
819
+ await fs9.remove(latestLink);
820
+ await fs9.symlink(skillVersionDir, latestLink, "junction");
813
821
  } catch {
814
- await fs7.copy(skillVersionDir, path6.join(skillDir, LATEST_LINK), { overwrite: true });
822
+ await fs9.copy(skillVersionDir, path8.join(skillDir, LATEST_LINK), { overwrite: true });
815
823
  }
816
824
  let targetAdapters = [];
817
825
  if (options?.platforms && options.platforms.length > 0) {
@@ -872,8 +880,8 @@ Installing to ${targetAdapters.length} platform(s)...
872
880
  }
873
881
 
874
882
  // src/commands/sync.ts
875
- import fs8 from "fs-extra";
876
- import path7 from "path";
883
+ import fs10 from "fs-extra";
884
+ import path9 from "path";
877
885
  async function syncPlatformLinks() {
878
886
  await ensureMarketDirs();
879
887
  const skillsDir = getSkillsDir();
@@ -881,20 +889,20 @@ async function syncPlatformLinks() {
881
889
  const registry = await loadRegistry();
882
890
  console.log("Syncing platform links...\n");
883
891
  for (const platform of PLATFORMS) {
884
- const platformDir = path7.join(platformLinksDir, platform, "skills");
885
- await fs8.ensureDir(platformDir);
892
+ const platformDir = path9.join(platformLinksDir, platform, "skills");
893
+ await fs10.ensureDir(platformDir);
886
894
  for (const [skillId, skillInfo] of Object.entries(registry.skills)) {
887
- const skillLatestLink = path7.join(skillsDir, skillId, LATEST_LINK);
888
- const targetPlatformDir = path7.join(skillLatestLink, platform);
889
- const platformSkillDir = path7.join(platformDir, skillId);
890
- if (await fs8.pathExists(skillLatestLink)) {
891
- if (await fs8.pathExists(targetPlatformDir)) {
895
+ const skillLatestLink = path9.join(skillsDir, skillId, LATEST_LINK);
896
+ const targetPlatformDir = path9.join(skillLatestLink, platform);
897
+ const platformSkillDir = path9.join(platformDir, skillId);
898
+ if (await fs10.pathExists(skillLatestLink)) {
899
+ if (await fs10.pathExists(targetPlatformDir)) {
892
900
  try {
893
- await fs8.remove(platformSkillDir);
894
- await fs8.symlink(targetPlatformDir, platformSkillDir, "junction");
901
+ await fs10.remove(platformSkillDir);
902
+ await fs10.symlink(targetPlatformDir, platformSkillDir, "junction");
895
903
  console.log(` Linked: ${platform}/${skillId}`);
896
904
  } catch {
897
- await fs8.copy(targetPlatformDir, platformSkillDir, { overwrite: true });
905
+ await fs10.copy(targetPlatformDir, platformSkillDir, { overwrite: true });
898
906
  console.log(` Copied: ${platform}/${skillId}`);
899
907
  }
900
908
  }
@@ -923,11 +931,17 @@ async function syncSkill(skillId) {
923
931
  // src/commands/update.ts
924
932
  async function updateSkill(skillId) {
925
933
  if (skillId) {
926
- const pkgInfo = await fetchNpmPackage(`${NPM_SCOPE}/${skillId}`);
934
+ const pkgInfo = await fetchSkillPackage(skillId);
927
935
  if (pkgInfo) {
928
936
  const latestVersion = pkgInfo["dist-tags"]?.latest;
937
+ if (!latestVersion) {
938
+ console.log(`No latest version found for ${skillId}.`);
939
+ return;
940
+ }
929
941
  console.log(`Updating ${skillId} to ${latestVersion}...`);
930
942
  await installSkill(skillId, latestVersion);
943
+ } else {
944
+ console.log(`Skill "${skillId}" not found in any configured scope.`);
931
945
  }
932
946
  return;
933
947
  }
@@ -940,7 +954,7 @@ async function updateSkill(skillId) {
940
954
  `);
941
955
  let hasUpdates = false;
942
956
  for (const skill of installed) {
943
- const pkgInfo = await fetchNpmPackage(`${NPM_SCOPE_FALLBACK}/${skill.id}`);
957
+ const pkgInfo = await fetchSkillPackage(skill.id);
944
958
  if (pkgInfo) {
945
959
  const latestVersion = pkgInfo["dist-tags"]?.latest;
946
960
  if (latestVersion && latestVersion !== skill.version) {
@@ -954,6 +968,8 @@ async function updateSkill(skillId) {
954
968
  } else {
955
969
  console.log(` ${skill.id}: ${skill.version} (up to date)`);
956
970
  }
971
+ } else {
972
+ console.log(` ${skill.id}: ${skill.version} (failed to fetch remote)`);
957
973
  }
958
974
  }
959
975
  if (!hasUpdates) {
@@ -962,8 +978,8 @@ async function updateSkill(skillId) {
962
978
  }
963
979
 
964
980
  // src/commands/uninstall.ts
965
- import fs9 from "fs-extra";
966
- import path8 from "path";
981
+ import fs11 from "fs-extra";
982
+ import path10 from "path";
967
983
  import readline from "readline";
968
984
  async function askConfirmation(message) {
969
985
  const rl = readline.createInterface({
@@ -988,12 +1004,12 @@ async function getUninstallPreview(skillId, options) {
988
1004
  platformNames = adapters2.map((a) => a.name);
989
1005
  }
990
1006
  const skillsDir = getSkillsDir();
991
- const localPath = path8.join(skillsDir, skillId);
1007
+ const localPath = path10.join(skillsDir, skillId);
992
1008
  const platformLinksDir = getPlatformLinksDir();
993
1009
  const platformLinks = [];
994
1010
  for (const platform of PLATFORMS) {
995
- const linkPath = path8.join(platformLinksDir, platform, "skills", skillId);
996
- if (await fs9.pathExists(linkPath)) {
1011
+ const linkPath = path10.join(platformLinksDir, platform, "skills", skillId);
1012
+ if (await fs11.pathExists(linkPath)) {
997
1013
  platformLinks.push(linkPath);
998
1014
  }
999
1015
  }
@@ -1072,17 +1088,17 @@ Uninstalling from ${validAdapters.length} platform(s)...
1072
1088
  }
1073
1089
  }
1074
1090
  const skillsDir = getSkillsDir();
1075
- const skillDir = path8.join(skillsDir, skillId);
1076
- if (await fs9.pathExists(skillDir)) {
1077
- await fs9.remove(skillDir);
1091
+ const skillDir = path10.join(skillsDir, skillId);
1092
+ if (await fs11.pathExists(skillDir)) {
1093
+ await fs11.remove(skillDir);
1078
1094
  console.log(`\u2705 Removed local files: ${skillDir}`);
1079
1095
  }
1080
1096
  const platformLinksDir = getPlatformLinksDir();
1081
1097
  let removedLinks = 0;
1082
1098
  for (const platform of PLATFORMS) {
1083
- const linkPath = path8.join(platformLinksDir, platform, "skills", skillId);
1084
- if (await fs9.pathExists(linkPath)) {
1085
- await fs9.remove(linkPath);
1099
+ const linkPath = path10.join(platformLinksDir, platform, "skills", skillId);
1100
+ if (await fs11.pathExists(linkPath)) {
1101
+ await fs11.remove(linkPath);
1086
1102
  removedLinks++;
1087
1103
  }
1088
1104
  }
@@ -1152,8 +1168,8 @@ Uninstalling all skills...
1152
1168
  }
1153
1169
 
1154
1170
  // src/commands/github-install.ts
1155
- import fs10 from "fs-extra";
1156
- import path9 from "path";
1171
+ import fs12 from "fs-extra";
1172
+ import path11 from "path";
1157
1173
  var GITHUB_URL_PATTERNS = [
1158
1174
  /^https?:\/\/github\.com\/([^/]+)\/([^/]+)(?:\/tree\/([^/]+)(?:\/(.+))?)?$/,
1159
1175
  /^([^/]+)\/([^/]+)(?:#(.+))?$/,
@@ -1170,13 +1186,13 @@ function parseGitHubUrl(input) {
1170
1186
  const repo = match[2].replace(/\.git$/, "");
1171
1187
  const branch = match[3] || "main";
1172
1188
  const commitOrPath = match[4] || match[3];
1173
- const path12 = match[5] || void 0;
1189
+ const path14 = match[5] || void 0;
1174
1190
  return {
1175
1191
  owner,
1176
1192
  repo,
1177
1193
  branch: commitOrPath && !commitOrPath.includes("/") ? commitOrPath : branch,
1178
1194
  commit: commitOrPath?.match(/^[0-9a-f]{40}$/) ? commitOrPath : void 0,
1179
- path: path12
1195
+ path: path14
1180
1196
  };
1181
1197
  }
1182
1198
  }
@@ -1275,17 +1291,17 @@ async function detectSkillFromGitHub(source) {
1275
1291
  }
1276
1292
  async function generatePlatformAdapters(skillId, existingPlatforms, targetPlatforms, sourceDir) {
1277
1293
  const skillsDir = getSkillsDir();
1278
- const skillVersionDir = path9.join(skillsDir, `${skillId}@github`);
1294
+ const skillVersionDir = path11.join(skillsDir, `${skillId}@github`);
1279
1295
  for (const platform of targetPlatforms) {
1280
1296
  if (existingPlatforms.includes(platform)) {
1281
1297
  continue;
1282
1298
  }
1283
- const platformDir = path9.join(skillVersionDir, platform);
1284
- await fs10.ensureDir(platformDir);
1285
- const sourceSkillMd = path9.join(sourceDir, "SKILL.md");
1286
- const targetSkillMd = path9.join(platformDir, "SKILL.md");
1287
- if (await fs10.pathExists(sourceSkillMd)) {
1288
- await fs10.copy(sourceSkillMd, targetSkillMd);
1299
+ const platformDir = path11.join(skillVersionDir, platform);
1300
+ await fs12.ensureDir(platformDir);
1301
+ const sourceSkillMd = path11.join(sourceDir, "SKILL.md");
1302
+ const targetSkillMd = path11.join(platformDir, "SKILL.md");
1303
+ if (await fs12.pathExists(sourceSkillMd)) {
1304
+ await fs12.copy(sourceSkillMd, targetSkillMd);
1289
1305
  }
1290
1306
  if (platform === "opencode" || platform === "cursor" || platform === "codex" || platform === "antigravity") {
1291
1307
  } else if (platform === "vscode") {
@@ -1294,14 +1310,14 @@ async function generatePlatformAdapters(skillId, existingPlatforms, targetPlatfo
1294
1310
  description: `Skill: ${skillId}`,
1295
1311
  version: "1.0.0"
1296
1312
  };
1297
- await fs10.writeJson(path9.join(platformDir, "skill.json"), skillJson, { spaces: 2 });
1313
+ await fs12.writeJson(path11.join(platformDir, "skill.json"), skillJson, { spaces: 2 });
1298
1314
  } else if (platform === "claude") {
1299
1315
  const skillJson = {
1300
1316
  name: skillId,
1301
1317
  description: `Skill: ${skillId}`,
1302
1318
  version: "1.0.0"
1303
1319
  };
1304
- await fs10.writeJson(path9.join(platformDir, "skill.json"), skillJson, { spaces: 2 });
1320
+ await fs12.writeJson(path11.join(platformDir, "skill.json"), skillJson, { spaces: 2 });
1305
1321
  }
1306
1322
  }
1307
1323
  }
@@ -1330,26 +1346,26 @@ async function installFromGitHub(input, options) {
1330
1346
  Setting up skill: ${skillId}@${version}`);
1331
1347
  await ensureMarketDirs();
1332
1348
  const skillsDir = getSkillsDir();
1333
- const skillVersionDir = path9.join(skillsDir, `${skillId}@${version}`);
1334
- await fs10.ensureDir(skillVersionDir);
1349
+ const skillVersionDir = path11.join(skillsDir, `${skillId}@${version}`);
1350
+ await fs12.ensureDir(skillVersionDir);
1335
1351
  console.log("Downloading files...");
1336
1352
  const basePath = source.path || "";
1337
1353
  const skillMdPath = basePath ? `${basePath}/SKILL.md` : "SKILL.md";
1338
1354
  const skillMdContent = await fetchGitHubFile(source, skillMdPath);
1339
1355
  if (skillMdContent) {
1340
- await fs10.writeFile(path9.join(skillVersionDir, "SKILL.md"), skillMdContent);
1356
+ await fs12.writeFile(path11.join(skillVersionDir, "SKILL.md"), skillMdContent);
1341
1357
  console.log(" \u2705 SKILL.md");
1342
1358
  }
1343
1359
  const packageJsonPath = basePath ? `${basePath}/package.json` : "package.json";
1344
1360
  const packageJsonContent = await fetchGitHubFile(source, packageJsonPath);
1345
1361
  if (packageJsonContent) {
1346
- await fs10.writeFile(path9.join(skillVersionDir, "package.json"), packageJsonContent);
1362
+ await fs12.writeFile(path11.join(skillVersionDir, "package.json"), packageJsonContent);
1347
1363
  console.log(" \u2705 package.json");
1348
1364
  }
1349
1365
  const metadataPath = basePath ? `${basePath}/metadata.json` : "metadata.json";
1350
1366
  const metadataContent = await fetchGitHubFile(source, metadataPath);
1351
1367
  if (metadataContent) {
1352
- await fs10.writeFile(path9.join(skillVersionDir, "metadata.json"), metadataContent);
1368
+ await fs12.writeFile(path11.join(skillVersionDir, "metadata.json"), metadataContent);
1353
1369
  console.log(" \u2705 metadata.json");
1354
1370
  }
1355
1371
  const targetPlatforms = options?.platforms || detected.platforms;
@@ -1361,14 +1377,14 @@ Generating adapters for missing platforms: ${missingPlatforms.join(", ")}`);
1361
1377
  await generatePlatformAdapters(skillId, existingPlatforms, targetPlatforms, skillVersionDir);
1362
1378
  console.log(" \u2705 Platform adapters generated");
1363
1379
  }
1364
- const skillDir = path9.join(skillsDir, skillId);
1365
- await fs10.ensureDir(skillDir);
1366
- const latestLink = path9.join(skillDir, "latest");
1380
+ const skillDir = path11.join(skillsDir, skillId);
1381
+ await fs12.ensureDir(skillDir);
1382
+ const latestLink = path11.join(skillDir, "latest");
1367
1383
  try {
1368
- await fs10.remove(latestLink);
1369
- await fs10.symlink(skillVersionDir, latestLink, "junction");
1384
+ await fs12.remove(latestLink);
1385
+ await fs12.symlink(skillVersionDir, latestLink, "junction");
1370
1386
  } catch {
1371
- await fs10.copy(skillVersionDir, path9.join(skillDir, "latest"), { overwrite: true });
1387
+ await fs12.copy(skillVersionDir, path11.join(skillDir, "latest"), { overwrite: true });
1372
1388
  }
1373
1389
  console.log(`
1374
1390
  Installing to ${targetPlatforms.length} platform(s)...
@@ -1401,9 +1417,6 @@ Installing to ${targetPlatforms.length} platform(s)...
1401
1417
  const failed = results.filter((r) => r.status === "failed").length;
1402
1418
  console.log(`
1403
1419
  \u{1F4CA} Summary: ${installed} installed, ${skipped} skipped, ${failed} failed`);
1404
- console.log(`
1405
- Installing to platforms: ${targetPlatforms.join(", ")}`);
1406
- console.log(" (Platform installation logic needs to be completed)");
1407
1420
  const registry = await loadRegistry();
1408
1421
  registry.skills[skillId] = {
1409
1422
  id: skillId,
@@ -1419,15 +1432,15 @@ Installing to platforms: ${targetPlatforms.join(", ")}`);
1419
1432
 
1420
1433
  // src/commands/publish.ts
1421
1434
  import { execSync } from "child_process";
1422
- import { existsSync as existsSync4 } from "fs";
1423
- import { join as join4 } from "path";
1435
+ import { existsSync as existsSync2 } from "fs";
1436
+ import { join as join2 } from "path";
1424
1437
  import { fileURLToPath } from "url";
1425
1438
  async function publishSkill(skillName, options) {
1426
1439
  const __dirname4 = fileURLToPath(new URL(".", import.meta.url));
1427
- const projectRoot = join4(__dirname4, "..", "..");
1428
- const skillDir = join4(projectRoot, "skills", skillName);
1440
+ const projectRoot = join2(__dirname4, "..", "..");
1441
+ const skillDir = join2(projectRoot, "skills", skillName);
1429
1442
  console.log(`Publishing ${skillName}...`);
1430
- if (!existsSync4(skillDir)) {
1443
+ if (!existsSync2(skillDir)) {
1431
1444
  throw new Error(`Skill '${skillName}' not found in skills/ directory`);
1432
1445
  }
1433
1446
  if (!options?.skipInstall) {
@@ -1467,29 +1480,29 @@ async function publishSkill(skillName, options) {
1467
1480
  }
1468
1481
 
1469
1482
  // src/commands/verify.ts
1470
- import fs11 from "fs-extra";
1471
- import path10 from "path";
1483
+ import fs13 from "fs-extra";
1484
+ import path12 from "path";
1472
1485
  import { fileURLToPath as fileURLToPath2 } from "url";
1473
1486
  var __filename = fileURLToPath2(import.meta.url);
1474
- var __dirname = path10.dirname(__filename);
1487
+ var __dirname = path12.dirname(__filename);
1475
1488
  async function verifySkill(skillName) {
1476
1489
  try {
1477
1490
  console.log(`
1478
1491
  \u{1F50D} Verifying skill: ${skillName}
1479
1492
  `);
1480
- const skillDir = path10.join(process.env.HOME || process.env.USERPROFILE || "", ".skillmarket", "skills", skillName);
1481
- if (!await fs11.pathExists(skillDir)) {
1493
+ const skillDir = path12.join(process.env.HOME || process.env.USERPROFILE || "", ".skillmarket", "skills", skillName);
1494
+ if (!await fs13.pathExists(skillDir)) {
1482
1495
  console.error(`\u274C Skill "${skillName}" not found locally.`);
1483
1496
  console.log(` Try: skm install ${skillName}`);
1484
1497
  process.exit(1);
1485
1498
  }
1486
1499
  let passed = 0;
1487
1500
  let failed = 0;
1488
- const skillMdPath = path10.join(skillDir, "SKILL.md");
1489
- if (await fs11.pathExists(skillMdPath)) {
1501
+ const skillMdPath = path12.join(skillDir, "SKILL.md");
1502
+ if (await fs13.pathExists(skillMdPath)) {
1490
1503
  console.log(`\u2705 SKILL.md exists`);
1491
1504
  passed++;
1492
- const content = await fs11.readFile(skillMdPath, "utf-8");
1505
+ const content = await fs13.readFile(skillMdPath, "utf-8");
1493
1506
  if (content.trim().length > 0) {
1494
1507
  console.log(`\u2705 SKILL.md is not empty (${content.length} chars)`);
1495
1508
  passed++;
@@ -1501,12 +1514,12 @@ async function verifySkill(skillName) {
1501
1514
  console.log(`\u274C SKILL.md not found`);
1502
1515
  failed++;
1503
1516
  }
1504
- const pkgPath = path10.join(skillDir, "package.json");
1505
- if (await fs11.pathExists(pkgPath)) {
1517
+ const pkgPath = path12.join(skillDir, "package.json");
1518
+ if (await fs13.pathExists(pkgPath)) {
1506
1519
  console.log(`\u2705 package.json exists`);
1507
1520
  passed++;
1508
1521
  try {
1509
- const pkg = await fs11.readJson(pkgPath);
1522
+ const pkg = await fs13.readJson(pkgPath);
1510
1523
  const requiredFields = ["name", "version", "description"];
1511
1524
  for (const field of requiredFields) {
1512
1525
  if (pkg[field]) {
@@ -1524,12 +1537,13 @@ async function verifySkill(skillName) {
1524
1537
  } else {
1525
1538
  console.log(`\u26A0\uFE0F package.json not found (optional for basic skills)`);
1526
1539
  }
1527
- const registryPath = path10.join(process.env.HOME || process.env.USERPROFILE || "", ".skillmarket", "registry.json");
1528
- if (await fs11.pathExists(registryPath)) {
1540
+ const registryPath = path12.join(process.env.HOME || process.env.USERPROFILE || "", ".skillmarket", "registry.json");
1541
+ if (await fs13.pathExists(registryPath)) {
1529
1542
  try {
1530
- const registry = await fs11.readJson(registryPath);
1531
- if (registry[skillName]) {
1532
- console.log(`\u2705 Skill registered in registry (v${registry[skillName].version})`);
1543
+ const registry = await fs13.readJson(registryPath);
1544
+ const registered = registry.skills?.[skillName];
1545
+ if (registered) {
1546
+ console.log(`\u2705 Skill registered in registry (v${registered.version})`);
1533
1547
  passed++;
1534
1548
  } else {
1535
1549
  console.log(`\u26A0\uFE0F Skill not found in registry`);
@@ -1561,8 +1575,8 @@ async function verifySkill(skillName) {
1561
1575
 
1562
1576
  // src/commands/ui.ts
1563
1577
  import { createServer } from "http";
1564
- import { readFileSync as readFileSync2, existsSync as existsSync5 } from "fs";
1565
- import { join as join5, extname, dirname } from "path";
1578
+ import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
1579
+ import { join as join3, extname, dirname } from "path";
1566
1580
  import { fileURLToPath as fileURLToPath3 } from "url";
1567
1581
 
1568
1582
  // src/commands/admin.ts
@@ -2029,9 +2043,9 @@ async function adminAccess(skillId, level) {
2029
2043
  // src/commands/ui.ts
2030
2044
  var __filename2 = fileURLToPath3(import.meta.url);
2031
2045
  var __dirname2 = dirname(__filename2);
2032
- var guiDir = join5(__dirname2, "..", "gui");
2046
+ var guiDir = join3(__dirname2, "..", "gui");
2033
2047
  var cache = /* @__PURE__ */ new Map();
2034
- function getCached(key) {
2048
+ function getCached2(key) {
2035
2049
  const entry = cache.get(key);
2036
2050
  if (!entry) return null;
2037
2051
  if (Date.now() > entry.expiry) {
@@ -2040,7 +2054,7 @@ function getCached(key) {
2040
2054
  }
2041
2055
  return entry.data;
2042
2056
  }
2043
- function setCache(key, data, ttlMs = 6e4) {
2057
+ function setCache2(key, data, ttlMs = 6e4) {
2044
2058
  cache.set(key, { data, expiry: Date.now() + ttlMs });
2045
2059
  }
2046
2060
  async function throttledMap(items, fn, concurrency = 3) {
@@ -2100,7 +2114,7 @@ API_ROUTES.GET["/api/skills"] = async (_req, res, url) => {
2100
2114
  const sort = url.searchParams.get("sort") || "name";
2101
2115
  const platform = url.searchParams.get("platform") || "";
2102
2116
  const cacheKey = `search:${search}:limit:${limit}`;
2103
- let searchResult = getCached(cacheKey);
2117
+ let searchResult = getCached2(cacheKey);
2104
2118
  if (!searchResult) {
2105
2119
  searchResult = await searchSkillmarketPackages({
2106
2120
  from: 0,
@@ -2108,17 +2122,17 @@ API_ROUTES.GET["/api/skills"] = async (_req, res, url) => {
2108
2122
  // 一次拉取更多,避免分页
2109
2123
  keyword: search || void 0
2110
2124
  });
2111
- setCache(cacheKey, searchResult, 3e4);
2125
+ setCache2(cacheKey, searchResult, 3e4);
2112
2126
  }
2113
2127
  const { packages, total } = searchResult;
2114
2128
  let fetchErrors = 0;
2115
2129
  const skillDetails = await throttledMap(packages, async (pkgName) => {
2116
2130
  try {
2117
2131
  const pkgCacheKey = `pkg:${pkgName}`;
2118
- let info = getCached(pkgCacheKey);
2132
+ let info = getCached2(pkgCacheKey);
2119
2133
  if (!info) {
2120
2134
  info = await fetchNpmPackage(pkgName);
2121
- if (info) setCache(pkgCacheKey, info, 3e4);
2135
+ if (info) setCache2(pkgCacheKey, info, 3e4);
2122
2136
  }
2123
2137
  if (!info) {
2124
2138
  fetchErrors++;
@@ -2252,10 +2266,10 @@ API_ROUTES.GET["/api/skill-info"] = async (_req, res, url) => {
2252
2266
  return;
2253
2267
  }
2254
2268
  const cacheKey = `skill-info:${skillName}`;
2255
- let info = getCached(cacheKey);
2269
+ let info = getCached2(cacheKey);
2256
2270
  if (!info) {
2257
2271
  info = await fetchSkillPackage(skillName);
2258
- if (info) setCache(cacheKey, info, 3e4);
2272
+ if (info) setCache2(cacheKey, info, 3e4);
2259
2273
  }
2260
2274
  if (!info) {
2261
2275
  jsonResponse(res, 404, { error: `Skill "${skillName}" not found` });
@@ -2286,7 +2300,7 @@ API_ROUTES.GET["/api/skill-info"] = async (_req, res, url) => {
2286
2300
  };
2287
2301
  API_ROUTES.GET["/api/version"] = async (_req, res, _url) => {
2288
2302
  try {
2289
- const pkgPath = join5(__dirname2, "..", "package.json");
2303
+ const pkgPath = join3(__dirname2, "..", "package.json");
2290
2304
  const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
2291
2305
  jsonResponse(res, 200, { version: pkg.version || "1.0.0" });
2292
2306
  } catch {
@@ -2524,7 +2538,7 @@ API_ROUTES.POST["/api/update"] = async (req, res, _url) => {
2524
2538
  }
2525
2539
  };
2526
2540
  function serveStaticFile(res, filePath) {
2527
- if (!existsSync5(filePath)) {
2541
+ if (!existsSync3(filePath)) {
2528
2542
  res.writeHead(404, { "Content-Type": "text/plain" });
2529
2543
  res.end("Not Found");
2530
2544
  return;
@@ -2557,7 +2571,7 @@ async function handleRequest(req, res) {
2557
2571
  jsonResponse(res, 404, { error: `Unknown API endpoint: ${method} ${pathname}` });
2558
2572
  return;
2559
2573
  }
2560
- const filePath = join5(guiDir, pathname === "/" ? "index.html" : pathname);
2574
+ const filePath = join3(guiDir, pathname === "/" ? "index.html" : pathname);
2561
2575
  if (filePath.startsWith(guiDir)) {
2562
2576
  serveStaticFile(res, filePath);
2563
2577
  } else {
@@ -2586,9 +2600,9 @@ Press Ctrl+C to stop
2586
2600
  }
2587
2601
 
2588
2602
  // src/commands/config.ts
2589
- import path11 from "path";
2590
- import fs12 from "fs-extra";
2591
- import os6 from "os";
2603
+ import path13 from "path";
2604
+ import fs14 from "fs-extra";
2605
+ import os8 from "os";
2592
2606
  var CONFIG_DEFINITIONS = [
2593
2607
  {
2594
2608
  key: "npmScope",
@@ -2622,13 +2636,13 @@ var CONFIG_DEFINITIONS = [
2622
2636
  }
2623
2637
  ];
2624
2638
  function getConfigPath() {
2625
- return path11.join(os6.homedir(), ".skillmarket", "config.json");
2639
+ return path13.join(os8.homedir(), ".skillmarket", "config.json");
2626
2640
  }
2627
2641
  async function readConfigFile() {
2628
2642
  try {
2629
2643
  const configPath = getConfigPath();
2630
- if (await fs12.pathExists(configPath)) {
2631
- const data = await fs12.readJson(configPath);
2644
+ if (await fs14.pathExists(configPath)) {
2645
+ const data = await fs14.readJson(configPath);
2632
2646
  const valid = {};
2633
2647
  for (const def of CONFIG_DEFINITIONS) {
2634
2648
  if (data[def.key] !== void 0) {
@@ -2643,11 +2657,11 @@ async function readConfigFile() {
2643
2657
  }
2644
2658
  async function writeConfigFile(updates) {
2645
2659
  const configPath = getConfigPath();
2646
- await fs12.ensureDir(path11.dirname(configPath));
2660
+ await fs14.ensureDir(path13.dirname(configPath));
2647
2661
  let existing = {};
2648
2662
  try {
2649
- if (await fs12.pathExists(configPath)) {
2650
- existing = await fs12.readJson(configPath);
2663
+ if (await fs14.pathExists(configPath)) {
2664
+ existing = await fs14.readJson(configPath);
2651
2665
  }
2652
2666
  } catch {
2653
2667
  }
@@ -2657,25 +2671,25 @@ async function writeConfigFile(updates) {
2657
2671
  delete merged[key];
2658
2672
  }
2659
2673
  }
2660
- await fs12.writeJson(configPath, merged, { spaces: 2 });
2674
+ await fs14.writeJson(configPath, merged, { spaces: 2 });
2661
2675
  return merged;
2662
2676
  }
2663
2677
  async function removeConfigKeys(keys) {
2664
2678
  const configPath = getConfigPath();
2665
- if (!await fs12.pathExists(configPath)) return;
2679
+ if (!await fs14.pathExists(configPath)) return;
2666
2680
  try {
2667
- const existing = await fs12.readJson(configPath);
2681
+ const existing = await fs14.readJson(configPath);
2668
2682
  for (const key of keys) {
2669
2683
  delete existing[key];
2670
2684
  }
2671
- await fs12.writeJson(configPath, existing, { spaces: 2 });
2685
+ await fs14.writeJson(configPath, existing, { spaces: 2 });
2672
2686
  } catch {
2673
2687
  }
2674
2688
  }
2675
2689
  async function removeConfigFile() {
2676
2690
  const configPath = getConfigPath();
2677
- if (await fs12.pathExists(configPath)) {
2678
- await fs12.remove(configPath);
2691
+ if (await fs14.pathExists(configPath)) {
2692
+ await fs14.remove(configPath);
2679
2693
  }
2680
2694
  }
2681
2695
  async function getAllConfig() {
@@ -2816,54 +2830,81 @@ SkillMarket CLI
2816
2830
  Usage: skm <command> [options]
2817
2831
 
2818
2832
  Commands:
2819
- ls [options] List available skills
2820
- --installed Show only installed skills
2821
- --updates Check for updates
2822
- --page <n> Page number (default: 1)
2823
- --limit <n> Items per page (default: 20)
2824
- -s, --search Search by keyword
2825
- info <skill-id> Display skill information
2826
- install <skill> Install a skill
2827
- @version Install specific version
2828
- --platform Target platforms (opencode,claude,vscode)
2829
- --force Overwrite if already installed
2830
- uninstall <skill> Remove an installed skill
2831
- --platform Target platforms
2832
- --all Uninstall ALL installed skills
2833
- --dry-run Preview without deleting
2834
- -y, --yes Skip confirmation
2835
- update [options] Update skills
2836
- --all Update all skills
2837
- sync Synchronize platform links
2838
- platforms Show available platforms
2839
- config View all configuration
2840
- config get <key> Get a config value
2841
- config set <key> Set a config value
2842
- config reset [key] Reset config to defaults
2833
+ ls [options] List available skills
2834
+ --installed Show only installed skills
2835
+ --updates Check for updates
2836
+ --page <n> Page number (default: 1)
2837
+ --limit <n> Items per page (default: 20)
2838
+ -s, --search Search by keyword
2839
+ search <keyword> Search skills from npm registry
2840
+ info <skill> Display skill information
2841
+ install <skill> Install a skill from npm or GitHub
2842
+ @<version> Install specific version
2843
+ --platform Target platforms (opencode,claude,...)
2844
+ --force Overwrite if already installed
2845
+ -b, --branch GitHub branch to install from
2846
+ -c, --commit GitHub commit to install from
2847
+ uninstall <skill> Remove an installed skill
2848
+ --platform Target platforms
2849
+ --all Uninstall ALL installed skills
2850
+ --dry-run Preview without deleting
2851
+ -y, --yes Skip confirmation
2852
+ update [skill] Update installed skills (all if no skill specified)
2853
+ --all Update all skills
2854
+ publish <skill> Publish a skill to npm
2855
+ -v, --version Specify version
2856
+ verify <skill> Verify skill integrity and format
2857
+ sync [skill] Synchronize platform links (or sync a skill to latest)
2858
+ platforms Show available platforms
2859
+ gui [port] Start SkillMarket GUI web interface
2860
+ config View and manage configuration
2861
+ config get <key> Get a config value
2862
+ config set <key> <val> Set a config value
2863
+ config reset [key] Reset config to defaults
2864
+ admin Admin: manage published skills
2865
+ admin ls List all published skills
2866
+ admin info <skill> Show detailed info for a published skill
2867
+ admin search <keyword> Search published skills
2868
+ admin stats Publishing statistics
2869
+ admin verify <skill> Verify a published skill
2870
+ admin deprecate <skill> Deprecate a skill (--version, --message)
2871
+ admin unpublish <skill> Unpublish a skill (--version, --force)
2872
+ admin tag set/tag rm/tag ls Manage dist-tags
2873
+ admin owner add/rm Manage package maintainers
2874
+ admin access <skill> Set package access (public|restricted)
2843
2875
 
2844
2876
  Examples:
2845
- skm ls List all available skills (page 1)
2846
- skm ls --page 2 Go to page 2
2847
- skm ls --limit 10 Show 10 items per page
2848
- skm ls --search brain Search skills by keyword
2849
- skm ls -s brain Search with short form
2850
- skm ls --installed Show installed skills only
2851
- skm ls --installed --search test Search installed skills
2852
- skm ls --installed --page 2
2853
- skm info brainstorming View skill details
2854
- skm install brainstorming Install to all platforms
2855
- skm install brainstorming --platform opencode Install to OpenCode only
2856
- skm install brainstorming --platform claude,vscode Install to multiple
2857
- skm uninstall brainstorming
2858
- skm uninstall --all Uninstall all skills (with confirmation)
2859
- skm uninstall --all --yes Force uninstall all without confirmation
2860
- skm uninstall brainstorming --dry-run Preview uninstall
2861
- skm platforms Show available platforms
2862
- skm config View all configuration
2863
- skm config get npmRegistry View specific config
2877
+ skm ls List available skills
2878
+ skm ls --page 2 --limit 10 Paginated listing
2879
+ skm ls --installed Show installed skills
2880
+ skm ls -s brain Search skills by keyword
2881
+ skm search test Search from registry
2882
+ skm info brainstorming View skill details
2883
+ skm install brainstorming Install to all detected platforms
2884
+ skm install brainstorming@1.0.0 Install specific version
2885
+ skm install owner/repo Install from GitHub
2886
+ skm install brainstorming --platform opencode Install to specific platform
2887
+ skm uninstall brainstorming Uninstall skill
2888
+ skm uninstall --all Uninstall all skills
2889
+ skm uninstall --all --yes Force uninstall all without confirmation
2890
+ skm uninstall --dry-run Preview uninstall
2891
+ skm update brainstorming Update specific skill
2892
+ skm update Update all skills
2893
+ skm publish my-skill Publish a skill
2894
+ skm publish my-skill --version 1.0.1 Publish specific version
2895
+ skm verify my-skill Verify skill integrity
2896
+ skm sync Sync platform links
2897
+ skm sync brainstorming Sync skill to latest version
2898
+ skm platforms Show available platforms
2899
+ skm gui Start GUI on default port 18770
2900
+ skm gui 18790 Start GUI on custom port
2901
+ skm config View all configuration
2902
+ skm config get npmRegistry View specific config
2864
2903
  skm config set npmRegistry https://registry.npmmirror.com Set mirror registry
2865
- skm config reset npmScope Reset config to default
2866
- skm config reset --all Reset all config
2904
+ skm config reset npmScope Reset config to default
2905
+ skm config reset --all Reset all config
2906
+ skm admin ls List all published skills
2907
+ skm admin deprecate my-skill --message "Use v2" Deprecate a skill
2867
2908
  `);
2868
2909
  process.exit(0);
2869
2910
  }
package/gui/app.js CHANGED
@@ -92,6 +92,11 @@ const translations = {
92
92
  'status.unavailable': '❌ Not detected',
93
93
  'status.skillsInstalled': '{count} skills installed',
94
94
 
95
+ // Platform 详情
96
+ 'platform.id': 'Platform ID',
97
+ 'platform.installedSkills': 'Installed Skills ({count})',
98
+ 'platform.noSkills': 'No skills installed on this platform.',
99
+
95
100
  // 详情视图
96
101
  'detail.description': 'Description',
97
102
  'detail.details': 'Details',
@@ -248,10 +253,21 @@ const translations = {
248
253
  // 搜索
249
254
  'search.placeholder': '🔍 搜索技能...',
250
255
 
256
+ // 排序
257
+ 'sort.nameAsc': '名称 A-Z',
258
+ 'sort.nameDesc': '名称 Z-A',
259
+ 'sort.recentlyUpdated': '最近更新',
260
+ 'sort.leastUpdated': '最早更新',
261
+
262
+ // 筛选
263
+ 'filter.allPlatforms': '所有平台',
264
+
251
265
  // 分页
252
266
  'pagination.prev': '← 上一页',
253
267
  'pagination.next': '下一页 →',
254
268
  'pagination.pageInfo': '第 {page} 页 / 共 {totalPages} 页',
269
+ 'pagination.goTo': '跳转到',
270
+ 'pagination.go': '跳转',
255
271
 
256
272
  // 每页数量
257
273
  'pageSize.10': '每页 10 条',
@@ -263,6 +279,11 @@ const translations = {
263
279
  'status.unavailable': '❌ 未检测到',
264
280
  'status.skillsInstalled': '已安装 {count} 个技能',
265
281
 
282
+ // Platform 详情
283
+ 'platform.id': '平台 ID',
284
+ 'platform.installedSkills': '已安装技能({count} 个)',
285
+ 'platform.noSkills': '该平台未安装任何技能',
286
+
266
287
  // 详情视图
267
288
  'detail.description': '描述',
268
289
  'detail.details': '详细信息',
@@ -471,6 +492,28 @@ function applyI18nToStaticElements() {
471
492
  `;
472
493
  }
473
494
 
495
+ // sort-select 排序选项
496
+ const sortSelect = document.getElementById('sort-select');
497
+ if (sortSelect) {
498
+ const currentSort = sortSelect.value || state.sortBy;
499
+ sortSelect.innerHTML = `
500
+ <option value="name"${currentSort === 'name' ? ' selected' : ''}>${t('sort.nameAsc')}</option>
501
+ <option value="-name"${currentSort === '-name' ? ' selected' : ''}>${t('sort.nameDesc')}</option>
502
+ <option value="-updated"${currentSort === '-updated' ? ' selected' : ''}>${t('sort.recentlyUpdated')}</option>
503
+ <option value="updated"${currentSort === 'updated' ? ' selected' : ''}>${t('sort.leastUpdated')}</option>
504
+ `;
505
+ }
506
+
507
+ // platform-filter 默认选项(后续由 updatePlatformFilterOptions 补充完整列表)
508
+ const platformFilter = document.getElementById('platform-filter');
509
+ if (platformFilter && platformFilter.options.length <= 1) {
510
+ const currentFilter = platformFilter.value;
511
+ platformFilter.innerHTML = `
512
+ <option value="">${t('filter.allPlatforms')}</option>
513
+ `;
514
+ if (currentFilter) platformFilter.value = currentFilter;
515
+ }
516
+
474
517
  // 按钮文本
475
518
  const refreshSkills = document.getElementById('refresh-skills');
476
519
  const refreshAdmin = document.getElementById('refresh-admin');
@@ -817,13 +860,14 @@ function updatePlatformFilterOptions(skills) {
817
860
  const currentVal = select.value;
818
861
  const sortedPlatforms = [...allPlatforms].sort();
819
862
 
820
- // 只有当平台列表有变化时才重新渲染
863
+ // 当平台列表或当前语言变化时才重新渲染
821
864
  const currentOptions = Array.from(select.options).slice(1).map(o => o.value).sort().join(',');
822
865
  const newOptions = sortedPlatforms.join(',');
823
- if (currentOptions === newOptions) return;
866
+ const currentAllText = select.options[0]?.textContent || '';
867
+ if (currentOptions === newOptions && currentAllText === t('filter.allPlatforms')) return;
824
868
 
825
869
  select.innerHTML = `
826
- <option value="">All Platforms</option>
870
+ <option value="">${t('filter.allPlatforms')}</option>
827
871
  ${sortedPlatforms.map(p => `<option value="${p}"${currentVal === p ? ' selected' : ''}>${p}</option>`).join('')}
828
872
  `;
829
873
  select.value = currentVal && allPlatforms.has(currentVal) ? currentVal : '';
@@ -844,10 +888,10 @@ function renderPagination(currentPage, totalPages) {
844
888
  <button ${currentPage <= 1 ? 'disabled' : ''} onclick="changePage(${currentPage - 1})">${t('pagination.prev')}</button>
845
889
  <span class="page-info">${t('pagination.pageInfo', { page: currentPage, totalPages: totalPages })}</span>
846
890
  <span class="page-jump">
847
- <label>Go to</label>
891
+ <label>${t('pagination.goTo')}</label>
848
892
  <input type="number" id="page-jump-input" min="1" max="${totalPages}" value="${currentPage}"
849
893
  onkeydown="if(event.key==='Enter')jumpToPage()">
850
- <button onclick="jumpToPage()">Go</button>
894
+ <button onclick="jumpToPage()">${t('pagination.go')}</button>
851
895
  </span>
852
896
  <button ${currentPage >= totalPages ? 'disabled' : ''} onclick="changePage(${currentPage + 1})">${t('pagination.next')}</button>
853
897
  `;
@@ -910,7 +954,7 @@ function renderPlatforms(platforms, container) {
910
954
  </div>
911
955
  </div>
912
956
  <div>
913
- ${platform.installedCount ? `<span>${t('status.skillsInstalled', { count: platform.installedCount })}</span>` : '<span style="color: var(--text-muted); font-size: 0.85rem;">0 installed</span>'}
957
+ ${platform.installedCount ? `<span>${t('status.skillsInstalled', { count: platform.installedCount })}</span>` : `<span style="color: var(--text-muted); font-size: 0.85rem;">${t('status.skillsInstalled', { count: 0 })}</span>`}
914
958
  </div>
915
959
  </div>
916
960
  `).join('');
@@ -956,13 +1000,13 @@ async function showPlatformDetail(platformId) {
956
1000
  </div>
957
1001
  </div>
958
1002
  <div class="admin-skill-row" style="background: var(--bg-card); padding: 10px 16px; margin-bottom: 12px;">
959
- <span style="color: var(--text-muted); font-size: 0.85rem;">Platform ID</span>
1003
+ <span style="color: var(--text-muted); font-size: 0.85rem;">${t('platform.id')}</span>
960
1004
  <span style="color: var(--text-secondary); font-size: 0.9rem; font-family: monospace;">${platform.id}</span>
961
1005
  </div>
962
1006
  <h3 style="color: var(--text-secondary); margin-bottom: 10px; font-size: 1rem;">
963
- Installed Skills (${skills.length})
1007
+ ${t('platform.installedSkills', { count: skills.length })}
964
1008
  </h3>
965
- ${skills.length === 0 ? '<div class="loading" style="padding: 20px;">No skills installed on this platform.</div>' : `
1009
+ ${skills.length === 0 ? `<div class="loading" style="padding: 20px;">${t('platform.noSkills')}</div>` : `
966
1010
  <div style="display: flex; flex-direction: column; gap: 6px;">
967
1011
  ${skills.map((skill, i) => `
968
1012
  <div class="admin-skill-row" style="cursor: pointer;" onclick="showSkillDetail('${skill.id}')">
@@ -1083,16 +1127,32 @@ skm config reset --all # 全部恢复默认</pre>
1083
1127
  <tbody>
1084
1128
  <tr><td><code>skm ls</code></td><td>列出可用 skills</td></tr>
1085
1129
  <tr><td><code>skm ls --installed</code></td><td>列出已安装 skills</td></tr>
1130
+ <tr><td><code>skm search &lt;keyword&gt;</code></td><td>搜索 skills</td></tr>
1131
+ <tr><td><code>skm info &lt;skill&gt;</code></td><td>查看 skill 详情</td></tr>
1086
1132
  <tr><td><code>skm install &lt;skill&gt;</code></td><td>安装 skill 到所有平台</td></tr>
1133
+ <tr><td><code>skm install &lt;skill&gt;@&lt;ver&gt;</code></td><td>安装指定版本</td></tr>
1087
1134
  <tr><td><code>skm install &lt;skill&gt; --platform opencode</code></td><td>安装到指定平台</td></tr>
1135
+ <tr><td><code>skm install &lt;skill&gt; --force</code></td><td>强制覆盖安装</td></tr>
1136
+ <tr><td><code>skm install owner/repo</code></td><td>从 GitHub 安装</td></tr>
1088
1137
  <tr><td><code>skm uninstall &lt;skill&gt;</code></td><td>卸载 skill</td></tr>
1089
- <tr><td><code>skm update &lt;skill&gt;</code></td><td>更新 skill</td></tr>
1138
+ <tr><td><code>skm uninstall --all</code></td><td>卸载所有 skills</td></tr>
1139
+ <tr><td><code>skm update [skill]</code></td><td>更新 skill(不指定则更新全部)</td></tr>
1090
1140
  <tr><td><code>skm update --all</code></td><td>更新所有 skills</td></tr>
1141
+ <tr><td><code>skm publish &lt;skill&gt;</code></td><td>发布 skill 到 npm</td></tr>
1142
+ <tr><td><code>skm verify &lt;skill&gt;</code></td><td>验证 skill 完整性</td></tr>
1091
1143
  <tr><td><code>skm platforms</code></td><td>查看可用平台</td></tr>
1144
+ <tr><td><code>skm sync</code></td><td>同步平台链接</td></tr>
1145
+ <tr><td><code>skm sync &lt;skill&gt;</code></td><td>同步指定 skill 到最新</td></tr>
1092
1146
  <tr><td><code>skm gui</code></td><td>启动图形界面</td></tr>
1093
1147
  <tr><td><code>skm gui 18790</code></td><td>指定端口启动 GUI</td></tr>
1094
1148
  <tr><td><code>skm config</code></td><td>查看所有配置项</td></tr>
1095
- <tr><td><code>skm config set npmRegistry https://...</code></td><td>设置 registry 镜像</td></tr>
1149
+ <tr><td><code>skm config get &lt;key&gt;</code></td><td>查看指定配置</td></tr>
1150
+ <tr><td><code>skm config set &lt;key&gt; &lt;value&gt;</code></td><td>设置配置值</td></tr>
1151
+ <tr><td><code>skm config reset [key]</code></td><td>恢复配置为默认值</td></tr>
1152
+ <tr><td><code>skm admin ls</code></td><td>列出所有已发布 skills</td></tr>
1153
+ <tr><td><code>skm admin info &lt;skill&gt;</code></td><td>查看已发布 skill 详情</td></tr>
1154
+ <tr><td><code>skm admin deprecate &lt;skill&gt;</code></td><td>废弃 skill</td></tr>
1155
+ <tr><td><code>skm admin unpublish &lt;skill&gt;</code></td><td>取消发布 skill</td></tr>
1096
1156
  </tbody>
1097
1157
  </table>
1098
1158
  </div>
package/gui/index.html CHANGED
@@ -37,15 +37,8 @@
37
37
  <h2>Available Skills</h2>
38
38
  <div class="controls">
39
39
  <input type="text" id="search-input" placeholder="🔍 Search skills...">
40
- <select id="sort-select">
41
- <option value="name">Name A-Z</option>
42
- <option value="-name">Name Z-A</option>
43
- <option value="-updated">Recently Updated</option>
44
- <option value="updated">Least Recently Updated</option>
45
- </select>
46
- <select id="platform-filter">
47
- <option value="">All Platforms</option>
48
- </select>
40
+ <select id="sort-select"></select>
41
+ <select id="platform-filter"></select>
49
42
  <select id="page-size">
50
43
  <option value="10">10 per page</option>
51
44
  <option value="20" selected>20 per page</option>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itismyskillmarket",
3
- "version": "1.3.24",
3
+ "version": "1.3.26",
4
4
  "description": "Cross-platform skill manager for AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {