itismyskillmarket 1.3.23 → 1.3.25

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/README.md CHANGED
@@ -79,6 +79,13 @@ skm publish <skill-name> --version 1.0.1
79
79
  # Verify a skill
80
80
  skm verify <skill-name>
81
81
 
82
+ # Manage configuration
83
+ skm config # View all configuration values
84
+ skm config get npmRegistry # Get a specific config value
85
+ skm config set npmRegistry https://registry.npmmirror.com # Set a value (persistent)
86
+ skm config reset npmScope # Reset a config to default
87
+ skm config reset --all # Reset all config to defaults
88
+
82
89
  # Start GUI (web interface)
83
90
  skm gui
84
91
  skm gui 18790 # Custom port
@@ -165,9 +172,27 @@ The `skm gui` web interface includes an **Admin** view with:
165
172
  skm gui
166
173
  ```
167
174
 
168
- ## Environment Variables
175
+ ## Configuration
176
+
177
+ SkillMarket supports three levels of configuration (priority: high → low):
178
+
179
+ 1. **Environment variables** — highest priority, override everything
180
+ 2. **Config file** (`~/.skillmarket/config.json`) — set via `skm config set`
181
+ 3. **Defaults** — sensible built-in values
182
+
183
+ Use the `skm config` CLI commands to manage persistent configuration:
184
+
185
+ ```bash
186
+ skm config # View all config with source indicators
187
+ skm config set npmRegistry https://registry.npmmirror.com # Set mirror registry
188
+ skm config get npmRegistry # View single config value
189
+ skm config reset npmScope # Reset to default
190
+ skm config reset --all # Reset all config
191
+ ```
192
+
193
+ ### Environment Variables
169
194
 
170
- SkillMarket reads configuration from environment variables:
195
+ Environment variables override both config file and defaults:
171
196
 
172
197
  | Variable | Default | Description |
173
198
  |----------|---------|-------------|
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { Command } from "commander";
5
- import { readFileSync as readFileSync2 } from "fs";
5
+ import { readFileSync as readFileSync3 } from "fs";
6
6
  import { fileURLToPath as fileURLToPath4 } from "url";
7
7
  import { dirname as dirname2, resolve } from "path";
8
8
 
@@ -105,9 +105,24 @@ import https from "https";
105
105
  import { URL as URL2 } from "url";
106
106
 
107
107
  // src/config.ts
108
- var NPM_SCOPE = process.env.SKM_NPM_SCOPE || "@itismyskillmarket";
109
- var NPM_SCOPE_FALLBACK = process.env.SKM_NPM_SCOPE_FALLBACK || "@wanxuchen";
110
- var NPM_REGISTRY = process.env.SKM_NPM_REGISTRY || "https://registry.npmjs.org";
108
+ import { readFileSync, existsSync } from "fs";
109
+ import { join } from "path";
110
+ import os2 from "os";
111
+ var CONFIG_FILE_PATH = join(os2.homedir(), ".skillmarket", "config.json");
112
+ function loadConfigFileSync() {
113
+ try {
114
+ if (existsSync(CONFIG_FILE_PATH)) {
115
+ const raw = readFileSync(CONFIG_FILE_PATH, "utf-8");
116
+ return JSON.parse(raw);
117
+ }
118
+ } catch {
119
+ }
120
+ return {};
121
+ }
122
+ var fileConfig = loadConfigFileSync();
123
+ var NPM_SCOPE = process.env.SKM_NPM_SCOPE || fileConfig.npmScope || "@itismyskillmarket";
124
+ var NPM_SCOPE_FALLBACK = process.env.SKM_NPM_SCOPE_FALLBACK || fileConfig.npmScopeFallback || "@wanxuchen";
125
+ var NPM_REGISTRY = process.env.SKM_NPM_REGISTRY || fileConfig.npmRegistry || "https://registry.npmjs.org";
111
126
  var DEFAULT_SCOPES = [
112
127
  "@itismyskillmarket",
113
128
  "@wanxuchen",
@@ -115,8 +130,9 @@ var DEFAULT_SCOPES = [
115
130
  "@this-is-skillmarket",
116
131
  "@skillmarket"
117
132
  ];
118
- var SKILL_SCOPES = process.env.SKM_NPM_SCOPES ? process.env.SKM_NPM_SCOPES.split(",").map((s) => s.trim()).filter(Boolean) : DEFAULT_SCOPES;
119
- var SKM_URL = process.env.SKM_URL || `https://www.npmjs.com/package/${NPM_SCOPE}`;
133
+ var fileScopes = fileConfig.npmScopes ? fileConfig.npmScopes.split(",").map((s) => s.trim()).filter(Boolean) : null;
134
+ var SKILL_SCOPES = process.env.SKM_NPM_SCOPES ? process.env.SKM_NPM_SCOPES.split(",").map((s) => s.trim()).filter(Boolean) : fileScopes || DEFAULT_SCOPES;
135
+ var SKM_URL = process.env.SKM_URL || fileConfig.skmUrl || `https://www.npmjs.com/package/${NPM_SCOPE}`;
120
136
 
121
137
  // src/commands/npm.ts
122
138
  async function fetchNpmPackage(packageName, retries = 1) {
@@ -505,18 +521,18 @@ var BaseAdapter = class {
505
521
 
506
522
  // src/adapters/opencode.ts
507
523
  import path3 from "path";
508
- import os2 from "os";
524
+ import os3 from "os";
509
525
  import fs4 from "fs-extra";
510
526
  var OpenCodeAdapter = class extends BaseAdapter {
511
527
  id = "opencode";
512
528
  name = "OpenCode";
513
529
  get skillDir() {
514
- const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os2.homedir(), ".config", "opencode");
530
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os3.homedir(), ".config", "opencode");
515
531
  return path3.join(configDir, "skills");
516
532
  }
517
533
  async isAvailable() {
518
534
  if (process.env.OPENCODE) return true;
519
- const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os2.homedir(), ".config", "opencode");
535
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os3.homedir(), ".config", "opencode");
520
536
  try {
521
537
  await fs4.ensureDir(path3.join(configDir, "skills"));
522
538
  return true;
@@ -528,35 +544,35 @@ var OpenCodeAdapter = class extends BaseAdapter {
528
544
 
529
545
  // src/adapters/claude.ts
530
546
  import path4 from "path";
531
- import os3 from "os";
547
+ import os4 from "os";
532
548
  import fs5 from "fs-extra";
533
549
  var ClaudeAdapter = class extends BaseAdapter {
534
550
  id = "claude";
535
551
  name = "Claude Code";
536
552
  get skillDir() {
537
- return path4.join(os3.homedir(), ".claude", "skills");
553
+ return path4.join(os4.homedir(), ".claude", "skills");
538
554
  }
539
555
  async isAvailable() {
540
556
  if (process.env.CLAUDE_CODE) return true;
541
- const claudeDir = path4.join(os3.homedir(), ".claude");
557
+ const claudeDir = path4.join(os4.homedir(), ".claude");
542
558
  return fs5.pathExists(claudeDir);
543
559
  }
544
560
  };
545
561
 
546
562
  // src/adapters/vscode.ts
547
563
  import path5 from "path";
548
- import os4 from "os";
564
+ import os5 from "os";
549
565
  import fs6 from "fs-extra";
550
566
  var VSCodeAdapter = class extends BaseAdapter {
551
567
  id = "vscode";
552
568
  name = "VSCode";
553
569
  get skillDir() {
554
- return path5.join(os4.homedir(), ".copilot", "skills");
570
+ return path5.join(os5.homedir(), ".copilot", "skills");
555
571
  }
556
572
  async isAvailable() {
557
573
  const possibleDirs = [
558
- path5.join(os4.homedir(), ".copilot", "skills"),
559
- path5.join(os4.homedir(), ".claude", "skills")
574
+ path5.join(os5.homedir(), ".copilot", "skills"),
575
+ path5.join(os5.homedir(), ".claude", "skills")
560
576
  ];
561
577
  for (const dir of possibleDirs) {
562
578
  try {
@@ -570,7 +586,7 @@ var VSCodeAdapter = class extends BaseAdapter {
570
586
  }
571
587
  async install(skillId, sourceDir) {
572
588
  await super.install(skillId, sourceDir);
573
- const claudeSkillDir = path5.join(os4.homedir(), ".claude", "skills");
589
+ const claudeSkillDir = path5.join(os5.homedir(), ".claude", "skills");
574
590
  const targetPath = this.getSkillPath(skillId);
575
591
  const claudeTargetPath = path5.join(claudeSkillDir, skillId);
576
592
  try {
@@ -583,51 +599,51 @@ var VSCodeAdapter = class extends BaseAdapter {
583
599
  };
584
600
 
585
601
  // src/adapters/openclaw.ts
586
- import { readdirSync, existsSync, cpSync, rmSync } from "fs";
587
- import { join } from "path";
602
+ import { readdirSync, existsSync as existsSync2, cpSync, rmSync } from "fs";
603
+ import { join as join2 } from "path";
588
604
  import { homedir } from "os";
589
605
  import { ensureDirSync } from "fs-extra";
590
606
  var OpenClawAdapter = class {
591
607
  id = "openclaw";
592
608
  name = "OpenClaw";
593
- skillDir = join(homedir(), ".openclaw", "skills");
609
+ skillDir = join2(homedir(), ".openclaw", "skills");
594
610
  async isAvailable() {
595
611
  try {
596
- return existsSync(join(homedir(), ".openclaw"));
612
+ return existsSync2(join2(homedir(), ".openclaw"));
597
613
  } catch {
598
614
  return false;
599
615
  }
600
616
  }
601
617
  async isInstalled(skillId) {
602
618
  try {
603
- const skillPath = join(this.skillDir, skillId);
604
- return existsSync(skillPath);
619
+ const skillPath = join2(this.skillDir, skillId);
620
+ return existsSync2(skillPath);
605
621
  } catch {
606
622
  return false;
607
623
  }
608
624
  }
609
625
  async install(skillId, sourceDir) {
610
626
  ensureDirSync(this.skillDir);
611
- const targetDir = join(this.skillDir, skillId);
612
- if (existsSync(targetDir)) {
627
+ const targetDir = join2(this.skillDir, skillId);
628
+ if (existsSync2(targetDir)) {
613
629
  rmSync(targetDir, { recursive: true, force: true });
614
630
  }
615
631
  cpSync(sourceDir, targetDir, { recursive: true });
616
632
  }
617
633
  async uninstall(skillId) {
618
- const targetDir = join(this.skillDir, skillId);
619
- if (existsSync(targetDir)) {
634
+ const targetDir = join2(this.skillDir, skillId);
635
+ if (existsSync2(targetDir)) {
620
636
  rmSync(targetDir, { recursive: true, force: true });
621
637
  }
622
638
  }
623
639
  async listInstalled() {
624
640
  try {
625
- if (!existsSync(this.skillDir)) {
641
+ if (!existsSync2(this.skillDir)) {
626
642
  return [];
627
643
  }
628
644
  return readdirSync(this.skillDir).filter((name) => {
629
- const fullPath = join(this.skillDir, name);
630
- return existsSync(fullPath) && name !== ".";
645
+ const fullPath = join2(this.skillDir, name);
646
+ return existsSync2(fullPath) && name !== ".";
631
647
  });
632
648
  } catch {
633
649
  return [];
@@ -636,17 +652,17 @@ var OpenClawAdapter = class {
636
652
  };
637
653
 
638
654
  // src/adapters/hermes.ts
639
- import { readdirSync as readdirSync2, existsSync as existsSync2, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
640
- import { join as join2 } from "path";
655
+ import { readdirSync as readdirSync2, existsSync as existsSync3, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
656
+ import { join as join3 } from "path";
641
657
  import { homedir as homedir2 } from "os";
642
658
  import { ensureDirSync as ensureDirSync2 } from "fs-extra";
643
659
  var HermesAdapter = class {
644
660
  id = "hermes";
645
661
  name = "Hermes Agent";
646
- skillDir = join2(homedir2(), ".hermes", "skills");
662
+ skillDir = join3(homedir2(), ".hermes", "skills");
647
663
  async isAvailable() {
648
664
  try {
649
- if (existsSync2(join2(homedir2(), ".hermes"))) {
665
+ if (existsSync3(join3(homedir2(), ".hermes"))) {
650
666
  return true;
651
667
  }
652
668
  return false;
@@ -656,34 +672,34 @@ var HermesAdapter = class {
656
672
  }
657
673
  async isInstalled(skillId) {
658
674
  try {
659
- const skillPath = join2(this.skillDir, skillId);
660
- return existsSync2(skillPath);
675
+ const skillPath = join3(this.skillDir, skillId);
676
+ return existsSync3(skillPath);
661
677
  } catch {
662
678
  return false;
663
679
  }
664
680
  }
665
681
  async install(skillId, sourceDir) {
666
682
  ensureDirSync2(this.skillDir);
667
- const targetDir = join2(this.skillDir, skillId);
668
- if (existsSync2(targetDir)) {
683
+ const targetDir = join3(this.skillDir, skillId);
684
+ if (existsSync3(targetDir)) {
669
685
  rmSync2(targetDir, { recursive: true, force: true });
670
686
  }
671
687
  cpSync2(sourceDir, targetDir, { recursive: true });
672
688
  }
673
689
  async uninstall(skillId) {
674
- const targetDir = join2(this.skillDir, skillId);
675
- if (existsSync2(targetDir)) {
690
+ const targetDir = join3(this.skillDir, skillId);
691
+ if (existsSync3(targetDir)) {
676
692
  rmSync2(targetDir, { recursive: true, force: true });
677
693
  }
678
694
  }
679
695
  async listInstalled() {
680
696
  try {
681
- if (!existsSync2(this.skillDir)) {
697
+ if (!existsSync3(this.skillDir)) {
682
698
  return [];
683
699
  }
684
700
  return readdirSync2(this.skillDir).filter((name) => {
685
- const fullPath = join2(this.skillDir, name);
686
- return existsSync2(fullPath) && name !== ".";
701
+ const fullPath = join3(this.skillDir, name);
702
+ return existsSync3(fullPath) && name !== ".";
687
703
  });
688
704
  } catch {
689
705
  return [];
@@ -1154,13 +1170,13 @@ function parseGitHubUrl(input) {
1154
1170
  const repo = match[2].replace(/\.git$/, "");
1155
1171
  const branch = match[3] || "main";
1156
1172
  const commitOrPath = match[4] || match[3];
1157
- const path11 = match[5] || void 0;
1173
+ const path12 = match[5] || void 0;
1158
1174
  return {
1159
1175
  owner,
1160
1176
  repo,
1161
1177
  branch: commitOrPath && !commitOrPath.includes("/") ? commitOrPath : branch,
1162
1178
  commit: commitOrPath?.match(/^[0-9a-f]{40}$/) ? commitOrPath : void 0,
1163
- path: path11
1179
+ path: path12
1164
1180
  };
1165
1181
  }
1166
1182
  }
@@ -1403,15 +1419,15 @@ Installing to platforms: ${targetPlatforms.join(", ")}`);
1403
1419
 
1404
1420
  // src/commands/publish.ts
1405
1421
  import { execSync } from "child_process";
1406
- import { existsSync as existsSync3 } from "fs";
1407
- import { join as join3 } from "path";
1422
+ import { existsSync as existsSync4 } from "fs";
1423
+ import { join as join4 } from "path";
1408
1424
  import { fileURLToPath } from "url";
1409
1425
  async function publishSkill(skillName, options) {
1410
1426
  const __dirname4 = fileURLToPath(new URL(".", import.meta.url));
1411
- const projectRoot = join3(__dirname4, "..", "..");
1412
- const skillDir = join3(projectRoot, "skills", skillName);
1427
+ const projectRoot = join4(__dirname4, "..", "..");
1428
+ const skillDir = join4(projectRoot, "skills", skillName);
1413
1429
  console.log(`Publishing ${skillName}...`);
1414
- if (!existsSync3(skillDir)) {
1430
+ if (!existsSync4(skillDir)) {
1415
1431
  throw new Error(`Skill '${skillName}' not found in skills/ directory`);
1416
1432
  }
1417
1433
  if (!options?.skipInstall) {
@@ -1545,8 +1561,8 @@ async function verifySkill(skillName) {
1545
1561
 
1546
1562
  // src/commands/ui.ts
1547
1563
  import { createServer } from "http";
1548
- import { readFileSync, existsSync as existsSync4 } from "fs";
1549
- import { join as join4, extname, dirname } from "path";
1564
+ import { readFileSync as readFileSync2, existsSync as existsSync5 } from "fs";
1565
+ import { join as join5, extname, dirname } from "path";
1550
1566
  import { fileURLToPath as fileURLToPath3 } from "url";
1551
1567
 
1552
1568
  // src/commands/admin.ts
@@ -2013,7 +2029,7 @@ async function adminAccess(skillId, level) {
2013
2029
  // src/commands/ui.ts
2014
2030
  var __filename2 = fileURLToPath3(import.meta.url);
2015
2031
  var __dirname2 = dirname(__filename2);
2016
- var guiDir = join4(__dirname2, "..", "gui");
2032
+ var guiDir = join5(__dirname2, "..", "gui");
2017
2033
  var cache = /* @__PURE__ */ new Map();
2018
2034
  function getCached(key) {
2019
2035
  const entry = cache.get(key);
@@ -2270,8 +2286,8 @@ API_ROUTES.GET["/api/skill-info"] = async (_req, res, url) => {
2270
2286
  };
2271
2287
  API_ROUTES.GET["/api/version"] = async (_req, res, _url) => {
2272
2288
  try {
2273
- const pkgPath = join4(__dirname2, "..", "package.json");
2274
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
2289
+ const pkgPath = join5(__dirname2, "..", "package.json");
2290
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
2275
2291
  jsonResponse(res, 200, { version: pkg.version || "1.0.0" });
2276
2292
  } catch {
2277
2293
  jsonResponse(res, 200, { version: "1.0.0" });
@@ -2508,12 +2524,12 @@ API_ROUTES.POST["/api/update"] = async (req, res, _url) => {
2508
2524
  }
2509
2525
  };
2510
2526
  function serveStaticFile(res, filePath) {
2511
- if (!existsSync4(filePath)) {
2527
+ if (!existsSync5(filePath)) {
2512
2528
  res.writeHead(404, { "Content-Type": "text/plain" });
2513
2529
  res.end("Not Found");
2514
2530
  return;
2515
2531
  }
2516
- const content = readFileSync(filePath);
2532
+ const content = readFileSync2(filePath);
2517
2533
  const ext = extname(filePath);
2518
2534
  const mime = MIME_TYPES[ext] || "application/octet-stream";
2519
2535
  res.writeHead(200, {
@@ -2541,7 +2557,7 @@ async function handleRequest(req, res) {
2541
2557
  jsonResponse(res, 404, { error: `Unknown API endpoint: ${method} ${pathname}` });
2542
2558
  return;
2543
2559
  }
2544
- const filePath = join4(guiDir, pathname === "/" ? "index.html" : pathname);
2560
+ const filePath = join5(guiDir, pathname === "/" ? "index.html" : pathname);
2545
2561
  if (filePath.startsWith(guiDir)) {
2546
2562
  serveStaticFile(res, filePath);
2547
2563
  } else {
@@ -2569,10 +2585,226 @@ Press Ctrl+C to stop
2569
2585
  });
2570
2586
  }
2571
2587
 
2588
+ // src/commands/config.ts
2589
+ import path11 from "path";
2590
+ import fs12 from "fs-extra";
2591
+ import os6 from "os";
2592
+ var CONFIG_DEFINITIONS = [
2593
+ {
2594
+ key: "npmScope",
2595
+ envVar: "SKM_NPM_SCOPE",
2596
+ defaultValue: "@itismyskillmarket",
2597
+ description: "Primary npm scope for publishing and lookup"
2598
+ },
2599
+ {
2600
+ key: "npmScopeFallback",
2601
+ envVar: "SKM_NPM_SCOPE_FALLBACK",
2602
+ defaultValue: "@wanxuchen",
2603
+ description: "Fallback npm scope (backward compatibility)"
2604
+ },
2605
+ {
2606
+ key: "npmRegistry",
2607
+ envVar: "SKM_NPM_REGISTRY",
2608
+ defaultValue: "https://registry.npmjs.org",
2609
+ description: "npm registry URL (mirror/proxy support)"
2610
+ },
2611
+ {
2612
+ key: "npmScopes",
2613
+ envVar: "SKM_NPM_SCOPES",
2614
+ defaultValue: "@itismyskillmarket,@wanxuchen,@thisisskillmarket,@this-is-skillmarket,@skillmarket",
2615
+ description: "Comma-separated list of npm scopes to search"
2616
+ },
2617
+ {
2618
+ key: "skmUrl",
2619
+ envVar: "SKM_URL",
2620
+ defaultValue: "https://www.npmjs.com/package/@itismyskillmarket",
2621
+ description: "Base URL for skill links (publish output)"
2622
+ }
2623
+ ];
2624
+ function getConfigPath() {
2625
+ return path11.join(os6.homedir(), ".skillmarket", "config.json");
2626
+ }
2627
+ async function readConfigFile() {
2628
+ try {
2629
+ const configPath = getConfigPath();
2630
+ if (await fs12.pathExists(configPath)) {
2631
+ const data = await fs12.readJson(configPath);
2632
+ const valid = {};
2633
+ for (const def of CONFIG_DEFINITIONS) {
2634
+ if (data[def.key] !== void 0) {
2635
+ valid[def.key] = String(data[def.key]);
2636
+ }
2637
+ }
2638
+ return valid;
2639
+ }
2640
+ } catch {
2641
+ }
2642
+ return {};
2643
+ }
2644
+ async function writeConfigFile(updates) {
2645
+ const configPath = getConfigPath();
2646
+ await fs12.ensureDir(path11.dirname(configPath));
2647
+ let existing = {};
2648
+ try {
2649
+ if (await fs12.pathExists(configPath)) {
2650
+ existing = await fs12.readJson(configPath);
2651
+ }
2652
+ } catch {
2653
+ }
2654
+ const merged = { ...existing, ...updates };
2655
+ for (const key of Object.keys(merged)) {
2656
+ if (merged[key] === void 0) {
2657
+ delete merged[key];
2658
+ }
2659
+ }
2660
+ await fs12.writeJson(configPath, merged, { spaces: 2 });
2661
+ return merged;
2662
+ }
2663
+ async function removeConfigKeys(keys) {
2664
+ const configPath = getConfigPath();
2665
+ if (!await fs12.pathExists(configPath)) return;
2666
+ try {
2667
+ const existing = await fs12.readJson(configPath);
2668
+ for (const key of keys) {
2669
+ delete existing[key];
2670
+ }
2671
+ await fs12.writeJson(configPath, existing, { spaces: 2 });
2672
+ } catch {
2673
+ }
2674
+ }
2675
+ async function removeConfigFile() {
2676
+ const configPath = getConfigPath();
2677
+ if (await fs12.pathExists(configPath)) {
2678
+ await fs12.remove(configPath);
2679
+ }
2680
+ }
2681
+ async function getAllConfig() {
2682
+ const fileConfig2 = await readConfigFile();
2683
+ return CONFIG_DEFINITIONS.map((def) => {
2684
+ const envValue = process.env[def.envVar];
2685
+ if (envValue !== void 0) {
2686
+ return {
2687
+ ...def,
2688
+ value: envValue,
2689
+ source: "env"
2690
+ };
2691
+ }
2692
+ const fileValue = fileConfig2[def.key];
2693
+ if (fileValue !== void 0) {
2694
+ return {
2695
+ ...def,
2696
+ value: fileValue,
2697
+ source: "file"
2698
+ };
2699
+ }
2700
+ return {
2701
+ ...def,
2702
+ value: def.defaultValue,
2703
+ source: "default"
2704
+ };
2705
+ });
2706
+ }
2707
+ async function getConfig(key) {
2708
+ const all = await getAllConfig();
2709
+ return all.find((c) => c.key === key) || null;
2710
+ }
2711
+ async function listConfig() {
2712
+ const entries = await getAllConfig();
2713
+ console.log("\n\u{1F527} SkillMarket Configuration\n");
2714
+ const maxKeyLen = Math.max(...entries.map((e) => e.key.length));
2715
+ for (const entry of entries) {
2716
+ const key = entry.key.padEnd(maxKeyLen + 2);
2717
+ const sourceBadge = getSourceBadge(entry.source);
2718
+ console.log(` ${sourceBadge} ${key}${entry.value}`);
2719
+ }
2720
+ console.log("");
2721
+ console.log(" \u6765\u6E90: \u{1F535} \u73AF\u5883\u53D8\u91CF \u{1F7E2} \u914D\u7F6E\u6587\u4EF6 \u26AA \u9ED8\u8BA4\u503C");
2722
+ console.log(" \u914D\u7F6E\u6587\u4EF6: " + getConfigPath());
2723
+ console.log("");
2724
+ console.log(" \u7528\u6CD5:");
2725
+ console.log(" skm config get <key> \u67E5\u770B\u914D\u7F6E\u503C");
2726
+ console.log(" skm config set <key> <v> \u8BBE\u7F6E\u914D\u7F6E\u503C");
2727
+ console.log(" skm config reset <key> \u91CD\u7F6E\u4E3A\u9ED8\u8BA4\u503C");
2728
+ console.log(" skm config reset --all \u5168\u90E8\u91CD\u7F6E");
2729
+ console.log("");
2730
+ }
2731
+ function getSourceBadge(source) {
2732
+ switch (source) {
2733
+ case "env":
2734
+ return "\u{1F535}";
2735
+ case "file":
2736
+ return "\u{1F7E2}";
2737
+ case "default":
2738
+ return "\u26AA";
2739
+ }
2740
+ }
2741
+ async function getConfigValue(key) {
2742
+ const entry = await getConfig(key);
2743
+ if (!entry) {
2744
+ console.error(`\u274C Unknown config key: "${key}"`);
2745
+ console.log(` Valid keys: ${CONFIG_DEFINITIONS.map((d) => d.key).join(", ")}`);
2746
+ process.exit(1);
2747
+ }
2748
+ console.log(`
2749
+ \u{1F527} ${entry.key}`);
2750
+ console.log(` Value: ${entry.value}`);
2751
+ console.log(` Source: ${entry.source}`);
2752
+ console.log(` Env var: ${entry.envVar}`);
2753
+ console.log(` Default: ${entry.defaultValue}`);
2754
+ console.log(` Description: ${entry.description}`);
2755
+ console.log("");
2756
+ }
2757
+ async function setConfigValue(key, value) {
2758
+ const def = CONFIG_DEFINITIONS.find((d) => d.key === key);
2759
+ if (!def) {
2760
+ console.error(`\u274C Unknown config key: "${key}"`);
2761
+ console.log(` Valid keys: ${CONFIG_DEFINITIONS.map((d) => d.key).join(", ")}`);
2762
+ process.exit(1);
2763
+ }
2764
+ await writeConfigFile({ [key]: value });
2765
+ console.log(`
2766
+ \u2705 ${key} set to "${value}"`);
2767
+ console.log(` Stored in: ${getConfigPath()}`);
2768
+ console.log("");
2769
+ if (process.env[def.envVar] !== void 0) {
2770
+ console.log(` \u26A0\uFE0F Currently overridden by environment variable ${def.envVar}=${process.env[def.envVar]}`);
2771
+ console.log(` To use the config file value, unset the environment variable.`);
2772
+ console.log("");
2773
+ }
2774
+ }
2775
+ async function resetConfig(key, all = false) {
2776
+ if (all) {
2777
+ await removeConfigFile();
2778
+ console.log("\n\u2705 All configuration reset to defaults.");
2779
+ console.log(` Removed: ${getConfigPath()}`);
2780
+ console.log("");
2781
+ return;
2782
+ }
2783
+ if (key) {
2784
+ const def = CONFIG_DEFINITIONS.find((d) => d.key === key);
2785
+ if (!def) {
2786
+ console.error(`\u274C Unknown config key: "${key}"`);
2787
+ console.log(` Valid keys: ${CONFIG_DEFINITIONS.map((d) => d.key).join(", ")}`);
2788
+ process.exit(1);
2789
+ }
2790
+ await removeConfigKeys([key]);
2791
+ const sourceNow = process.env[def.envVar] ? "env" : "default";
2792
+ console.log(`
2793
+ \u2705 ${key} reset to ${sourceNow === "env" ? "environment variable" : "default"} value.`);
2794
+ console.log(` Effective value: "${sourceNow === "env" ? process.env[def.envVar] : def.defaultValue}"`);
2795
+ console.log("");
2796
+ return;
2797
+ }
2798
+ console.log("\n\u{1F527} Usage: skm config reset <key>");
2799
+ console.log(" skm config reset --all");
2800
+ console.log(` Valid keys: ${CONFIG_DEFINITIONS.map((d) => d.key).join(", ")}`);
2801
+ console.log("");
2802
+ }
2803
+
2572
2804
  // src/cli.ts
2573
2805
  var __filename3 = fileURLToPath4(import.meta.url);
2574
2806
  var __dirname3 = dirname2(__filename3);
2575
- var packageJson = JSON.parse(readFileSync2(resolve(__dirname3, "../package.json"), "utf-8"));
2807
+ var packageJson = JSON.parse(readFileSync3(resolve(__dirname3, "../package.json"), "utf-8"));
2576
2808
  var VERSION = packageJson.version || "1.3.1";
2577
2809
  var program = new Command();
2578
2810
  program.name("skm").description("SkillMarket - Cross-platform skill manager for AI coding tools").version(VERSION);
@@ -2584,45 +2816,81 @@ SkillMarket CLI
2584
2816
  Usage: skm <command> [options]
2585
2817
 
2586
2818
  Commands:
2587
- ls [options] List available skills
2588
- --installed Show only installed skills
2589
- --updates Check for updates
2590
- --page <n> Page number (default: 1)
2591
- --limit <n> Items per page (default: 20)
2592
- -s, --search Search by keyword
2593
- info <skill-id> Display skill information
2594
- install <skill> Install a skill
2595
- @version Install specific version
2596
- --platform Target platforms (opencode,claude,vscode)
2597
- --force Overwrite if already installed
2598
- uninstall <skill> Remove an installed skill
2599
- --platform Target platforms
2600
- --all Uninstall ALL installed skills
2601
- --dry-run Preview without deleting
2602
- -y, --yes Skip confirmation
2603
- update [options] Update skills
2604
- --all Update all skills
2605
- sync Synchronize platform links
2606
- platforms Show available platforms
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
+ search <keyword> Search skills from npm registry
2826
+ info <skill> Display skill information
2827
+ install <skill> Install a skill from npm or GitHub
2828
+ @<version> Install specific version
2829
+ --platform Target platforms (opencode,claude,...)
2830
+ --force Overwrite if already installed
2831
+ -b, --branch GitHub branch to install from
2832
+ -c, --commit GitHub commit to install from
2833
+ uninstall <skill> Remove an installed skill
2834
+ --platform Target platforms
2835
+ --all Uninstall ALL installed skills
2836
+ --dry-run Preview without deleting
2837
+ -y, --yes Skip confirmation
2838
+ update [skill] Update installed skills (all if no skill specified)
2839
+ --all Update all skills
2840
+ publish <skill> Publish a skill to npm
2841
+ -v, --version Specify version
2842
+ verify <skill> Verify skill integrity and format
2843
+ sync [skill] Synchronize platform links (or sync a skill to latest)
2844
+ platforms Show available platforms
2845
+ gui [port] Start SkillMarket GUI web interface
2846
+ config View and manage configuration
2847
+ config get <key> Get a config value
2848
+ config set <key> <val> Set a config value
2849
+ config reset [key] Reset config to defaults
2850
+ admin Admin: manage published skills
2851
+ admin ls List all published skills
2852
+ admin info <skill> Show detailed info for a published skill
2853
+ admin search <keyword> Search published skills
2854
+ admin stats Publishing statistics
2855
+ admin verify <skill> Verify a published skill
2856
+ admin deprecate <skill> Deprecate a skill (--version, --message)
2857
+ admin unpublish <skill> Unpublish a skill (--version, --force)
2858
+ admin tag set/tag rm/tag ls Manage dist-tags
2859
+ admin owner add/rm Manage package maintainers
2860
+ admin access <skill> Set package access (public|restricted)
2607
2861
 
2608
2862
  Examples:
2609
- skm ls List all available skills (page 1)
2610
- skm ls --page 2 Go to page 2
2611
- skm ls --limit 10 Show 10 items per page
2612
- skm ls --search brain Search skills by keyword
2613
- skm ls -s brain Search with short form
2614
- skm ls --installed Show installed skills only
2615
- skm ls --installed --search test Search installed skills
2616
- skm ls --installed --page 2
2617
- skm info brainstorming View skill details
2618
- skm install brainstorming Install to all platforms
2619
- skm install brainstorming --platform opencode Install to OpenCode only
2620
- skm install brainstorming --platform claude,vscode Install to multiple
2621
- skm uninstall brainstorming
2622
- skm uninstall --all Uninstall all skills (with confirmation)
2623
- skm uninstall --all --yes Force uninstall all without confirmation
2624
- skm uninstall brainstorming --dry-run Preview uninstall
2625
- skm platforms Show available platforms
2863
+ skm ls List available skills
2864
+ skm ls --page 2 --limit 10 Paginated listing
2865
+ skm ls --installed Show installed skills
2866
+ skm ls -s brain Search skills by keyword
2867
+ skm search test Search from registry
2868
+ skm info brainstorming View skill details
2869
+ skm install brainstorming Install to all detected platforms
2870
+ skm install brainstorming@1.0.0 Install specific version
2871
+ skm install owner/repo Install from GitHub
2872
+ skm install brainstorming --platform opencode Install to specific platform
2873
+ skm uninstall brainstorming Uninstall skill
2874
+ skm uninstall --all Uninstall all skills
2875
+ skm uninstall --all --yes Force uninstall all without confirmation
2876
+ skm uninstall --dry-run Preview uninstall
2877
+ skm update brainstorming Update specific skill
2878
+ skm update Update all skills
2879
+ skm publish my-skill Publish a skill
2880
+ skm publish my-skill --version 1.0.1 Publish specific version
2881
+ skm verify my-skill Verify skill integrity
2882
+ skm sync Sync platform links
2883
+ skm sync brainstorming Sync skill to latest version
2884
+ skm platforms Show available platforms
2885
+ skm gui Start GUI on default port 18770
2886
+ skm gui 18790 Start GUI on custom port
2887
+ skm config View all configuration
2888
+ skm config get npmRegistry View specific config
2889
+ skm config set npmRegistry https://registry.npmmirror.com Set mirror registry
2890
+ skm config reset npmScope Reset config to default
2891
+ skm config reset --all Reset all config
2892
+ skm admin ls List all published skills
2893
+ skm admin deprecate my-skill --message "Use v2" Deprecate a skill
2626
2894
  `);
2627
2895
  process.exit(0);
2628
2896
  }
@@ -2761,6 +3029,22 @@ program.command("verify <skill>").description("Verify skill integrity and format
2761
3029
  process.exit(1);
2762
3030
  }
2763
3031
  });
3032
+ var config = program.command("config").description("View and manage configuration");
3033
+ config.command("list").alias("ls").description("List all configuration values").action(async () => {
3034
+ await listConfig();
3035
+ });
3036
+ config.action(async () => {
3037
+ await listConfig();
3038
+ });
3039
+ config.command("get <key>").description("Get a specific configuration value").action(async (key) => {
3040
+ await getConfigValue(key);
3041
+ });
3042
+ config.command("set <key> <value>").description("Set a configuration value (persisted to config file)").action(async (key, value) => {
3043
+ await setConfigValue(key, value);
3044
+ });
3045
+ config.command("reset [key]").description("Reset configuration to default values").option("--all", "Reset all configuration values").action(async (key, opts) => {
3046
+ await resetConfig(key, opts.all ?? false);
3047
+ });
2764
3048
  var admin = program.command("admin").description("Admin: manage published skills (cloud)");
2765
3049
  admin.command("ls").description("List all published skills").action(async () => {
2766
3050
  try {
package/gui/app.js CHANGED
@@ -66,10 +66,21 @@ const translations = {
66
66
  // 搜索
67
67
  'search.placeholder': '🔍 Search skills...',
68
68
 
69
+ // 排序
70
+ 'sort.nameAsc': 'Name A-Z',
71
+ 'sort.nameDesc': 'Name Z-A',
72
+ 'sort.recentlyUpdated': 'Recently Updated',
73
+ 'sort.leastUpdated': 'Least Recently Updated',
74
+
75
+ // 筛选
76
+ 'filter.allPlatforms': 'All Platforms',
77
+
69
78
  // 分页
70
79
  'pagination.prev': '← Prev',
71
80
  'pagination.next': 'Next →',
72
81
  'pagination.pageInfo': 'Page {page} of {totalPages}',
82
+ 'pagination.goTo': 'Go to',
83
+ 'pagination.go': 'Go',
73
84
 
74
85
  // 每页数量
75
86
  'pageSize.10': '10 per page',
@@ -81,6 +92,11 @@ const translations = {
81
92
  'status.unavailable': '❌ Not detected',
82
93
  'status.skillsInstalled': '{count} skills installed',
83
94
 
95
+ // Platform 详情
96
+ 'platform.id': 'Platform ID',
97
+ 'platform.installedSkills': 'Installed Skills ({count})',
98
+ 'platform.noSkills': 'No skills installed on this platform.',
99
+
84
100
  // 详情视图
85
101
  'detail.description': 'Description',
86
102
  'detail.details': 'Details',
@@ -237,10 +253,21 @@ const translations = {
237
253
  // 搜索
238
254
  'search.placeholder': '🔍 搜索技能...',
239
255
 
256
+ // 排序
257
+ 'sort.nameAsc': '名称 A-Z',
258
+ 'sort.nameDesc': '名称 Z-A',
259
+ 'sort.recentlyUpdated': '最近更新',
260
+ 'sort.leastUpdated': '最早更新',
261
+
262
+ // 筛选
263
+ 'filter.allPlatforms': '所有平台',
264
+
240
265
  // 分页
241
266
  'pagination.prev': '← 上一页',
242
267
  'pagination.next': '下一页 →',
243
268
  'pagination.pageInfo': '第 {page} 页 / 共 {totalPages} 页',
269
+ 'pagination.goTo': '跳转到',
270
+ 'pagination.go': '跳转',
244
271
 
245
272
  // 每页数量
246
273
  'pageSize.10': '每页 10 条',
@@ -252,6 +279,11 @@ const translations = {
252
279
  'status.unavailable': '❌ 未检测到',
253
280
  'status.skillsInstalled': '已安装 {count} 个技能',
254
281
 
282
+ // Platform 详情
283
+ 'platform.id': '平台 ID',
284
+ 'platform.installedSkills': '已安装技能({count} 个)',
285
+ 'platform.noSkills': '该平台未安装任何技能',
286
+
255
287
  // 详情视图
256
288
  'detail.description': '描述',
257
289
  'detail.details': '详细信息',
@@ -460,6 +492,28 @@ function applyI18nToStaticElements() {
460
492
  `;
461
493
  }
462
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
+
463
517
  // 按钮文本
464
518
  const refreshSkills = document.getElementById('refresh-skills');
465
519
  const refreshAdmin = document.getElementById('refresh-admin');
@@ -806,13 +860,14 @@ function updatePlatformFilterOptions(skills) {
806
860
  const currentVal = select.value;
807
861
  const sortedPlatforms = [...allPlatforms].sort();
808
862
 
809
- // 只有当平台列表有变化时才重新渲染
863
+ // 当平台列表或当前语言变化时才重新渲染
810
864
  const currentOptions = Array.from(select.options).slice(1).map(o => o.value).sort().join(',');
811
865
  const newOptions = sortedPlatforms.join(',');
812
- if (currentOptions === newOptions) return;
866
+ const currentAllText = select.options[0]?.textContent || '';
867
+ if (currentOptions === newOptions && currentAllText === t('filter.allPlatforms')) return;
813
868
 
814
869
  select.innerHTML = `
815
- <option value="">All Platforms</option>
870
+ <option value="">${t('filter.allPlatforms')}</option>
816
871
  ${sortedPlatforms.map(p => `<option value="${p}"${currentVal === p ? ' selected' : ''}>${p}</option>`).join('')}
817
872
  `;
818
873
  select.value = currentVal && allPlatforms.has(currentVal) ? currentVal : '';
@@ -833,10 +888,10 @@ function renderPagination(currentPage, totalPages) {
833
888
  <button ${currentPage <= 1 ? 'disabled' : ''} onclick="changePage(${currentPage - 1})">${t('pagination.prev')}</button>
834
889
  <span class="page-info">${t('pagination.pageInfo', { page: currentPage, totalPages: totalPages })}</span>
835
890
  <span class="page-jump">
836
- <label>Go to</label>
891
+ <label>${t('pagination.goTo')}</label>
837
892
  <input type="number" id="page-jump-input" min="1" max="${totalPages}" value="${currentPage}"
838
893
  onkeydown="if(event.key==='Enter')jumpToPage()">
839
- <button onclick="jumpToPage()">Go</button>
894
+ <button onclick="jumpToPage()">${t('pagination.go')}</button>
840
895
  </span>
841
896
  <button ${currentPage >= totalPages ? 'disabled' : ''} onclick="changePage(${currentPage + 1})">${t('pagination.next')}</button>
842
897
  `;
@@ -899,7 +954,7 @@ function renderPlatforms(platforms, container) {
899
954
  </div>
900
955
  </div>
901
956
  <div>
902
- ${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>`}
903
958
  </div>
904
959
  </div>
905
960
  `).join('');
@@ -945,13 +1000,13 @@ async function showPlatformDetail(platformId) {
945
1000
  </div>
946
1001
  </div>
947
1002
  <div class="admin-skill-row" style="background: var(--bg-card); padding: 10px 16px; margin-bottom: 12px;">
948
- <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>
949
1004
  <span style="color: var(--text-secondary); font-size: 0.9rem; font-family: monospace;">${platform.id}</span>
950
1005
  </div>
951
1006
  <h3 style="color: var(--text-secondary); margin-bottom: 10px; font-size: 1rem;">
952
- Installed Skills (${skills.length})
1007
+ ${t('platform.installedSkills', { count: skills.length })}
953
1008
  </h3>
954
- ${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>` : `
955
1010
  <div style="display: flex; flex-direction: column; gap: 6px;">
956
1011
  ${skills.map((skill, i) => `
957
1012
  <div class="admin-skill-row" style="cursor: pointer;" onclick="showSkillDetail('${skill.id}')">
@@ -1035,9 +1090,20 @@ skm publish &lt;skill-name&gt; --version 1.0.1</pre>
1035
1090
  </div>
1036
1091
  </div>
1037
1092
 
1093
+ <div class="help-section">
1094
+ <h3>⚙️ 配置管理</h3>
1095
+ <p>使用 <code>skm config</code> 命令可查看和修改配置(持久化到 <code>~/.skillmarket/config.json</code>):</p>
1096
+ <pre>skm config # 查看所有配置
1097
+ skm config get &lt;key&gt; # 查看指定配置
1098
+ skm config set &lt;key&gt; &lt;value&gt; # 设置配置值
1099
+ skm config reset &lt;key&gt; # 恢复默认值
1100
+ skm config reset --all # 全部恢复默认</pre>
1101
+ <p class="help-note">⚠ 环境变量优先级高于配置文件。如需环境变量生效,unset 后可重开终端。</p>
1102
+ </div>
1103
+
1038
1104
  <div class="help-section">
1039
1105
  <h3>⚙️ 环境变量配置</h3>
1040
- <p>设置以下环境变量可覆盖默认配置:</p>
1106
+ <p>设置以下环境变量可覆盖配置文件和默认值(最高优先级):</p>
1041
1107
  <table class="help-table">
1042
1108
  <thead>
1043
1109
  <tr><th>变量</th><th>当前值</th><th>说明</th></tr>
@@ -1061,14 +1127,32 @@ skm publish &lt;skill-name&gt; --version 1.0.1</pre>
1061
1127
  <tbody>
1062
1128
  <tr><td><code>skm ls</code></td><td>列出可用 skills</td></tr>
1063
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>
1064
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>
1065
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>
1066
1137
  <tr><td><code>skm uninstall &lt;skill&gt;</code></td><td>卸载 skill</td></tr>
1067
- <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>
1068
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>
1069
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>
1070
1146
  <tr><td><code>skm gui</code></td><td>启动图形界面</td></tr>
1071
1147
  <tr><td><code>skm gui 18790</code></td><td>指定端口启动 GUI</td></tr>
1148
+ <tr><td><code>skm config</code></td><td>查看所有配置项</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>
1072
1156
  </tbody>
1073
1157
  </table>
1074
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.23",
3
+ "version": "1.3.25",
4
4
  "description": "Cross-platform skill manager for AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {