itismyskillmarket 1.3.23 → 1.3.24

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);
@@ -2604,6 +2836,10 @@ Commands:
2604
2836
  --all Update all skills
2605
2837
  sync Synchronize platform links
2606
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
2607
2843
 
2608
2844
  Examples:
2609
2845
  skm ls List all available skills (page 1)
@@ -2623,6 +2859,11 @@ Examples:
2623
2859
  skm uninstall --all --yes Force uninstall all without confirmation
2624
2860
  skm uninstall brainstorming --dry-run Preview uninstall
2625
2861
  skm platforms Show available platforms
2862
+ skm config View all configuration
2863
+ skm config get npmRegistry View specific config
2864
+ 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
2626
2867
  `);
2627
2868
  process.exit(0);
2628
2869
  }
@@ -2761,6 +3002,22 @@ program.command("verify <skill>").description("Verify skill integrity and format
2761
3002
  process.exit(1);
2762
3003
  }
2763
3004
  });
3005
+ var config = program.command("config").description("View and manage configuration");
3006
+ config.command("list").alias("ls").description("List all configuration values").action(async () => {
3007
+ await listConfig();
3008
+ });
3009
+ config.action(async () => {
3010
+ await listConfig();
3011
+ });
3012
+ config.command("get <key>").description("Get a specific configuration value").action(async (key) => {
3013
+ await getConfigValue(key);
3014
+ });
3015
+ config.command("set <key> <value>").description("Set a configuration value (persisted to config file)").action(async (key, value) => {
3016
+ await setConfigValue(key, value);
3017
+ });
3018
+ config.command("reset [key]").description("Reset configuration to default values").option("--all", "Reset all configuration values").action(async (key, opts) => {
3019
+ await resetConfig(key, opts.all ?? false);
3020
+ });
2764
3021
  var admin = program.command("admin").description("Admin: manage published skills (cloud)");
2765
3022
  admin.command("ls").description("List all published skills").action(async () => {
2766
3023
  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',
@@ -1035,9 +1046,20 @@ skm publish &lt;skill-name&gt; --version 1.0.1</pre>
1035
1046
  </div>
1036
1047
  </div>
1037
1048
 
1049
+ <div class="help-section">
1050
+ <h3>⚙️ 配置管理</h3>
1051
+ <p>使用 <code>skm config</code> 命令可查看和修改配置(持久化到 <code>~/.skillmarket/config.json</code>):</p>
1052
+ <pre>skm config # 查看所有配置
1053
+ skm config get &lt;key&gt; # 查看指定配置
1054
+ skm config set &lt;key&gt; &lt;value&gt; # 设置配置值
1055
+ skm config reset &lt;key&gt; # 恢复默认值
1056
+ skm config reset --all # 全部恢复默认</pre>
1057
+ <p class="help-note">⚠ 环境变量优先级高于配置文件。如需环境变量生效,unset 后可重开终端。</p>
1058
+ </div>
1059
+
1038
1060
  <div class="help-section">
1039
1061
  <h3>⚙️ 环境变量配置</h3>
1040
- <p>设置以下环境变量可覆盖默认配置:</p>
1062
+ <p>设置以下环境变量可覆盖配置文件和默认值(最高优先级):</p>
1041
1063
  <table class="help-table">
1042
1064
  <thead>
1043
1065
  <tr><th>变量</th><th>当前值</th><th>说明</th></tr>
@@ -1069,6 +1091,8 @@ skm publish &lt;skill-name&gt; --version 1.0.1</pre>
1069
1091
  <tr><td><code>skm platforms</code></td><td>查看可用平台</td></tr>
1070
1092
  <tr><td><code>skm gui</code></td><td>启动图形界面</td></tr>
1071
1093
  <tr><td><code>skm gui 18790</code></td><td>指定端口启动 GUI</td></tr>
1094
+ <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>
1072
1096
  </tbody>
1073
1097
  </table>
1074
1098
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itismyskillmarket",
3
- "version": "1.3.23",
3
+ "version": "1.3.24",
4
4
  "description": "Cross-platform skill manager for AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {