itismyskillmarket 1.3.40 → 1.3.41

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.
@@ -1596,6 +1596,222 @@ async function adminAccess(skillId, level) {
1596
1596
  `);
1597
1597
  }
1598
1598
 
1599
+ // src/commands/config.ts
1600
+ import path11 from "path";
1601
+ import fs12 from "fs-extra";
1602
+ import os9 from "os";
1603
+ var CONFIG_DEFINITIONS = [
1604
+ {
1605
+ key: "npmScope",
1606
+ envVar: "SKM_NPM_SCOPE",
1607
+ defaultValue: "@itismyskillmarket",
1608
+ description: "Primary npm scope for publishing and lookup"
1609
+ },
1610
+ {
1611
+ key: "npmScopeFallback",
1612
+ envVar: "SKM_NPM_SCOPE_FALLBACK",
1613
+ defaultValue: "@wanxuchen",
1614
+ description: "Fallback npm scope (backward compatibility)"
1615
+ },
1616
+ {
1617
+ key: "npmRegistry",
1618
+ envVar: "SKM_NPM_REGISTRY",
1619
+ defaultValue: "https://registry.npmjs.org",
1620
+ description: "npm registry URL (mirror/proxy support)"
1621
+ },
1622
+ {
1623
+ key: "npmScopes",
1624
+ envVar: "SKM_NPM_SCOPES",
1625
+ defaultValue: "@itismyskillmarket,@wanxuchen,@thisisskillmarket,@this-is-skillmarket,@skillmarket",
1626
+ description: "Comma-separated list of npm scopes to search"
1627
+ },
1628
+ {
1629
+ key: "skmUrl",
1630
+ envVar: "SKM_URL",
1631
+ defaultValue: "https://www.npmjs.com/package/@itismyskillmarket",
1632
+ description: "Base URL for skill links (publish output)"
1633
+ }
1634
+ ];
1635
+ function getConfigPath() {
1636
+ return path11.join(os9.homedir(), ".skillmarket", "config.json");
1637
+ }
1638
+ async function readConfigFile() {
1639
+ try {
1640
+ const configPath = getConfigPath();
1641
+ if (await fs12.pathExists(configPath)) {
1642
+ const data = await fs12.readJson(configPath);
1643
+ const valid = {};
1644
+ for (const def of CONFIG_DEFINITIONS) {
1645
+ if (data[def.key] !== void 0) {
1646
+ valid[def.key] = String(data[def.key]);
1647
+ }
1648
+ }
1649
+ return valid;
1650
+ }
1651
+ } catch {
1652
+ }
1653
+ return {};
1654
+ }
1655
+ async function writeConfigFile(updates) {
1656
+ const configPath = getConfigPath();
1657
+ await fs12.ensureDir(path11.dirname(configPath));
1658
+ let existing = {};
1659
+ try {
1660
+ if (await fs12.pathExists(configPath)) {
1661
+ existing = await fs12.readJson(configPath);
1662
+ }
1663
+ } catch {
1664
+ }
1665
+ const merged = { ...existing, ...updates };
1666
+ for (const key of Object.keys(merged)) {
1667
+ if (merged[key] === void 0) {
1668
+ delete merged[key];
1669
+ }
1670
+ }
1671
+ await fs12.writeJson(configPath, merged, { spaces: 2 });
1672
+ return merged;
1673
+ }
1674
+ async function removeConfigKeys(keys) {
1675
+ const configPath = getConfigPath();
1676
+ if (!await fs12.pathExists(configPath)) return;
1677
+ try {
1678
+ const existing = await fs12.readJson(configPath);
1679
+ for (const key of keys) {
1680
+ delete existing[key];
1681
+ }
1682
+ await fs12.writeJson(configPath, existing, { spaces: 2 });
1683
+ } catch {
1684
+ }
1685
+ }
1686
+ async function removeConfigFile() {
1687
+ const configPath = getConfigPath();
1688
+ if (await fs12.pathExists(configPath)) {
1689
+ await fs12.remove(configPath);
1690
+ }
1691
+ }
1692
+ async function getAllConfig() {
1693
+ const fileConfig2 = await readConfigFile();
1694
+ return CONFIG_DEFINITIONS.map((def) => {
1695
+ const envValue = process.env[def.envVar];
1696
+ if (envValue !== void 0) {
1697
+ return {
1698
+ ...def,
1699
+ value: envValue,
1700
+ source: "env"
1701
+ };
1702
+ }
1703
+ const fileValue = fileConfig2[def.key];
1704
+ if (fileValue !== void 0) {
1705
+ return {
1706
+ ...def,
1707
+ value: fileValue,
1708
+ source: "file"
1709
+ };
1710
+ }
1711
+ return {
1712
+ ...def,
1713
+ value: def.defaultValue,
1714
+ source: "default"
1715
+ };
1716
+ });
1717
+ }
1718
+ async function getConfig(key) {
1719
+ const all = await getAllConfig();
1720
+ return all.find((c) => c.key === key) || null;
1721
+ }
1722
+ async function listConfig() {
1723
+ const entries = await getAllConfig();
1724
+ console.log("\n\u{1F527} SkillMarket Configuration\n");
1725
+ const maxKeyLen = Math.max(...entries.map((e) => e.key.length));
1726
+ for (const entry of entries) {
1727
+ const key = entry.key.padEnd(maxKeyLen + 2);
1728
+ const sourceBadge = getSourceBadge(entry.source);
1729
+ console.log(` ${sourceBadge} ${key}${entry.value}`);
1730
+ }
1731
+ console.log("");
1732
+ console.log(" \u6765\u6E90: \u{1F535} \u73AF\u5883\u53D8\u91CF \u{1F7E2} \u914D\u7F6E\u6587\u4EF6 \u26AA \u9ED8\u8BA4\u503C");
1733
+ console.log(" \u914D\u7F6E\u6587\u4EF6: " + getConfigPath());
1734
+ console.log("");
1735
+ console.log(" \u7528\u6CD5:");
1736
+ console.log(" skm config get <key> \u67E5\u770B\u914D\u7F6E\u503C");
1737
+ console.log(" skm config set <key> <v> \u8BBE\u7F6E\u914D\u7F6E\u503C");
1738
+ console.log(" skm config reset <key> \u91CD\u7F6E\u4E3A\u9ED8\u8BA4\u503C");
1739
+ console.log(" skm config reset --all \u5168\u90E8\u91CD\u7F6E");
1740
+ console.log("");
1741
+ }
1742
+ function getSourceBadge(source) {
1743
+ switch (source) {
1744
+ case "env":
1745
+ return "\u{1F535}";
1746
+ case "file":
1747
+ return "\u{1F7E2}";
1748
+ case "default":
1749
+ return "\u26AA";
1750
+ }
1751
+ }
1752
+ async function getConfigValue(key) {
1753
+ const entry = await getConfig(key);
1754
+ if (!entry) {
1755
+ console.error(`\u274C Unknown config key: "${key}"`);
1756
+ console.log(` Valid keys: ${CONFIG_DEFINITIONS.map((d) => d.key).join(", ")}`);
1757
+ process.exit(1);
1758
+ }
1759
+ console.log(`
1760
+ \u{1F527} ${entry.key}`);
1761
+ console.log(` Value: ${entry.value}`);
1762
+ console.log(` Source: ${entry.source}`);
1763
+ console.log(` Env var: ${entry.envVar}`);
1764
+ console.log(` Default: ${entry.defaultValue}`);
1765
+ console.log(` Description: ${entry.description}`);
1766
+ console.log("");
1767
+ }
1768
+ async function setConfigValue(key, value) {
1769
+ const def = CONFIG_DEFINITIONS.find((d) => d.key === key);
1770
+ if (!def) {
1771
+ console.error(`\u274C Unknown config key: "${key}"`);
1772
+ console.log(` Valid keys: ${CONFIG_DEFINITIONS.map((d) => d.key).join(", ")}`);
1773
+ process.exit(1);
1774
+ }
1775
+ await writeConfigFile({ [key]: value });
1776
+ console.log(`
1777
+ \u2705 ${key} set to "${value}"`);
1778
+ console.log(` Stored in: ${getConfigPath()}`);
1779
+ console.log("");
1780
+ if (process.env[def.envVar] !== void 0) {
1781
+ console.log(` \u26A0\uFE0F Currently overridden by environment variable ${def.envVar}=${process.env[def.envVar]}`);
1782
+ console.log(` To use the config file value, unset the environment variable.`);
1783
+ console.log("");
1784
+ }
1785
+ }
1786
+ async function resetConfig(key, all = false) {
1787
+ if (all) {
1788
+ await removeConfigFile();
1789
+ console.log("\n\u2705 All configuration reset to defaults.");
1790
+ console.log(` Removed: ${getConfigPath()}`);
1791
+ console.log("");
1792
+ return;
1793
+ }
1794
+ if (key) {
1795
+ const def = CONFIG_DEFINITIONS.find((d) => d.key === key);
1796
+ if (!def) {
1797
+ console.error(`\u274C Unknown config key: "${key}"`);
1798
+ console.log(` Valid keys: ${CONFIG_DEFINITIONS.map((d) => d.key).join(", ")}`);
1799
+ process.exit(1);
1800
+ }
1801
+ await removeConfigKeys([key]);
1802
+ const sourceNow = process.env[def.envVar] ? "env" : "default";
1803
+ console.log(`
1804
+ \u2705 ${key} reset to ${sourceNow === "env" ? "environment variable" : "default"} value.`);
1805
+ console.log(` Effective value: "${sourceNow === "env" ? process.env[def.envVar] : def.defaultValue}"`);
1806
+ console.log("");
1807
+ return;
1808
+ }
1809
+ console.log("\n\u{1F527} Usage: skm config reset <key>");
1810
+ console.log(" skm config reset --all");
1811
+ console.log(` Valid keys: ${CONFIG_DEFINITIONS.map((d) => d.key).join(", ")}`);
1812
+ console.log("");
1813
+ }
1814
+
1599
1815
  // src/commands/ui.ts
1600
1816
  var MAX_UPLOAD_SIZE = 50 * 1024 * 1024;
1601
1817
  var __filename = fileURLToPath2(import.meta.url);
@@ -1661,7 +1877,8 @@ function parseBody(req) {
1661
1877
  }
1662
1878
  var API_ROUTES = {
1663
1879
  GET: {},
1664
- POST: {}
1880
+ POST: {},
1881
+ DELETE: {}
1665
1882
  };
1666
1883
  API_ROUTES.GET["/api/skills"] = async (_req, res, url) => {
1667
1884
  try {
@@ -1873,6 +2090,40 @@ API_ROUTES.GET["/api/config"] = async (_req, res, _url) => {
1873
2090
  skillScopes: SKILL_SCOPES
1874
2091
  });
1875
2092
  };
2093
+ API_ROUTES.GET["/api/github-token"] = async (_req, res, _url) => {
2094
+ try {
2095
+ const config = await readConfigFile();
2096
+ const token = config.githubToken || "";
2097
+ jsonResponse(res, 200, {
2098
+ hasToken: !!token,
2099
+ tokenPrefix: token ? token.substring(0, 6) + "\u2026" : ""
2100
+ });
2101
+ } catch (err) {
2102
+ jsonResponse(res, 500, { error: String(err) });
2103
+ }
2104
+ };
2105
+ API_ROUTES.POST["/api/github-token"] = async (req, res, _url) => {
2106
+ try {
2107
+ const body = await parseBody(req);
2108
+ const token = String(body.token || "").trim();
2109
+ if (!token) {
2110
+ jsonResponse(res, 400, { error: "Token is required" });
2111
+ return;
2112
+ }
2113
+ await writeConfigFile({ githubToken: token });
2114
+ jsonResponse(res, 200, { success: true, message: "GitHub token saved successfully" });
2115
+ } catch (err) {
2116
+ jsonResponse(res, 500, { error: String(err) });
2117
+ }
2118
+ };
2119
+ API_ROUTES.DELETE["/api/github-token"] = async (_req, res, _url) => {
2120
+ try {
2121
+ await removeConfigKeys(["githubToken"]);
2122
+ jsonResponse(res, 200, { success: true, message: "GitHub token removed successfully" });
2123
+ } catch (err) {
2124
+ jsonResponse(res, 500, { error: String(err) });
2125
+ }
2126
+ };
1876
2127
  API_ROUTES.POST["/api/install"] = async (req, res, _url) => {
1877
2128
  try {
1878
2129
  const body = await parseBody(req);
@@ -2345,5 +2596,9 @@ export {
2345
2596
  adminOwnerAdd,
2346
2597
  adminOwnerRemove,
2347
2598
  adminAccess,
2599
+ listConfig,
2600
+ getConfigValue,
2601
+ setConfigValue,
2602
+ resetConfig,
2348
2603
  startGuiServer
2349
2604
  };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startGuiServer
4
- } from "./chunk-VRXNOGLL.js";
4
+ } from "./chunk-XQ653HCI.js";
5
5
  export {
6
6
  startGuiServer
7
7
  };
package/dist/index.js CHANGED
@@ -21,20 +21,24 @@ import {
21
21
  fetchSkillPackage,
22
22
  getAdapterByPlatform,
23
23
  getAllAdapters,
24
+ getConfigValue,
24
25
  getInstalledSkills,
25
26
  getPlatformLinksDir,
26
27
  getSkillsDir,
27
28
  installSkill,
28
29
  isSkillInstalled,
30
+ listConfig,
29
31
  loadRegistry,
30
32
  publishSkill,
33
+ resetConfig,
31
34
  saveRegistry,
32
35
  searchSkillmarketPackages,
36
+ setConfigValue,
33
37
  startGuiServer,
34
38
  uninstallAll,
35
39
  uninstallSkill,
36
40
  updateSkill
37
- } from "./chunk-VRXNOGLL.js";
41
+ } from "./chunk-XQ653HCI.js";
38
42
 
39
43
  // src/cli.ts
40
44
  import { Command } from "commander";
@@ -303,13 +307,13 @@ function parseGitHubUrl(input) {
303
307
  const repo = match[2].replace(/\.git$/, "");
304
308
  const branch = match[3] || "main";
305
309
  const commitOrPath = match[4] || match[3];
306
- const path5 = match[4] || void 0;
310
+ const path4 = match[4] || void 0;
307
311
  return {
308
312
  owner,
309
313
  repo,
310
314
  branch: commitOrPath && !commitOrPath.includes("/") ? commitOrPath : branch,
311
315
  commit: commitOrPath?.match(/^[0-9a-f]{40}$/) ? commitOrPath : void 0,
312
- path: path5
316
+ path: path4
313
317
  };
314
318
  }
315
319
  }
@@ -641,222 +645,6 @@ async function verifySkill(skillName) {
641
645
  }
642
646
  }
643
647
 
644
- // src/commands/config.ts
645
- import path4 from "path";
646
- import fs4 from "fs-extra";
647
- import os from "os";
648
- var CONFIG_DEFINITIONS = [
649
- {
650
- key: "npmScope",
651
- envVar: "SKM_NPM_SCOPE",
652
- defaultValue: "@itismyskillmarket",
653
- description: "Primary npm scope for publishing and lookup"
654
- },
655
- {
656
- key: "npmScopeFallback",
657
- envVar: "SKM_NPM_SCOPE_FALLBACK",
658
- defaultValue: "@wanxuchen",
659
- description: "Fallback npm scope (backward compatibility)"
660
- },
661
- {
662
- key: "npmRegistry",
663
- envVar: "SKM_NPM_REGISTRY",
664
- defaultValue: "https://registry.npmjs.org",
665
- description: "npm registry URL (mirror/proxy support)"
666
- },
667
- {
668
- key: "npmScopes",
669
- envVar: "SKM_NPM_SCOPES",
670
- defaultValue: "@itismyskillmarket,@wanxuchen,@thisisskillmarket,@this-is-skillmarket,@skillmarket",
671
- description: "Comma-separated list of npm scopes to search"
672
- },
673
- {
674
- key: "skmUrl",
675
- envVar: "SKM_URL",
676
- defaultValue: "https://www.npmjs.com/package/@itismyskillmarket",
677
- description: "Base URL for skill links (publish output)"
678
- }
679
- ];
680
- function getConfigPath() {
681
- return path4.join(os.homedir(), ".skillmarket", "config.json");
682
- }
683
- async function readConfigFile() {
684
- try {
685
- const configPath = getConfigPath();
686
- if (await fs4.pathExists(configPath)) {
687
- const data = await fs4.readJson(configPath);
688
- const valid = {};
689
- for (const def of CONFIG_DEFINITIONS) {
690
- if (data[def.key] !== void 0) {
691
- valid[def.key] = String(data[def.key]);
692
- }
693
- }
694
- return valid;
695
- }
696
- } catch {
697
- }
698
- return {};
699
- }
700
- async function writeConfigFile(updates) {
701
- const configPath = getConfigPath();
702
- await fs4.ensureDir(path4.dirname(configPath));
703
- let existing = {};
704
- try {
705
- if (await fs4.pathExists(configPath)) {
706
- existing = await fs4.readJson(configPath);
707
- }
708
- } catch {
709
- }
710
- const merged = { ...existing, ...updates };
711
- for (const key of Object.keys(merged)) {
712
- if (merged[key] === void 0) {
713
- delete merged[key];
714
- }
715
- }
716
- await fs4.writeJson(configPath, merged, { spaces: 2 });
717
- return merged;
718
- }
719
- async function removeConfigKeys(keys) {
720
- const configPath = getConfigPath();
721
- if (!await fs4.pathExists(configPath)) return;
722
- try {
723
- const existing = await fs4.readJson(configPath);
724
- for (const key of keys) {
725
- delete existing[key];
726
- }
727
- await fs4.writeJson(configPath, existing, { spaces: 2 });
728
- } catch {
729
- }
730
- }
731
- async function removeConfigFile() {
732
- const configPath = getConfigPath();
733
- if (await fs4.pathExists(configPath)) {
734
- await fs4.remove(configPath);
735
- }
736
- }
737
- async function getAllConfig() {
738
- const fileConfig = await readConfigFile();
739
- return CONFIG_DEFINITIONS.map((def) => {
740
- const envValue = process.env[def.envVar];
741
- if (envValue !== void 0) {
742
- return {
743
- ...def,
744
- value: envValue,
745
- source: "env"
746
- };
747
- }
748
- const fileValue = fileConfig[def.key];
749
- if (fileValue !== void 0) {
750
- return {
751
- ...def,
752
- value: fileValue,
753
- source: "file"
754
- };
755
- }
756
- return {
757
- ...def,
758
- value: def.defaultValue,
759
- source: "default"
760
- };
761
- });
762
- }
763
- async function getConfig(key) {
764
- const all = await getAllConfig();
765
- return all.find((c) => c.key === key) || null;
766
- }
767
- async function listConfig() {
768
- const entries = await getAllConfig();
769
- console.log("\n\u{1F527} SkillMarket Configuration\n");
770
- const maxKeyLen = Math.max(...entries.map((e) => e.key.length));
771
- for (const entry of entries) {
772
- const key = entry.key.padEnd(maxKeyLen + 2);
773
- const sourceBadge = getSourceBadge(entry.source);
774
- console.log(` ${sourceBadge} ${key}${entry.value}`);
775
- }
776
- console.log("");
777
- console.log(" \u6765\u6E90: \u{1F535} \u73AF\u5883\u53D8\u91CF \u{1F7E2} \u914D\u7F6E\u6587\u4EF6 \u26AA \u9ED8\u8BA4\u503C");
778
- console.log(" \u914D\u7F6E\u6587\u4EF6: " + getConfigPath());
779
- console.log("");
780
- console.log(" \u7528\u6CD5:");
781
- console.log(" skm config get <key> \u67E5\u770B\u914D\u7F6E\u503C");
782
- console.log(" skm config set <key> <v> \u8BBE\u7F6E\u914D\u7F6E\u503C");
783
- console.log(" skm config reset <key> \u91CD\u7F6E\u4E3A\u9ED8\u8BA4\u503C");
784
- console.log(" skm config reset --all \u5168\u90E8\u91CD\u7F6E");
785
- console.log("");
786
- }
787
- function getSourceBadge(source) {
788
- switch (source) {
789
- case "env":
790
- return "\u{1F535}";
791
- case "file":
792
- return "\u{1F7E2}";
793
- case "default":
794
- return "\u26AA";
795
- }
796
- }
797
- async function getConfigValue(key) {
798
- const entry = await getConfig(key);
799
- if (!entry) {
800
- console.error(`\u274C Unknown config key: "${key}"`);
801
- console.log(` Valid keys: ${CONFIG_DEFINITIONS.map((d) => d.key).join(", ")}`);
802
- process.exit(1);
803
- }
804
- console.log(`
805
- \u{1F527} ${entry.key}`);
806
- console.log(` Value: ${entry.value}`);
807
- console.log(` Source: ${entry.source}`);
808
- console.log(` Env var: ${entry.envVar}`);
809
- console.log(` Default: ${entry.defaultValue}`);
810
- console.log(` Description: ${entry.description}`);
811
- console.log("");
812
- }
813
- async function setConfigValue(key, value) {
814
- const def = CONFIG_DEFINITIONS.find((d) => d.key === key);
815
- if (!def) {
816
- console.error(`\u274C Unknown config key: "${key}"`);
817
- console.log(` Valid keys: ${CONFIG_DEFINITIONS.map((d) => d.key).join(", ")}`);
818
- process.exit(1);
819
- }
820
- await writeConfigFile({ [key]: value });
821
- console.log(`
822
- \u2705 ${key} set to "${value}"`);
823
- console.log(` Stored in: ${getConfigPath()}`);
824
- console.log("");
825
- if (process.env[def.envVar] !== void 0) {
826
- console.log(` \u26A0\uFE0F Currently overridden by environment variable ${def.envVar}=${process.env[def.envVar]}`);
827
- console.log(` To use the config file value, unset the environment variable.`);
828
- console.log("");
829
- }
830
- }
831
- async function resetConfig(key, all = false) {
832
- if (all) {
833
- await removeConfigFile();
834
- console.log("\n\u2705 All configuration reset to defaults.");
835
- console.log(` Removed: ${getConfigPath()}`);
836
- console.log("");
837
- return;
838
- }
839
- if (key) {
840
- const def = CONFIG_DEFINITIONS.find((d) => d.key === key);
841
- if (!def) {
842
- console.error(`\u274C Unknown config key: "${key}"`);
843
- console.log(` Valid keys: ${CONFIG_DEFINITIONS.map((d) => d.key).join(", ")}`);
844
- process.exit(1);
845
- }
846
- await removeConfigKeys([key]);
847
- const sourceNow = process.env[def.envVar] ? "env" : "default";
848
- console.log(`
849
- \u2705 ${key} reset to ${sourceNow === "env" ? "environment variable" : "default"} value.`);
850
- console.log(` Effective value: "${sourceNow === "env" ? process.env[def.envVar] : def.defaultValue}"`);
851
- console.log("");
852
- return;
853
- }
854
- console.log("\n\u{1F527} Usage: skm config reset <key>");
855
- console.log(" skm config reset --all");
856
- console.log(` Valid keys: ${CONFIG_DEFINITIONS.map((d) => d.key).join(", ")}`);
857
- console.log("");
858
- }
859
-
860
648
  // src/cli.ts
861
649
  var __filename2 = fileURLToPath2(import.meta.url);
862
650
  var __dirname2 = dirname(__filename2);
package/gui/app.js CHANGED
@@ -248,6 +248,24 @@ const translations = {
248
248
 
249
249
  // 通用错误
250
250
  'error.generic': 'Error',
251
+
252
+ // GitHub Token
253
+ 'token.title': '🔑 GitHub Token',
254
+ 'token.desc': 'Used to create GitHub Releases and other operations from the desktop app.',
255
+ 'token.checking': 'Checking...',
256
+ 'token.active': '✅ Token set ({prefix})',
257
+ 'token.inactive': '❌ Token not set',
258
+ 'token.checkFailed': '⚠️ Check failed: {error}',
259
+ 'token.placeholder': 'Enter GitHub Personal Access Token (ghp_... / gho_...)',
260
+ 'token.placeholderOverride': 'Enter new Token to override current value...',
261
+ 'token.save': '💾 Save',
262
+ 'token.remove': '🗑 Remove',
263
+ 'token.hint': 'Token is stored in',
264
+ 'token.saved': '✅ GitHub Token saved',
265
+ 'token.removed': '✅ GitHub Token removed',
266
+ 'token.saveFailed': 'Save failed: {error}',
267
+ 'token.removeFailed': 'Remove failed: {error}',
268
+ 'token.inputRequired': 'Please enter a GitHub Token',
251
269
  },
252
270
 
253
271
  zh: {
@@ -476,6 +494,24 @@ const translations = {
476
494
 
477
495
  // 通用错误
478
496
  'error.generic': '错误',
497
+
498
+ // GitHub Token
499
+ 'token.title': '🔑 GitHub Token',
500
+ 'token.desc': '用于从桌面软件直接创建 GitHub Release 等操作。',
501
+ 'token.checking': '检查中...',
502
+ 'token.active': '✅ Token 已设置 ({prefix})',
503
+ 'token.inactive': '❌ 未设置 Token',
504
+ 'token.checkFailed': '⚠️ 检查失败: {error}',
505
+ 'token.placeholder': '输入 GitHub Personal Access Token (ghp_... / gho_...)',
506
+ 'token.placeholderOverride': '输入新 Token 以覆盖当前值...',
507
+ 'token.save': '💾 保存',
508
+ 'token.remove': '🗑 移除',
509
+ 'token.hint': 'Token 保存在',
510
+ 'token.saved': '✅ GitHub Token 已保存',
511
+ 'token.removed': '✅ GitHub Token 已移除',
512
+ 'token.saveFailed': '保存失败: {error}',
513
+ 'token.removeFailed': '移除失败: {error}',
514
+ 'token.inputRequired': '请输入 GitHub Token',
479
515
  }
480
516
  };
481
517
 
@@ -634,6 +670,25 @@ function applyI18nToStaticElements() {
634
670
  if (uploadActionBoth) uploadActionBoth.innerHTML = t('upload.actionBoth');
635
671
  if (uploadActionDiscard) uploadActionDiscard.innerHTML = t('upload.actionDiscard');
636
672
  if (uploadProgressText) uploadProgressText.textContent = t('upload.processing');
673
+
674
+ // GitHub Token 静态文本
675
+ const tokenTitle = document.getElementById('token-section-title');
676
+ const tokenDesc = document.getElementById('token-section-desc');
677
+ const tokenHint = document.getElementById('token-section-hint');
678
+ const tokenSaveBtn = document.getElementById('token-save-btn');
679
+ const tokenRemoveBtn = document.getElementById('token-remove-btn');
680
+ const tokenInput = document.getElementById('token-input');
681
+
682
+ if (tokenTitle) tokenTitle.textContent = t('token.title');
683
+ if (tokenDesc) tokenDesc.textContent = t('token.desc');
684
+ if (tokenHint) tokenHint.textContent = t('token.hint');
685
+ if (tokenSaveBtn) tokenSaveBtn.textContent = t('token.save');
686
+ if (tokenRemoveBtn) tokenRemoveBtn.textContent = t('token.remove');
687
+ if (tokenInput && !tokenInput.value) {
688
+ // Only update placeholder when input is empty
689
+ const hasToken = document.getElementById('token-indicator')?.classList.contains('token-active');
690
+ tokenInput.placeholder = hasToken ? t('token.placeholderOverride') : t('token.placeholder');
691
+ }
637
692
  }
638
693
 
639
694
  // -----------------------------------------------------------------------------
@@ -833,6 +888,23 @@ function initializeControls() {
833
888
  });
834
889
  }
835
890
 
891
+ // GitHub Token 管理
892
+ const tokenSaveBtn = document.getElementById('token-save-btn');
893
+ const tokenRemoveBtn = document.getElementById('token-remove-btn');
894
+ const tokenInput = document.getElementById('token-input');
895
+
896
+ if (tokenSaveBtn) {
897
+ tokenSaveBtn.addEventListener('click', saveGithubToken);
898
+ }
899
+ if (tokenRemoveBtn) {
900
+ tokenRemoveBtn.addEventListener('click', removeGithubToken);
901
+ }
902
+ if (tokenInput) {
903
+ tokenInput.addEventListener('keydown', (e) => {
904
+ if (e.key === 'Enter') saveGithubToken();
905
+ });
906
+ }
907
+
836
908
  // 语言切换
837
909
  const langSelect = document.getElementById('lang-select');
838
910
  if (langSelect) {
@@ -1160,8 +1232,10 @@ async function loadHelp() {
1160
1232
  container.innerHTML = `<div class="loading">${t('loading.generic')}</div>`;
1161
1233
 
1162
1234
  try {
1163
- const response = await fetch('/api/config');
1164
- const config = await response.json();
1235
+ const [config] = await Promise.all([
1236
+ fetch('/api/config').then(r => r.json()),
1237
+ checkGithubToken(),
1238
+ ]);
1165
1239
  renderHelp(config, container);
1166
1240
  } catch (err) {
1167
1241
  container.innerHTML = `<div class="loading">Error: ${err.message}</div>`;
@@ -1276,6 +1350,87 @@ skm config reset --all # 全部恢复默认</pre>
1276
1350
  `;
1277
1351
  }
1278
1352
 
1353
+ // -----------------------------------------------------------------------------
1354
+ // GitHub Token 管理
1355
+ // -----------------------------------------------------------------------------
1356
+
1357
+ async function checkGithubToken() {
1358
+ try {
1359
+ const response = await fetch('/api/github-token');
1360
+ const data = await response.json();
1361
+
1362
+ const indicator = document.getElementById('token-indicator');
1363
+ const statusText = document.getElementById('token-status-text');
1364
+ const tokenInput = document.getElementById('token-input');
1365
+ const removeBtn = document.getElementById('token-remove-btn');
1366
+
1367
+ if (data.hasToken) {
1368
+ indicator.className = 'token-indicator token-active';
1369
+ statusText.textContent = t('token.active', { prefix: data.tokenPrefix });
1370
+ if (removeBtn) removeBtn.style.display = '';
1371
+ if (tokenInput) tokenInput.placeholder = t('token.placeholderOverride');
1372
+ // Re-apply i18n for button texts
1373
+ const saveBtn = document.getElementById('token-save-btn');
1374
+ const rmBtn = document.getElementById('token-remove-btn');
1375
+ if (saveBtn) saveBtn.textContent = t('token.save');
1376
+ if (rmBtn) rmBtn.textContent = t('token.remove');
1377
+ } else {
1378
+ indicator.className = 'token-indicator token-none';
1379
+ statusText.textContent = t('token.inactive');
1380
+ if (removeBtn) removeBtn.style.display = 'none';
1381
+ if (tokenInput) tokenInput.placeholder = t('token.placeholder');
1382
+ }
1383
+ } catch (err) {
1384
+ const statusText = document.getElementById('token-status-text');
1385
+ if (statusText) statusText.textContent = t('token.checkFailed', { error: err.message });
1386
+ }
1387
+ }
1388
+
1389
+ async function saveGithubToken() {
1390
+ const input = document.getElementById('token-input');
1391
+ const token = input ? input.value.trim() : '';
1392
+
1393
+ if (!token) {
1394
+ showToast(t('token.inputRequired'), 'error');
1395
+ return;
1396
+ }
1397
+
1398
+ try {
1399
+ const response = await fetch('/api/github-token', {
1400
+ method: 'POST',
1401
+ headers: { 'Content-Type': 'application/json' },
1402
+ body: JSON.stringify({ token }),
1403
+ });
1404
+ const result = await response.json();
1405
+ if (result.error) {
1406
+ showToast(t('token.saveFailed', { error: result.error }), 'error');
1407
+ } else {
1408
+ showToast(t('token.saved'), 'success');
1409
+ input.value = '';
1410
+ checkGithubToken();
1411
+ }
1412
+ } catch (err) {
1413
+ showToast(t('token.saveFailed', { error: err.message }), 'error');
1414
+ }
1415
+ }
1416
+
1417
+ async function removeGithubToken() {
1418
+ try {
1419
+ const response = await fetch('/api/github-token', {
1420
+ method: 'DELETE',
1421
+ });
1422
+ const result = await response.json();
1423
+ if (result.error) {
1424
+ showToast(t('token.removeFailed', { error: result.error }), 'error');
1425
+ } else {
1426
+ showToast(t('token.removed'), 'success');
1427
+ checkGithubToken();
1428
+ }
1429
+ } catch (err) {
1430
+ showToast(t('token.removeFailed', { error: err.message }), 'error');
1431
+ }
1432
+ }
1433
+
1279
1434
  async function installSkill(skillId) {
1280
1435
  try {
1281
1436
  showToast(t('toast.installing', { skillId: skillId }), 'info');
package/gui/index.html CHANGED
@@ -82,6 +82,21 @@
82
82
  <div class="view-header">
83
83
  <h2>Help &amp; Configuration</h2>
84
84
  </div>
85
+ <!-- GitHub Token 管理 -->
86
+ <div class="help-token-section" id="help-token-section">
87
+ <h3 id="token-section-title">🔑 GitHub Token</h3>
88
+ <p class="help-token-desc" id="token-section-desc">用于从桌面软件直接创建 GitHub Release 等操作。</p>
89
+ <div class="help-token-status" id="token-status">
90
+ <span class="token-indicator token-unknown" id="token-indicator"></span>
91
+ <span id="token-status-text">检查中...</span>
92
+ </div>
93
+ <div class="help-token-input-row">
94
+ <input type="password" id="token-input" placeholder="输入 GitHub Personal Access Token (ghp_... / gho_...)" spellcheck="false">
95
+ <button id="token-save-btn" class="btn btn-success"></button>
96
+ <button id="token-remove-btn" class="btn btn-danger" style="display:none"></button>
97
+ </div>
98
+ <p class="help-token-hint"><span id="token-section-hint">Token 保存在</span> <code>~/.skillmarket/config.json</code>。</p>
99
+ </div>
85
100
  <div id="help-content"></div>
86
101
  </div>
87
102
 
package/gui/style.css CHANGED
@@ -1171,3 +1171,88 @@ body {
1171
1171
  background: var(--bg-secondary);
1172
1172
  color: var(--text-secondary);
1173
1173
  }
1174
+
1175
+ /* -----------------------------------------------------------------------------
1176
+ GitHub Token 管理(Help 视图顶部)
1177
+ ----------------------------------------------------------------------------- */
1178
+
1179
+ .help-token-section {
1180
+ background: var(--bg-secondary);
1181
+ border: 1px solid var(--border-color);
1182
+ border-radius: 10px;
1183
+ padding: 20px 22px;
1184
+ margin-bottom: 18px;
1185
+ }
1186
+
1187
+ .help-token-section h3 {
1188
+ color: var(--accent);
1189
+ font-size: 1.05rem;
1190
+ margin-bottom: 6px;
1191
+ }
1192
+
1193
+ .help-token-desc {
1194
+ color: var(--text-muted);
1195
+ font-size: 0.82rem;
1196
+ margin-bottom: 12px;
1197
+ }
1198
+
1199
+ .help-token-status {
1200
+ display: flex;
1201
+ align-items: center;
1202
+ gap: 8px;
1203
+ margin-bottom: 10px;
1204
+ font-size: 0.85rem;
1205
+ color: var(--text-secondary);
1206
+ }
1207
+
1208
+ .token-indicator {
1209
+ width: 10px;
1210
+ height: 10px;
1211
+ border-radius: 50%;
1212
+ display: inline-block;
1213
+ flex-shrink: 0;
1214
+ }
1215
+
1216
+ .token-indicator.token-unknown { background: var(--text-muted); }
1217
+ .token-indicator.token-active { background: var(--success); }
1218
+ .token-indicator.token-none { background: var(--danger); }
1219
+
1220
+ .help-token-input-row {
1221
+ display: flex;
1222
+ gap: 8px;
1223
+ align-items: center;
1224
+ }
1225
+
1226
+ .help-token-input-row input {
1227
+ flex: 1;
1228
+ padding: 8px 12px;
1229
+ background: var(--bg-primary);
1230
+ border: 1px solid var(--border-color);
1231
+ border-radius: 6px;
1232
+ color: var(--text-secondary);
1233
+ font-size: 0.85rem;
1234
+ font-family: 'Menlo', 'Consolas', monospace;
1235
+ }
1236
+
1237
+ .help-token-input-row input:focus {
1238
+ outline: none;
1239
+ border-color: var(--accent);
1240
+ }
1241
+
1242
+ .help-token-input-row input::placeholder {
1243
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1244
+ color: var(--text-muted);
1245
+ }
1246
+
1247
+ .help-token-hint {
1248
+ color: var(--text-muted);
1249
+ font-size: 0.75rem;
1250
+ margin-top: 8px;
1251
+ }
1252
+
1253
+ .help-token-hint code {
1254
+ background: var(--bg-card);
1255
+ padding: 1px 5px;
1256
+ border-radius: 3px;
1257
+ font-size: 0.72rem;
1258
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itismyskillmarket",
3
- "version": "1.3.40",
3
+ "version": "1.3.41",
4
4
  "description": "Cross-platform skill manager for AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {