huaweicloud-devkit 1.0.2-dev.9 → 1.0.2-next.0

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.
Files changed (97) hide show
  1. package/.agents/plugins/marketplace.json +20 -20
  2. package/LICENSE +201 -201
  3. package/README.md +163 -138
  4. package/README.zh-CN.md +161 -138
  5. package/bin/setup.cjs +8 -8
  6. package/integrations/opencode/commands/huaweicloud-doctor.md +14 -14
  7. package/integrations/opencode/opencode.json +14 -14
  8. package/integrations/opencode/skills/huaweicloud-core/SKILL.md +32 -32
  9. package/package.json +46 -44
  10. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +44 -44
  11. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +43 -43
  12. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +44 -44
  13. package/plugins/huaweicloud-core/.mcp.json +15 -15
  14. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +44 -44
  15. package/plugins/huaweicloud-core/hooks/hooks.json +26 -26
  16. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +136 -136
  17. package/plugins/huaweicloud-core/safety/policy.json +99 -99
  18. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +199 -199
  19. package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +164 -164
  20. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +48 -48
  21. package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +50 -50
  22. package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +101 -101
  23. package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +90 -90
  24. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +190 -190
  25. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +124 -124
  26. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +55 -55
  27. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +70 -70
  28. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +56 -56
  29. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +97 -97
  30. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +29 -29
  31. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +23 -23
  32. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +185 -185
  33. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +133 -133
  34. package/plugins/huaweicloud-core/skills/huawei-ecs/references/evs.md +31 -31
  35. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +55 -55
  36. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +52 -52
  37. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +131 -131
  38. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +58 -58
  39. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +71 -71
  40. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/deploy-workflow.md +119 -119
  41. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +142 -142
  42. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +56 -56
  43. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +65 -65
  44. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +111 -111
  45. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +122 -122
  46. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +56 -56
  47. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +65 -65
  48. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +133 -127
  49. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +49 -49
  50. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +26 -26
  51. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +100 -100
  52. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +40 -40
  53. package/plugins/huaweicloud-core/skills/huawei-obs/references/static-website.md +56 -56
  54. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +224 -224
  55. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +135 -134
  56. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +69 -69
  57. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +84 -84
  58. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +76 -76
  59. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +59 -59
  60. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +50 -50
  61. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +63 -63
  62. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +56 -56
  63. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +4 -4
  64. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +51 -47
  65. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +4 -4
  66. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +128 -128
  67. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +4 -4
  68. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +110 -95
  69. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +4 -4
  70. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +19 -19
  71. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +27 -27
  72. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +60 -60
  73. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +4 -4
  74. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +83 -83
  75. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +4 -4
  76. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +105 -87
  77. package/plugins/huaweicloud-core/src/auth/credentials.mjs +95 -95
  78. package/plugins/huaweicloud-core/src/auth/service.mjs +63 -63
  79. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +265 -265
  80. package/plugins/huaweicloud-core/src/mcp-server.mjs +129 -129
  81. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +155 -0
  82. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +87 -0
  83. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +137 -137
  84. package/plugins/huaweicloud-core/src/safety-policy.mjs +302 -302
  85. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +91 -87
  86. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +162 -153
  87. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +112 -105
  88. package/plugins/huaweicloud-core/src/search-market.mjs +174 -169
  89. package/plugins/huaweicloud-core/src/setup-cli.mjs +2017 -1594
  90. package/plugins/huaweicloud-core/src/tools.mjs +1008 -993
  91. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +427 -427
  92. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +132 -132
  93. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +227 -227
  94. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +202 -202
  95. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +158 -158
  96. package/plugins/huaweicloud-core/src/ws-exec/index.js +19 -19
  97. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +338 -338
@@ -1,105 +1,112 @@
1
- import { spawn } from 'node:child_process';
2
- import { join, dirname } from 'node:path';
3
- import { fileURLToPath, pathToFileURL } from 'node:url';
4
- import { createConnection, getCredentials } from './hwlink-api.mjs';
5
-
6
- const __dirname = dirname(fileURLToPath(import.meta.url));
7
- export const WS_EXEC_INDEX_URL = pathToFileURL(join(__dirname, '..', 'ws-exec', 'index.js')).href;
8
-
9
- const DEFAULT_WORKSPACE_ID = process.env.HW_WORKSPACE_ID || '0107bd9997aa4287bd2b4890b49af07d';
10
-
11
- function resolveEnv() {
12
- const env = { ...process.env };
13
- env.PATH = `${env.HOME || '/root'}/.huawei/bin:${env.PATH || ''}`;
14
- return env;
15
- }
16
-
17
- async function runNodeExec(args, timeoutMs = 30000) {
18
- const env = resolveEnv();
19
- return new Promise((resolve) => {
20
- const proc = spawn('node', args, { env, stdio: ['pipe', 'pipe', 'pipe'] });
21
- let stdout = '';
22
- let stderr = '';
23
- proc.stdout.on('data', (d) => { stdout += d.toString(); });
24
- proc.stderr.on('data', (d) => { stderr += d.toString(); });
25
-
26
- const timer = setTimeout(() => {
27
- proc.kill();
28
- resolve({ error: 'timed out', exitCode: 124 });
29
- }, timeoutMs);
30
-
31
- proc.on('close', (code) => {
32
- clearTimeout(timer);
33
- const out = stdout.trim();
34
- if (out) {
35
- try {
36
- resolve({ ...JSON.parse(out), exitCode: code || 0 });
37
- return;
38
- } catch {}
39
- }
40
- if (code && code !== 0 && !out) {
41
- resolve({ error: stderr.trim() || `exit code ${code}`, exitCode: code });
42
- return;
43
- }
44
- resolve({ data: out, exitCode: code || 0 });
45
- });
46
- });
47
- }
48
-
49
- const sessions = new Map();
50
-
51
- async function getSession(workspaceId, username, timeoutMs) {
52
- const key = `${workspaceId}:${username}`;
53
- if (sessions.has(key)) return sessions.get(key);
54
-
55
- const { ak, sk, securitytoken } = getCredentials();
56
- const { wsUrl, source } = await createConnection(workspaceId, ak, sk, securitytoken);
57
-
58
- const { connectHwlinkTerminalSession } = await import(WS_EXEC_INDEX_URL);
59
- const session = await connectHwlinkTerminalSession({
60
- url: wsUrl,
61
- source,
62
- username,
63
- timeoutMs,
64
- });
65
-
66
- sessions.set(key, session);
67
- return session;
68
- }
69
-
70
- export async function execOneShot(workspaceId, command, username, timeoutMs) {
71
- const { ak, sk, securitytoken } = getCredentials();
72
- const { wsUrl, source } = await createConnection(workspaceId, ak, sk, securitytoken);
73
-
74
- const { executeHwlinkCommand } = await import(WS_EXEC_INDEX_URL);
75
- return await executeHwlinkCommand({
76
- url: wsUrl,
77
- source,
78
- username,
79
- command,
80
- timeoutMs,
81
- });
82
- }
83
-
84
- export async function execWithSession(workspaceId, command, username, timeoutMs) {
85
- const session = await getSession(workspaceId, username, timeoutMs);
86
- return await session.exec(command, { timeoutMs });
87
- }
88
-
89
- export async function closeSession(workspaceId, username) {
90
- const key = `${workspaceId}:${username}`;
91
- const session = sessions.get(key);
92
- if (!session) return false;
93
- sessions.delete(key);
94
- try { session.close(); } catch {}
95
- return true;
96
- }
97
-
98
- export async function closeAllSessions() {
99
- for (const [key, session] of sessions) {
100
- sessions.delete(key);
101
- try { session.close(); } catch {}
102
- }
103
- }
104
-
105
- export { DEFAULT_WORKSPACE_ID, runNodeExec };
1
+ import { spawn } from 'node:child_process';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+ import { createConnection, getCredentials } from './hwlink-api.mjs';
5
+ import { getWebSocketImpl } from '../proxy/proxy-agent.mjs';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ export const WS_EXEC_INDEX_URL = pathToFileURL(join(__dirname, '..', 'ws-exec', 'index.js')).href;
9
+
10
+ const DEFAULT_WORKSPACE_ID = process.env.HW_WORKSPACE_ID || '0107bd9997aa4287bd2b4890b49af07d';
11
+
12
+ function resolveEnv() {
13
+ const env = { ...process.env };
14
+ env.PATH = `${env.HOME || '/root'}/.huawei/bin:${env.PATH || ''}`;
15
+ return env;
16
+ }
17
+
18
+ async function runNodeExec(args, timeoutMs = 30000) {
19
+ const env = resolveEnv();
20
+ return new Promise((resolve) => {
21
+ const proc = spawn('node', args, { env, stdio: ['pipe', 'pipe', 'pipe'] });
22
+ let stdout = '';
23
+ let stderr = '';
24
+ proc.stdout.on('data', (d) => { stdout += d.toString(); });
25
+ proc.stderr.on('data', (d) => { stderr += d.toString(); });
26
+
27
+ const timer = setTimeout(() => {
28
+ proc.kill();
29
+ resolve({ error: 'timed out', exitCode: 124 });
30
+ }, timeoutMs);
31
+
32
+ proc.on('close', (code) => {
33
+ clearTimeout(timer);
34
+ const out = stdout.trim();
35
+ if (out) {
36
+ try {
37
+ resolve({ ...JSON.parse(out), exitCode: code || 0 });
38
+ return;
39
+ } catch {}
40
+ }
41
+ if (code && code !== 0 && !out) {
42
+ resolve({ error: stderr.trim() || `exit code ${code}`, exitCode: code });
43
+ return;
44
+ }
45
+ resolve({ data: out, exitCode: code || 0 });
46
+ });
47
+ });
48
+ }
49
+
50
+ const sessions = new Map();
51
+
52
+ async function getSession(workspaceId, username, timeoutMs) {
53
+ const key = `${workspaceId}:${username}`;
54
+ if (sessions.has(key)) return sessions.get(key);
55
+
56
+ const { ak, sk, securitytoken } = getCredentials();
57
+ const { wsUrl, source } = await createConnection(workspaceId, ak, sk, securitytoken);
58
+
59
+ const WebSocketImpl = getWebSocketImpl(wsUrl);
60
+
61
+ const { connectHwlinkTerminalSession } = await import(WS_EXEC_INDEX_URL);
62
+ const session = await connectHwlinkTerminalSession({
63
+ url: wsUrl,
64
+ source,
65
+ username,
66
+ timeoutMs,
67
+ WebSocketImpl,
68
+ });
69
+
70
+ sessions.set(key, session);
71
+ return session;
72
+ }
73
+
74
+ export async function execOneShot(workspaceId, command, username, timeoutMs) {
75
+ const { ak, sk, securitytoken } = getCredentials();
76
+ const { wsUrl, source } = await createConnection(workspaceId, ak, sk, securitytoken);
77
+
78
+ const WebSocketImpl = getWebSocketImpl(wsUrl);
79
+
80
+ const { executeHwlinkCommand } = await import(WS_EXEC_INDEX_URL);
81
+ return await executeHwlinkCommand({
82
+ url: wsUrl,
83
+ source,
84
+ username,
85
+ command,
86
+ timeoutMs,
87
+ WebSocketImpl,
88
+ });
89
+ }
90
+
91
+ export async function execWithSession(workspaceId, command, username, timeoutMs) {
92
+ const session = await getSession(workspaceId, username, timeoutMs);
93
+ return await session.exec(command, { timeoutMs });
94
+ }
95
+
96
+ export async function closeSession(workspaceId, username) {
97
+ const key = `${workspaceId}:${username}`;
98
+ const session = sessions.get(key);
99
+ if (!session) return false;
100
+ sessions.delete(key);
101
+ try { session.close(); } catch {}
102
+ return true;
103
+ }
104
+
105
+ export async function closeAllSessions() {
106
+ for (const [key, session] of sessions) {
107
+ sessions.delete(key);
108
+ try { session.close(); } catch {}
109
+ }
110
+ }
111
+
112
+ export { DEFAULT_WORKSPACE_ID, runNodeExec };
@@ -1,169 +1,174 @@
1
- const INDEX_URL = 'https://gitcode.com/api/v5/repos/2501_91318609/skills-for-index/contents/skills-index/index.json?ref=main';
2
- const CN_EN_MAP_URL = 'https://gitcode.com/api/v5/repos/2501_91318609/skills-for-index/contents/skills-index/cn-en-map.json?ref=main';
3
- const HTTP_TIMEOUT_MS = 10000;
4
-
5
- const GENERIC_KEYWORDS = new Set([
6
- '华为云', 'huawei', 'huawei cloud', '云', 'cloud',
7
- '技能', 'skill', 'skills', '所有', 'all', '全部',
8
- '有什么', '有哪些', '相关', '列表', 'list',
9
- '查找', '搜索', '发现', '浏览', 'find', 'search',
10
- 'discover', 'browse', 'show', 'explore',
11
- 'agent', '市场', 'market', '类目', 'category',
12
- '安装', 'install',
13
- ]);
14
-
15
- let cachedIndex = null;
16
- let cachedCnEnMap = null;
17
-
18
- async function fetchJson(url, label = '') {
19
- const controller = new AbortController();
20
- const timer = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS);
21
- try {
22
- const resp = await fetch(url, {
23
- headers: { 'User-Agent': 'huaweicloud-devkit/1.0' },
24
- signal: controller.signal,
25
- });
26
- const data = await resp.json();
27
- if (data && data.encoding === 'base64' && data.content) {
28
- const decoded = Buffer.from(data.content, 'base64').toString('utf8');
29
- return JSON.parse(decoded);
30
- }
31
- return data;
32
- } catch (e) {
33
- throw new Error(`Failed to fetch ${label}: ${e.message}`);
34
- } finally {
35
- clearTimeout(timer);
36
- }
37
- }
38
-
39
- async function loadIndex() {
40
- if (!cachedIndex) cachedIndex = await fetchJson(INDEX_URL, 'index.json');
41
- return cachedIndex;
42
- }
43
-
44
- async function loadCnEnMap() {
45
- if (!cachedCnEnMap) cachedCnEnMap = await fetchJson(CN_EN_MAP_URL, 'cn-en-map.json');
46
- return cachedCnEnMap;
47
- }
48
-
49
- export function clearMarketCache() {
50
- cachedIndex = null;
51
- cachedCnEnMap = null;
52
- }
53
-
54
- function isGeneric(kw) {
55
- const k = kw.toLowerCase().trim();
56
- return GENERIC_KEYWORDS.has(k) || /华为云|huawei/i.test(k);
57
- }
58
-
59
- function expandKeywords(rawKeyword, cnEnMap) {
60
- if (!rawKeyword) return [[], []];
61
- const parts = rawKeyword.replace(/[,;]/g, ' ').split(/\s+/).filter(Boolean);
62
- const expanded = [...parts];
63
- for (const p of parts) {
64
- const lower = p.toLowerCase();
65
- if (cnEnMap[p]) expanded.push(cnEnMap[p]);
66
- for (const [cn, en] of Object.entries(cnEnMap)) {
67
- if (en === lower) expanded.push(cn);
68
- }
69
- }
70
- const unique = [...new Set(expanded)].sort();
71
- const specific = unique.filter((kw) => !isGeneric(kw));
72
- const generic = unique.filter((kw) => isGeneric(kw));
73
- return [specific, generic];
74
- }
75
-
76
- function scoreSkill(skill, specificKws, genericKws) {
77
- if (!specificKws.length && !genericKws.length) return [1, []];
78
- let total = 0;
79
- const matched = [];
80
- const nameLower = (skill.name || '').toLowerCase();
81
- const descLower = (skill.description || '').toLowerCase();
82
- const serviceLower = (skill.service || '').toLowerCase();
83
- const triggers = (skill.triggers || []).map((t) => (t || '').toLowerCase());
84
-
85
- for (const kw of specificKws) {
86
- const k = kw.toLowerCase();
87
- let s = 0;
88
- if (nameLower.includes(k)) s += 10;
89
- else if (triggers.some((t) => t.includes(k))) s += 8;
90
- else if (descLower.includes(k)) s += 5;
91
- else if (serviceLower.includes(k)) s += 3;
92
- if (s > 0) { total += s; matched.push(kw); }
93
- }
94
- for (const kw of genericKws) {
95
- const k = kw.toLowerCase();
96
- let s = 0;
97
- if (nameLower.includes(k)) s += 10;
98
- else if (triggers.some((t) => t.includes(k))) s += 4;
99
- else if (descLower.includes(k)) s += 2;
100
- else if (serviceLower.includes(k)) s += 1;
101
- if (s > 0) { total += s; matched.push(kw); }
102
- }
103
- if (!specificKws.length && total === 0) {
104
- total = 1;
105
- if (descLower.length > 20) total += 1;
106
- if (triggers.length) total += 1;
107
- }
108
- return [total, matched];
109
- }
110
-
111
- function truncate(desc, limit = 150) {
112
- if (!desc) return '';
113
- return desc.length > limit ? desc.slice(0, limit) + '...' : desc;
114
- }
115
-
116
- export async function searchMarketplace(query = '', category = '') {
117
- const idx = await loadIndex();
118
- const cnEnMap = await loadCnEnMap();
119
- const [specificKws, genericKws] = expandKeywords(query, cnEnMap);
120
- const hasSpecific = specificKws.length > 0;
121
-
122
- const results = [];
123
- for (const skill of idx.skills || []) {
124
- if (category && skill.category !== category) continue;
125
- const [score, matched] = scoreSkill(skill, specificKws, genericKws);
126
- if (hasSpecific && score === 0) continue;
127
- results.push({
128
- score,
129
- name: skill.name || '',
130
- category: skill.category || '',
131
- service: skill.service || '',
132
- description: truncate(skill.description),
133
- triggers: (skill.triggers || []).slice(0, 5),
134
- matched,
135
- });
136
- }
137
- results.sort((a, b) => b.score - a.score);
138
-
139
- if (!results.length) {
140
- return {
141
- ok: true,
142
- query,
143
- category,
144
- count: 0,
145
- results: [],
146
- fallback: [
147
- 'Try broader or alternative keywords',
148
- 'Remove category filter',
149
- 'Switch CN<->EN (e.g., obs <-> object storage)',
150
- 'List all: search with empty keyword and a category',
151
- ],
152
- };
153
- }
154
-
155
- const allKws = [...new Set([...specificKws, ...genericKws])];
156
- return {
157
- ok: true,
158
- query,
159
- category,
160
- count: results.length,
161
- expandedKeywords: allKws.length > 1 ? allKws : undefined,
162
- results,
163
- };
164
- }
165
-
166
- export async function getMarketplaceCategories() {
167
- const idx = await loadIndex();
168
- return { ok: true, categories: idx.categories || [] };
169
- }
1
+ import { getProxyDispatcher } from './proxy/proxy-agent.mjs';
2
+
3
+ const INDEX_URL = 'https://gitcode.com/api/v5/repos/2501_91318609/skills-for-index/contents/skills-index/index.json?ref=main';
4
+ const CN_EN_MAP_URL = 'https://gitcode.com/api/v5/repos/2501_91318609/skills-for-index/contents/skills-index/cn-en-map.json?ref=main';
5
+ const HTTP_TIMEOUT_MS = 10000;
6
+
7
+ const GENERIC_KEYWORDS = new Set([
8
+ '华为云', 'huawei', 'huawei cloud', '', 'cloud',
9
+ '技能', 'skill', 'skills', '所有', 'all', '全部',
10
+ '有什么', '有哪些', '相关', '列表', 'list',
11
+ '查找', '搜索', '发现', '浏览', 'find', 'search',
12
+ 'discover', 'browse', 'show', 'explore',
13
+ 'agent', '市场', 'market', '类目', 'category',
14
+ '安装', 'install',
15
+ ]);
16
+
17
+ let cachedIndex = null;
18
+ let cachedCnEnMap = null;
19
+
20
+ async function fetchJson(url, label = '') {
21
+ const controller = new AbortController();
22
+ const timer = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS);
23
+ try {
24
+ const dispatcher = await getProxyDispatcher(url);
25
+ const fetchOpts = {
26
+ headers: { 'User-Agent': 'huaweicloud-devkit/1.0' },
27
+ signal: controller.signal,
28
+ };
29
+ if (dispatcher) fetchOpts.dispatcher = dispatcher;
30
+ const resp = await fetch(url, fetchOpts);
31
+ const data = await resp.json();
32
+ if (data && data.encoding === 'base64' && data.content) {
33
+ const decoded = Buffer.from(data.content, 'base64').toString('utf8');
34
+ return JSON.parse(decoded);
35
+ }
36
+ return data;
37
+ } catch (e) {
38
+ throw new Error(`Failed to fetch ${label}: ${e.message}`);
39
+ } finally {
40
+ clearTimeout(timer);
41
+ }
42
+ }
43
+
44
+ async function loadIndex() {
45
+ if (!cachedIndex) cachedIndex = await fetchJson(INDEX_URL, 'index.json');
46
+ return cachedIndex;
47
+ }
48
+
49
+ async function loadCnEnMap() {
50
+ if (!cachedCnEnMap) cachedCnEnMap = await fetchJson(CN_EN_MAP_URL, 'cn-en-map.json');
51
+ return cachedCnEnMap;
52
+ }
53
+
54
+ export function clearMarketCache() {
55
+ cachedIndex = null;
56
+ cachedCnEnMap = null;
57
+ }
58
+
59
+ function isGeneric(kw) {
60
+ const k = kw.toLowerCase().trim();
61
+ return GENERIC_KEYWORDS.has(k) || /华为云|huawei/i.test(k);
62
+ }
63
+
64
+ function expandKeywords(rawKeyword, cnEnMap) {
65
+ if (!rawKeyword) return [[], []];
66
+ const parts = rawKeyword.replace(/[,;]/g, ' ').split(/\s+/).filter(Boolean);
67
+ const expanded = [...parts];
68
+ for (const p of parts) {
69
+ const lower = p.toLowerCase();
70
+ if (cnEnMap[p]) expanded.push(cnEnMap[p]);
71
+ for (const [cn, en] of Object.entries(cnEnMap)) {
72
+ if (en === lower) expanded.push(cn);
73
+ }
74
+ }
75
+ const unique = [...new Set(expanded)].sort();
76
+ const specific = unique.filter((kw) => !isGeneric(kw));
77
+ const generic = unique.filter((kw) => isGeneric(kw));
78
+ return [specific, generic];
79
+ }
80
+
81
+ function scoreSkill(skill, specificKws, genericKws) {
82
+ if (!specificKws.length && !genericKws.length) return [1, []];
83
+ let total = 0;
84
+ const matched = [];
85
+ const nameLower = (skill.name || '').toLowerCase();
86
+ const descLower = (skill.description || '').toLowerCase();
87
+ const serviceLower = (skill.service || '').toLowerCase();
88
+ const triggers = (skill.triggers || []).map((t) => (t || '').toLowerCase());
89
+
90
+ for (const kw of specificKws) {
91
+ const k = kw.toLowerCase();
92
+ let s = 0;
93
+ if (nameLower.includes(k)) s += 10;
94
+ else if (triggers.some((t) => t.includes(k))) s += 8;
95
+ else if (descLower.includes(k)) s += 5;
96
+ else if (serviceLower.includes(k)) s += 3;
97
+ if (s > 0) { total += s; matched.push(kw); }
98
+ }
99
+ for (const kw of genericKws) {
100
+ const k = kw.toLowerCase();
101
+ let s = 0;
102
+ if (nameLower.includes(k)) s += 10;
103
+ else if (triggers.some((t) => t.includes(k))) s += 4;
104
+ else if (descLower.includes(k)) s += 2;
105
+ else if (serviceLower.includes(k)) s += 1;
106
+ if (s > 0) { total += s; matched.push(kw); }
107
+ }
108
+ if (!specificKws.length && total === 0) {
109
+ total = 1;
110
+ if (descLower.length > 20) total += 1;
111
+ if (triggers.length) total += 1;
112
+ }
113
+ return [total, matched];
114
+ }
115
+
116
+ function truncate(desc, limit = 150) {
117
+ if (!desc) return '';
118
+ return desc.length > limit ? desc.slice(0, limit) + '...' : desc;
119
+ }
120
+
121
+ export async function searchMarketplace(query = '', category = '') {
122
+ const idx = await loadIndex();
123
+ const cnEnMap = await loadCnEnMap();
124
+ const [specificKws, genericKws] = expandKeywords(query, cnEnMap);
125
+ const hasSpecific = specificKws.length > 0;
126
+
127
+ const results = [];
128
+ for (const skill of idx.skills || []) {
129
+ if (category && skill.category !== category) continue;
130
+ const [score, matched] = scoreSkill(skill, specificKws, genericKws);
131
+ if (hasSpecific && score === 0) continue;
132
+ results.push({
133
+ score,
134
+ name: skill.name || '',
135
+ category: skill.category || '',
136
+ service: skill.service || '',
137
+ description: truncate(skill.description),
138
+ triggers: (skill.triggers || []).slice(0, 5),
139
+ matched,
140
+ });
141
+ }
142
+ results.sort((a, b) => b.score - a.score);
143
+
144
+ if (!results.length) {
145
+ return {
146
+ ok: true,
147
+ query,
148
+ category,
149
+ count: 0,
150
+ results: [],
151
+ fallback: [
152
+ 'Try broader or alternative keywords',
153
+ 'Remove category filter',
154
+ 'Switch CN<->EN (e.g., obs <-> object storage)',
155
+ 'List all: search with empty keyword and a category',
156
+ ],
157
+ };
158
+ }
159
+
160
+ const allKws = [...new Set([...specificKws, ...genericKws])];
161
+ return {
162
+ ok: true,
163
+ query,
164
+ category,
165
+ count: results.length,
166
+ expandedKeywords: allKws.length > 1 ? allKws : undefined,
167
+ results,
168
+ };
169
+ }
170
+
171
+ export async function getMarketplaceCategories() {
172
+ const idx = await loadIndex();
173
+ return { ok: true, categories: idx.categories || [] };
174
+ }