itismyskillmarket 1.3.22 → 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 +27 -2
- package/dist/index.js +319 -60
- package/gui/app.js +42 -1
- package/gui/style.css +48 -0
- package/package.json +1 -1
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
|
-
##
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
119
|
-
var
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
570
|
+
return path5.join(os5.homedir(), ".copilot", "skills");
|
|
555
571
|
}
|
|
556
572
|
async isAvailable() {
|
|
557
573
|
const possibleDirs = [
|
|
558
|
-
path5.join(
|
|
559
|
-
path5.join(
|
|
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(
|
|
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 =
|
|
609
|
+
skillDir = join2(homedir(), ".openclaw", "skills");
|
|
594
610
|
async isAvailable() {
|
|
595
611
|
try {
|
|
596
|
-
return
|
|
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 =
|
|
604
|
-
return
|
|
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 =
|
|
612
|
-
if (
|
|
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 =
|
|
619
|
-
if (
|
|
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 (!
|
|
641
|
+
if (!existsSync2(this.skillDir)) {
|
|
626
642
|
return [];
|
|
627
643
|
}
|
|
628
644
|
return readdirSync(this.skillDir).filter((name) => {
|
|
629
|
-
const fullPath =
|
|
630
|
-
return
|
|
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
|
|
640
|
-
import { join as
|
|
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 =
|
|
662
|
+
skillDir = join3(homedir2(), ".hermes", "skills");
|
|
647
663
|
async isAvailable() {
|
|
648
664
|
try {
|
|
649
|
-
if (
|
|
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 =
|
|
660
|
-
return
|
|
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 =
|
|
668
|
-
if (
|
|
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 =
|
|
675
|
-
if (
|
|
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 (!
|
|
697
|
+
if (!existsSync3(this.skillDir)) {
|
|
682
698
|
return [];
|
|
683
699
|
}
|
|
684
700
|
return readdirSync2(this.skillDir).filter((name) => {
|
|
685
|
-
const fullPath =
|
|
686
|
-
return
|
|
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
|
|
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:
|
|
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
|
|
1407
|
-
import { join as
|
|
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 =
|
|
1412
|
-
const skillDir =
|
|
1427
|
+
const projectRoot = join4(__dirname4, "..", "..");
|
|
1428
|
+
const skillDir = join4(projectRoot, "skills", skillName);
|
|
1413
1429
|
console.log(`Publishing ${skillName}...`);
|
|
1414
|
-
if (!
|
|
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
|
|
1549
|
-
import { join as
|
|
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 =
|
|
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);
|
|
@@ -2151,7 +2167,9 @@ API_ROUTES.GET["/api/skills"] = async (_req, res, url) => {
|
|
|
2151
2167
|
});
|
|
2152
2168
|
const filteredTotal = skills.length;
|
|
2153
2169
|
const totalPages = Math.ceil(filteredTotal / limit) || 1;
|
|
2154
|
-
|
|
2170
|
+
const start = (page - 1) * limit;
|
|
2171
|
+
const pagedSkills = skills.slice(start, start + limit);
|
|
2172
|
+
jsonResponse(res, 200, { skills: pagedSkills, page, totalPages, total: filteredTotal, fetchErrors });
|
|
2155
2173
|
} catch (err) {
|
|
2156
2174
|
jsonResponse(res, 500, {
|
|
2157
2175
|
error: String(err),
|
|
@@ -2268,8 +2286,8 @@ API_ROUTES.GET["/api/skill-info"] = async (_req, res, url) => {
|
|
|
2268
2286
|
};
|
|
2269
2287
|
API_ROUTES.GET["/api/version"] = async (_req, res, _url) => {
|
|
2270
2288
|
try {
|
|
2271
|
-
const pkgPath =
|
|
2272
|
-
const pkg = JSON.parse(
|
|
2289
|
+
const pkgPath = join5(__dirname2, "..", "package.json");
|
|
2290
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
2273
2291
|
jsonResponse(res, 200, { version: pkg.version || "1.0.0" });
|
|
2274
2292
|
} catch {
|
|
2275
2293
|
jsonResponse(res, 200, { version: "1.0.0" });
|
|
@@ -2506,12 +2524,12 @@ API_ROUTES.POST["/api/update"] = async (req, res, _url) => {
|
|
|
2506
2524
|
}
|
|
2507
2525
|
};
|
|
2508
2526
|
function serveStaticFile(res, filePath) {
|
|
2509
|
-
if (!
|
|
2527
|
+
if (!existsSync5(filePath)) {
|
|
2510
2528
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
2511
2529
|
res.end("Not Found");
|
|
2512
2530
|
return;
|
|
2513
2531
|
}
|
|
2514
|
-
const content =
|
|
2532
|
+
const content = readFileSync2(filePath);
|
|
2515
2533
|
const ext = extname(filePath);
|
|
2516
2534
|
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
2517
2535
|
res.writeHead(200, {
|
|
@@ -2539,7 +2557,7 @@ async function handleRequest(req, res) {
|
|
|
2539
2557
|
jsonResponse(res, 404, { error: `Unknown API endpoint: ${method} ${pathname}` });
|
|
2540
2558
|
return;
|
|
2541
2559
|
}
|
|
2542
|
-
const filePath =
|
|
2560
|
+
const filePath = join5(guiDir, pathname === "/" ? "index.html" : pathname);
|
|
2543
2561
|
if (filePath.startsWith(guiDir)) {
|
|
2544
2562
|
serveStaticFile(res, filePath);
|
|
2545
2563
|
} else {
|
|
@@ -2567,10 +2585,226 @@ Press Ctrl+C to stop
|
|
|
2567
2585
|
});
|
|
2568
2586
|
}
|
|
2569
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
|
+
|
|
2570
2804
|
// src/cli.ts
|
|
2571
2805
|
var __filename3 = fileURLToPath4(import.meta.url);
|
|
2572
2806
|
var __dirname3 = dirname2(__filename3);
|
|
2573
|
-
var packageJson = JSON.parse(
|
|
2807
|
+
var packageJson = JSON.parse(readFileSync3(resolve(__dirname3, "../package.json"), "utf-8"));
|
|
2574
2808
|
var VERSION = packageJson.version || "1.3.1";
|
|
2575
2809
|
var program = new Command();
|
|
2576
2810
|
program.name("skm").description("SkillMarket - Cross-platform skill manager for AI coding tools").version(VERSION);
|
|
@@ -2602,6 +2836,10 @@ Commands:
|
|
|
2602
2836
|
--all Update all skills
|
|
2603
2837
|
sync Synchronize platform links
|
|
2604
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
|
|
2605
2843
|
|
|
2606
2844
|
Examples:
|
|
2607
2845
|
skm ls List all available skills (page 1)
|
|
@@ -2621,6 +2859,11 @@ Examples:
|
|
|
2621
2859
|
skm uninstall --all --yes Force uninstall all without confirmation
|
|
2622
2860
|
skm uninstall brainstorming --dry-run Preview uninstall
|
|
2623
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
|
|
2624
2867
|
`);
|
|
2625
2868
|
process.exit(0);
|
|
2626
2869
|
}
|
|
@@ -2759,6 +3002,22 @@ program.command("verify <skill>").description("Verify skill integrity and format
|
|
|
2759
3002
|
process.exit(1);
|
|
2760
3003
|
}
|
|
2761
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
|
+
});
|
|
2762
3021
|
var admin = program.command("admin").description("Admin: manage published skills (cloud)");
|
|
2763
3022
|
admin.command("ls").description("List all published skills").action(async () => {
|
|
2764
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',
|
|
@@ -832,6 +843,12 @@ function renderPagination(currentPage, totalPages) {
|
|
|
832
843
|
let html = `
|
|
833
844
|
<button ${currentPage <= 1 ? 'disabled' : ''} onclick="changePage(${currentPage - 1})">${t('pagination.prev')}</button>
|
|
834
845
|
<span class="page-info">${t('pagination.pageInfo', { page: currentPage, totalPages: totalPages })}</span>
|
|
846
|
+
<span class="page-jump">
|
|
847
|
+
<label>Go to</label>
|
|
848
|
+
<input type="number" id="page-jump-input" min="1" max="${totalPages}" value="${currentPage}"
|
|
849
|
+
onkeydown="if(event.key==='Enter')jumpToPage()">
|
|
850
|
+
<button onclick="jumpToPage()">Go</button>
|
|
851
|
+
</span>
|
|
835
852
|
<button ${currentPage >= totalPages ? 'disabled' : ''} onclick="changePage(${currentPage + 1})">${t('pagination.next')}</button>
|
|
836
853
|
`;
|
|
837
854
|
|
|
@@ -844,6 +861,17 @@ function changePage(page) {
|
|
|
844
861
|
loadSkills();
|
|
845
862
|
}
|
|
846
863
|
|
|
864
|
+
function jumpToPage() {
|
|
865
|
+
const input = document.getElementById('page-jump-input');
|
|
866
|
+
if (!input) return;
|
|
867
|
+
const page = parseInt(input.value);
|
|
868
|
+
if (isNaN(page) || page < 1 || page > state.totalPages) {
|
|
869
|
+
input.value = state.currentPage;
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
changePage(page);
|
|
873
|
+
}
|
|
874
|
+
|
|
847
875
|
// -----------------------------------------------------------------------------
|
|
848
876
|
// Platforms
|
|
849
877
|
// -----------------------------------------------------------------------------
|
|
@@ -1018,9 +1046,20 @@ skm publish <skill-name> --version 1.0.1</pre>
|
|
|
1018
1046
|
</div>
|
|
1019
1047
|
</div>
|
|
1020
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 <key> # 查看指定配置
|
|
1054
|
+
skm config set <key> <value> # 设置配置值
|
|
1055
|
+
skm config reset <key> # 恢复默认值
|
|
1056
|
+
skm config reset --all # 全部恢复默认</pre>
|
|
1057
|
+
<p class="help-note">⚠ 环境变量优先级高于配置文件。如需环境变量生效,unset 后可重开终端。</p>
|
|
1058
|
+
</div>
|
|
1059
|
+
|
|
1021
1060
|
<div class="help-section">
|
|
1022
1061
|
<h3>⚙️ 环境变量配置</h3>
|
|
1023
|
-
<p
|
|
1062
|
+
<p>设置以下环境变量可覆盖配置文件和默认值(最高优先级):</p>
|
|
1024
1063
|
<table class="help-table">
|
|
1025
1064
|
<thead>
|
|
1026
1065
|
<tr><th>变量</th><th>当前值</th><th>说明</th></tr>
|
|
@@ -1052,6 +1091,8 @@ skm publish <skill-name> --version 1.0.1</pre>
|
|
|
1052
1091
|
<tr><td><code>skm platforms</code></td><td>查看可用平台</td></tr>
|
|
1053
1092
|
<tr><td><code>skm gui</code></td><td>启动图形界面</td></tr>
|
|
1054
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>
|
|
1055
1096
|
</tbody>
|
|
1056
1097
|
</table>
|
|
1057
1098
|
</div>
|
package/gui/style.css
CHANGED
|
@@ -366,6 +366,54 @@ body {
|
|
|
366
366
|
font-size: 0.85rem;
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
.pagination .page-jump {
|
|
370
|
+
display: flex;
|
|
371
|
+
align-items: center;
|
|
372
|
+
gap: 4px;
|
|
373
|
+
color: var(--text-muted);
|
|
374
|
+
font-size: 0.85rem;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.pagination .page-jump input {
|
|
378
|
+
width: 50px;
|
|
379
|
+
padding: 5px 6px;
|
|
380
|
+
background: var(--bg-secondary);
|
|
381
|
+
border: 1px solid var(--border-color);
|
|
382
|
+
color: var(--text-secondary);
|
|
383
|
+
border-radius: 4px;
|
|
384
|
+
font-size: 0.85rem;
|
|
385
|
+
text-align: center;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.pagination .page-jump input:focus {
|
|
389
|
+
outline: none;
|
|
390
|
+
border-color: var(--accent);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.pagination .page-jump button {
|
|
394
|
+
padding: 5px 10px;
|
|
395
|
+
background: var(--bg-card);
|
|
396
|
+
border: 1px solid var(--border-color);
|
|
397
|
+
color: var(--text-secondary);
|
|
398
|
+
border-radius: 4px;
|
|
399
|
+
cursor: pointer;
|
|
400
|
+
font-size: 0.8rem;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.pagination .page-jump button:hover {
|
|
404
|
+
background: var(--bg-hover);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* Hide spinner for number input */
|
|
408
|
+
.pagination .page-jump input::-webkit-outer-spin-button,
|
|
409
|
+
.pagination .page-jump input::-webkit-inner-spin-button {
|
|
410
|
+
-webkit-appearance: none;
|
|
411
|
+
margin: 0;
|
|
412
|
+
}
|
|
413
|
+
.pagination .page-jump input[type=number] {
|
|
414
|
+
-moz-appearance: textfield;
|
|
415
|
+
}
|
|
416
|
+
|
|
369
417
|
/* -----------------------------------------------------------------------------
|
|
370
418
|
Skill 详情视图
|
|
371
419
|
----------------------------------------------------------------------------- */
|