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 +27 -2
- package/dist/index.js +380 -96
- package/gui/app.js +95 -11
- package/gui/index.html +2 -9
- 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);
|
|
@@ -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 =
|
|
2274
|
-
const pkg = JSON.parse(
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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(
|
|
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]
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
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
|
|
2610
|
-
skm ls --page 2
|
|
2611
|
-
skm ls --
|
|
2612
|
-
skm ls
|
|
2613
|
-
skm
|
|
2614
|
-
skm
|
|
2615
|
-
skm
|
|
2616
|
-
skm
|
|
2617
|
-
skm
|
|
2618
|
-
skm install brainstorming Install to
|
|
2619
|
-
skm
|
|
2620
|
-
skm
|
|
2621
|
-
skm uninstall
|
|
2622
|
-
skm uninstall --
|
|
2623
|
-
skm
|
|
2624
|
-
skm
|
|
2625
|
-
skm
|
|
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
|
-
|
|
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=""
|
|
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
|
|
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()"
|
|
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>` :
|
|
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;"
|
|
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
|
-
|
|
1007
|
+
${t('platform.installedSkills', { count: skills.length })}
|
|
953
1008
|
</h3>
|
|
954
|
-
${skills.length === 0 ?
|
|
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 <skill-name> --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 <key> # 查看指定配置
|
|
1098
|
+
skm config set <key> <value> # 设置配置值
|
|
1099
|
+
skm config reset <key> # 恢复默认值
|
|
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
|
|
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 <skill-name> --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 <keyword></code></td><td>搜索 skills</td></tr>
|
|
1131
|
+
<tr><td><code>skm info <skill></code></td><td>查看 skill 详情</td></tr>
|
|
1064
1132
|
<tr><td><code>skm install <skill></code></td><td>安装 skill 到所有平台</td></tr>
|
|
1133
|
+
<tr><td><code>skm install <skill>@<ver></code></td><td>安装指定版本</td></tr>
|
|
1065
1134
|
<tr><td><code>skm install <skill> --platform opencode</code></td><td>安装到指定平台</td></tr>
|
|
1135
|
+
<tr><td><code>skm install <skill> --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 <skill></code></td><td>卸载 skill</td></tr>
|
|
1067
|
-
<tr><td><code>skm
|
|
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 <skill></code></td><td>发布 skill 到 npm</td></tr>
|
|
1142
|
+
<tr><td><code>skm verify <skill></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 <skill></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 <key></code></td><td>查看指定配置</td></tr>
|
|
1150
|
+
<tr><td><code>skm config set <key> <value></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 <skill></code></td><td>查看已发布 skill 详情</td></tr>
|
|
1154
|
+
<tr><td><code>skm admin deprecate <skill></code></td><td>废弃 skill</td></tr>
|
|
1155
|
+
<tr><td><code>skm admin unpublish <skill></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
|
-
|
|
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>
|