huaweicloud-devkit 1.0.2 → 1.0.3-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.0.2",
3
+ "version": "1.0.3-next.0",
4
4
  "description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
5
5
  "type": "module",
6
6
  "files": [
@@ -17,7 +17,7 @@
17
17
  "mcpServers": "./.mcp.json",
18
18
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
19
19
  "skills": "./skills/",
20
- "version": "1.0.2",
20
+ "version": "1.0.3-next.0",
21
21
  "author": {
22
22
  "name": "HuaweiCloud Mate",
23
23
  "url": "https://github.com/huaweicloud"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.0.2",
3
+ "version": "1.0.3-next.0",
4
4
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
5
5
  "author": {
6
6
  "name": "HuaweiCloud Mate",
@@ -17,7 +17,7 @@
17
17
  "mcpServers": "./.mcp.json",
18
18
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
19
19
  "skills": "./skills/",
20
- "version": "1.0.2",
20
+ "version": "1.0.3-next.0",
21
21
  "author": {
22
22
  "name": "HuaweiCloud Mate",
23
23
  "url": "https://github.com/huaweicloud"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.0.2",
3
+ "version": "1.0.3-next.0",
4
4
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
5
5
  "author": {
6
6
  "name": "HuaweiCloud Mate",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.0.2",
3
+ "version": "1.0.3-next.0",
4
4
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
5
5
  "author": {
6
6
  "name": "HuaweiCloud Mate",
@@ -2,7 +2,7 @@
2
2
  "name": "huaweicloud-devkit",
3
3
  "id": "huaweicloud-devkit",
4
4
  "displayName": "HuaweiCloud DevKit",
5
- "version": "1.0.2",
5
+ "version": "1.0.3-next.0",
6
6
  "family": "bundle-plugin",
7
7
  "bundleFormat": "codex",
8
8
  "description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
@@ -63,3 +63,11 @@ Discover operation parameters with `--help` before executing any write operation
63
63
 
64
64
  - **OBS storage**: See `huawei-obs` for bucket and training data management
65
65
  - **VPC/Subnet**: See `huawei-vpc` for notebook network configuration
66
+
67
+ ## External Skills
68
+
69
+ For advanced workflows (notebook lifecycle, training job diagnosis,
70
+ hyperparameter tuning), search the marketplace:
71
+ `huaweicloud_search_marketplace` query="ModelArts notebook/training/diagnosis"
72
+ → install via `npx skills add huaweicloud/huaweicloud-skills --skill <name> -y -g`
73
+ → load and execute.
@@ -19,7 +19,10 @@ Use this skill to turn vague developer intent into a precise Huawei Cloud capabi
19
19
  6. Prefer SDK docs when the deliverable is application code.
20
20
  7. Prefer MCP only when an approved Huawei Cloud MCP tool exists for the needed operation.
21
21
  8. Treat Terraform as a secondary V1 path for reviewed IaC, not the default.
22
- 9. When no built-in devkit skill matches, browse the community skill marketplace at https://github.com/huaweicloud/huaweicloud-skills. Fetch the index from https://raw.githubusercontent.com/huaweicloud/huaweicloud-skills/master/skills-index/index.json.
22
+ 9. When no built-in devkit skill matches:
23
+ a. Search: use `huaweicloud_search_marketplace` to find a matching skill in the community marketplace
24
+ b. Install: run `npx skills add huaweicloud/huaweicloud-skills --skill <skill-name> -y -g`
25
+ c. Execute: load the installed skill and follow its procedure
23
26
  10. When the deliverable is a PPT, architecture diagram (draw.io), or frontend page that needs official Huawei Cloud service logos, use the `huaweicloud_get_service_icon` MCP tool to get logo URLs from the official Icons library instead of guessing or hotlinking unofficial images.
24
27
 
25
28
  ## Scenario Routing
@@ -438,18 +438,31 @@ function installRuntimeDeps(pluginsDir) {
438
438
  };
439
439
  mkdirSync(pluginsDir, { recursive: true });
440
440
  writeFileSync(join(pluginsDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
441
- const r = spawnSync('npm', ['install', '--omit=dev'], {
441
+ const spawnOpts = {
442
442
  cwd: pluginsDir,
443
+ shell: true,
443
444
  windowsHide: true,
444
445
  stdio: 'pipe',
445
446
  timeout: 120000,
446
- });
447
+ };
448
+ let r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
449
+ const isRetryable = (res) => {
450
+ const stderr = (res.stderr || '').toString();
451
+ return res.error?.code === 'EPERM' || res.error?.code === 'EBUSY' || /EPERM|EBUSY/.test(stderr);
452
+ };
453
+ if (r.status !== 0 && isRetryable(r)) {
454
+ console.log(` \x1b[33m[WARN]\x1b[0m npm install hit file-lock error, retrying in 2s...`);
455
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2000);
456
+ r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
457
+ }
447
458
  const undiciDir = join(pluginsDir, 'node_modules', 'undici');
448
459
  if (r.status === 0 && existsSync(undiciDir)) {
449
460
  console.log(` Runtime deps installed -> ${join(pluginsDir, 'node_modules')}`);
450
461
  } else {
462
+ const errCode = r.error?.code;
451
463
  const err = (r.stderr || '').toString().trim().split(/\r?\n/).slice(-2).join(' ');
452
- console.log(` \x1b[33m[WARN]\x1b[0m npm install failed in ${pluginsDir}${err ? `: ${err}` : ''}`);
464
+ const hint = errCode === 'ENOENT' ? ' (npm not found — ensure Node.js/npm is in PATH)' : '';
465
+ console.log(` \x1b[33m[WARN]\x1b[0m npm install failed in ${pluginsDir}${err ? `: ${err}` : ''}${hint}`);
453
466
  console.log(' Manual fix: cd %s && npm install', pluginsDir);
454
467
  if (!existsSync(undiciDir)) {
455
468
  console.log(` \x1b[31m[ERROR]\x1b[0m undici is NOT installed. MCP server will fail to start.`);
@@ -1378,6 +1391,7 @@ async function installDsh() {
1378
1391
  const safetyDir = join(PLUGIN_ROOT, 'safety');
1379
1392
  const pluginDest = dshPluginsDir();
1380
1393
 
1394
+ mkdirSync(pluginDest, { recursive: true });
1381
1395
  copyDir(skillsSrc, dshSkillsDir());
1382
1396
  console.log(` Skills -> ${dshSkillsDir()}`);
1383
1397
  copyDir(srcDir, join(pluginDest, 'src'));
@@ -1386,9 +1400,8 @@ async function installDsh() {
1386
1400
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1387
1401
  ensureDshMcpPatch();
1388
1402
  tryInstallDshMcpClient();
1389
- mkdirSync(pluginDest, { recursive: true });
1390
- writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1391
1403
  installRuntimeDeps(pluginDest);
1404
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1392
1405
  }
1393
1406
 
1394
1407
  async function updateDsh() {
@@ -1397,6 +1410,7 @@ async function updateDsh() {
1397
1410
  const safetyDir = join(PLUGIN_ROOT, 'safety');
1398
1411
  const pluginDest = dshPluginsDir();
1399
1412
 
1413
+ mkdirSync(pluginDest, { recursive: true });
1400
1414
  copyDir(skillsSrc, dshSkillsDir());
1401
1415
  const stale = pruneStale(dshSkillsDir(), skillsSrc);
1402
1416
  console.log(` Skills updated -> ${dshSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
@@ -1406,9 +1420,8 @@ async function updateDsh() {
1406
1420
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1407
1421
  ensureDshMcpPatch();
1408
1422
  tryInstallDshMcpClient();
1409
- mkdirSync(pluginDest, { recursive: true });
1410
- writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1411
1423
  installRuntimeDeps(pluginDest);
1424
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1412
1425
  }
1413
1426
 
1414
1427
  function uninstallDsh() {
@@ -1939,9 +1952,15 @@ async function cmdInstall() {
1939
1952
  : '当前 agent';
1940
1953
  const pad = ' '.repeat(24 - appName.length);
1941
1954
  if (target === 'officeace') {
1942
- console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
1943
- console.log(`\x1b[1m\x1b[33m║ 打开连接器 → 我的连接器 → huaweicloud-devkit ║`);
1944
- console.log(`\x1b[1m\x1b[33m║ → 连接 → 回到对话 → 输入框开启连接器 ║`);
1955
+ console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════════╗`);
1956
+ console.log(`\x1b[1m\x1b[33m║ 打开连接器 → 我的连接器 → huaweicloud-devkit �`);
1957
+ console.log(`\x1b[1m\x1b[33m║ → 连接 → 回到对话 → 输入框开启连接器 �`);
1958
+ console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
1959
+ } else if (target === 'workbuddy') {
1960
+ console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════════╗`);
1961
+ console.log(`\x1b[1m\x1b[33m║ MCP 工具即时生效,无需重启会话 �`);
1962
+ console.log(`\x1b[1m\x1b[33m║ 前往连接器 → 自定义连接器,确认 huaweicloud-devkit �`);
1963
+ console.log(`\x1b[1m\x1b[33m║ 已添加信任并启用 �`);
1945
1964
  console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
1946
1965
  } else {
1947
1966
  console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
@@ -1964,6 +1983,8 @@ async function cmdInstall() {
1964
1983
  console.log(` 2. 配置代理(企业内网):npx huaweicloud-devkit proxy init`);
1965
1984
  if (target === 'officeace') {
1966
1985
  console.log(` 3. 打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器`);
1986
+ } else if (target === 'workbuddy') {
1987
+ console.log(` 3. 前往连接器 → 自定义连接器,确认 huaweicloud-devkit 已添加信任并启用`);
1967
1988
  } else {
1968
1989
  console.log(` 3. 重启 ${appName} 会话(MCP 工具重启后生效)`);
1969
1990
  }
@@ -2189,6 +2210,18 @@ async function cmdDoctor() {
2189
2210
  check(`MCP server can start (${mcpTarget})`, true, '');
2190
2211
  }
2191
2212
 
2213
+ const undiciPluginDirs = [
2214
+ opencodePluginDir,
2215
+ codexPluginDir,
2216
+ codeartsPluginDir,
2217
+ workbuddyPluginDir,
2218
+ dshPluginDir,
2219
+ officeacePluginDir,
2220
+ hermesPluginDir,
2221
+ ];
2222
+ const undiciOk = undiciPluginDirs.some((d) => existsSync(join(d, 'node_modules', 'undici')));
2223
+ check('Runtime deps (undici) installed', undiciOk, 'Run: npx huaweicloud-devkit install');
2224
+
2192
2225
  const safetyOk =
2193
2226
  existsSync(join(opencodePluginDir, 'safety', 'policy.json')) ||
2194
2227
  existsSync(join(codexPluginDir, 'safety', 'policy.json')) ||
@@ -1,191 +0,0 @@
1
- ---
2
- name: huawei-cloud-find-skills
3
- description: |
4
- Invoke this skill to search, discover, browse, find and install any Huawei Cloud (华为云) agent skill.Triggers include: "华为云","华为云有什么skill","华为云相关skill","华为云agent skill 市场","华为云skill类目","explore Huawei Cloud skills","show Huawei Cloud skill categories","does a Huawei Cloud skill exist for...","which Huawei Cloud skills exist","搜索华为云技能","有没有管理ECS/OBS/RDS的skill","帮我找 XX 华为云skill","介绍 XX Skill 内容","华为云 XX Skill 具体做什么","安装华为云Skill".
5
- ---
6
-
7
- > [!IMPORTANT]
8
- > **For any Huawei Cloud query or management task:**
9
- >
10
- > 1. **Search** — use this skill (`huawei-cloud-find-skills`) to find the relevant Skill.
11
- > 2. **Install** — install the matched Skill (see [Step 3](#step-3-install-skill)).
12
- > 3. **Execute** — follow the installed Skill's instructions to fulfill the request.
13
-
14
- # Huawei Cloud Agent Skills Search and Discovery
15
-
16
- This skill enables users to efficiently search, discover, and install Huawei Cloud skills.
17
-
18
- ## Scenario Description
19
-
20
- This skill enables users to:
21
-
22
- - **Search Skills**: Find skills by keyword, category, or both (matched against name, description, and triggers)
23
- - **Browse Categories**: Explore available skill categories
24
- - **View Skill Details**: Fetch full SKILL.md content from GitHub for specific skills
25
- - **Install Skills**: Guide users through skill installation via `npx skills add` (GitCode default), `npx clawhub install`, or fallback GitHub method
26
-
27
- **Architecture**: GitCode API v5 (`index.json` + `cn-en-map.json`) → HTTP GET (base64 decode) → In-memory search → GitHub raw fetch for details → Install
28
-
29
- ### Use Cases
30
-
31
- - "Find a skill for managing ECS instances"
32
- - "What Huawei Cloud skills are available for OBS?"
33
- - "华为云有哪些 VPC 相关的 skill?"
34
- - "Browse all available Huawei Cloud skills"
35
- - "Install a skill for RDS management"
36
- - "帮我找一个华为云网络相关的skill"
37
-
38
- ## Prerequisites
39
-
40
- - **Node.js >= 22** must be installed
41
- - **Network access** to `gitcode.com` (API v5 for index) and `github.com` / `raw.githubusercontent.com` (for skill details)
42
-
43
- ### Step 0: Check Python Environment
44
-
45
- > **MANDATORY**: Before running any script command, verify Python is available.
46
-
47
- ```bash
48
- # Check Python availability
49
- python --version # or: python3 --version
50
- ```
51
-
52
- If the command fails or returns Python 2.x:
53
-
54
- 1. **Install Python 3**: Download from [python.org](https://www.python.org/downloads/) or use a package manager:
55
- ```bash
56
- # macOS
57
- brew install python3
58
- # Ubuntu/Debian
59
- sudo apt-get install python3
60
- # Windows — download installer from python.org, check "Add Python to PATH"
61
- ```
62
- 2. **Verify after install**: Run `python --version` again to confirm Python 3.6+ is available
63
- 3. **If `python` points to Python 2**: Use `python3` instead of `python` in all commands below
64
-
65
- ## Repository Info
66
-
67
- ```
68
- INDEX_REPO=2501_91318609/skills-for-index
69
- INDEX_BRANCH=main
70
- SKILLS_REPO=huaweicloud/huaweicloud-skills
71
- SKILLS_BRANCH=master
72
- RAW_BASE=https://raw.githubusercontent.com/$SKILLS_REPO/$SKILLS_BRANCH
73
- ```
74
-
75
- ## Index Source
76
-
77
- The search script fetches the skill index from GitCode API v5 via HTTP GET (base64 auto-decoded):
78
-
79
- ```
80
- SKILLS_INDEX_URL=https://gitcode.com/api/v5/repos/2501_91318609/skills-for-index/contents/skills-index/index.json?ref=main
81
- SKILLS_CN_EN_MAP_URL=https://gitcode.com/api/v5/repos/2501_91318609/skills-for-index/contents/skills-index/cn-en-map.json?ref=main
82
- ```
83
-
84
- ## Core Workflow
85
-
86
- ### Step 1: Search Skills
87
-
88
- > **MANDATORY**: The agent MUST execute the search script to search the skill index. Do NOT read the JSON file directly — always use the script.
89
-
90
- Given `keyword` (from AI-understood user intent) and optional `category`, run the search script:
91
-
92
- ```bash
93
- node scripts/search-skills.mjs <keyword>
94
- node scripts/search-skills.mjs <keyword> -c <category>
95
- node scripts/search-skills.mjs -c <category>
96
- ```
97
-
98
- → [scripts/search-skills.mjs](scripts/search-skills.mjs) (Node.js — cross-platform)
99
-
100
- **What the script does**:
101
-
102
- 1. Fetches `index.json` and `cn-en-map.json` via HTTP GET from GitCode API v5 (auto-decodes base64 content)
103
- 2. Expands keywords via `cn-en-map.json` (bidirectional CN↔EN, e.g., "ECS" → "ECS, 弹性云服务器, 云服务器")
104
- 3. Scores each skill: name match **+10**, trigger match **+8**, description match **+5**, service match **+3**
105
- 4. Sorts by score descending, outputs formatted results with matched keywords
106
-
107
- **Fallback iteration** (if no results): 1) Switch CN↔EN keywords 2) Expand keywords 3) Remove category filter 4) Try synonyms 5) List all skills
108
-
109
- The process should persist until the skill is found or its absence is confirmed. In the event of total failure, notify the user of the specific steps that were attempted.
110
-
111
- ### Step 2: View Skill Details (optional)
112
-
113
- Fetch the full SKILL.md content from GitHub for intent validation. Skip this step if the search results from Step 1 are sufficiently informative.
114
-
115
- ```bash
116
- # URL pattern — use the skill's category, service, and name from index
117
- DETAIL_URL="https://raw.githubusercontent.com/huaweicloud/huaweicloud-skills/master/skills/${category}/${service}/${name}/SKILL.md"
118
- ```
119
-
120
- The agent can fetch this URL using `curl` or its web-fetch tool, then present the skill's full documentation to the user.
121
-
122
- ### Step 3: Install Skill
123
-
124
- > **MANDATORY**: Use one of the commands below. Option A is the default; Option C is a fallback when Option A is unavailable.
125
-
126
- ```bash
127
- # Option A: npx skills add from GitCode (default)
128
- npx skills add https://gitcode.com/huaweicloud/huaweicloud-skills.git#master --skill <skill-name> -y
129
-
130
- # Option B: npx clawhub install (OpenClaw ecosystem)
131
- npx clawhub install <skill-name> -y
132
-
133
- # Option C (fallback): npx skills add from GitHub
134
- npx skills add huaweicloud/huaweicloud-skills --skill <skill-name> -y
135
- ```
136
-
137
- If all installation attempts fail, report the error message to the user. Do NOT attempt any method outside the commands above.
138
-
139
- ## Parameters
140
-
141
- | Parameter | Required/Optional | Description | Default |
142
- | ------------ | ----------------- | --------------------------------------------------------------------- | ------- |
143
- | `Keyword` | Optional | Search keyword (matched against name, description, triggers, service) | None |
144
- | `Category` | Optional | Category code for filtering (e.g., "computing", "storage", "network") | None |
145
- | `skill-name` | Required (Step 3) | Exact skill name for installing | None |
146
-
147
- ## Reference Documentation
148
-
149
- | Document | Description |
150
- | ---------------------------------------------------- | ------------------------------------------------------------------------------------- |
151
- | GitCode API v5 `index.json` | Skill index fetched via HTTP GET (base64 decoded) |
152
- | GitCode API v5 `cn-en-map.json` | Chinese-English keyword mapping fetched via HTTP GET (base64 decoded) |
153
- | [scripts/search-skills.py](scripts/search-skills.py) | Search script (Python) — fetches from GitCode API v5, expands keywords, scores, sorts |
154
-
155
- ## Search Heuristics
156
-
157
- > Optional reference for keyword-to-category hints. The agent can infer categories from `index.json` without these.
158
-
159
- - Cloud infrastructure keywords (ecs, bms, vpc, obs, rds, ...) → likely `computing`, `network`, `storage`, etc.
160
- - Tool keywords (cli, terraform, koo) → likely `devtools`
161
- - Management keywords (monitoring, alarm, log) → likely `monitoring`
162
-
163
- ## Troubleshooting
164
-
165
- ### Issue: Script fails with "Failed to fetch index.json"
166
-
167
- **Cause**: GitCode API v5 URL unreachable
168
- **Solution**: Verify network connectivity to `gitcode.com`
169
-
170
- ### Issue: GitCode API v5 returns 404
171
-
172
- **Cause**: File path incorrect or default branch is not `main`
173
- **Solution**: Verify the skill's `category`, `service`, and `name` from search results
174
-
175
- ### Issue: Search returns no results
176
-
177
- **Cause**: Keywords don't match any skill
178
- **Solution**:
179
-
180
- 1. Try broader keywords
181
- 2. Switch between Chinese and English keywords (e.g., "对象存储" → "obs")
182
- 3. List all skills: `python scripts/search-skills.py -c "computing"`
183
-
184
- ## Notes
185
-
186
- - This skill is **read-only** and does not create any cloud resources
187
- - **No cache management needed** — index is fetched fresh from GitCode API v5 each run
188
- - **Network required** — index data is hosted on GitCode, fetched via HTTP GET (base64 decoded)
189
- - **MUST use script to search** — do not read index.json directly
190
- - Index repo: `https://gitcode.com/2501_91318609/skills-for-index` (branch: `main`)
191
- - Skills repo: `https://github.com/huaweicloud/huaweicloud-skills` (branch: `master`)
@@ -1,171 +0,0 @@
1
- import { Buffer } from 'node:buffer';
2
-
3
- const INDEX_URL =
4
- 'https://gitcode.com/api/v5/repos/2501_91318609/skills-for-index/contents/skills-index/index.json?ref=main';
5
- const CN_EN_MAP_URL =
6
- 'https://gitcode.com/api/v5/repos/2501_91318609/skills-for-index/contents/skills-index/cn-en-map.json?ref=main';
7
-
8
- const GENERIC_KEYWORDS = new Set([
9
- '华为云',
10
- 'huawei',
11
- 'huawei cloud',
12
- '云',
13
- 'cloud',
14
- '技能',
15
- 'skill',
16
- 'skills',
17
- '所有',
18
- 'all',
19
- '全部',
20
- '有什么',
21
- '有哪些',
22
- '相关',
23
- '列表',
24
- 'list',
25
- '查找',
26
- '搜索',
27
- '发现',
28
- '浏览',
29
- 'find',
30
- 'search',
31
- 'discover',
32
- 'browse',
33
- 'show',
34
- 'explore',
35
- 'agent',
36
- '市场',
37
- 'market',
38
- '类目',
39
- 'category',
40
- '安装',
41
- 'install',
42
- ]);
43
-
44
- async function fetchJson(url, _label) {
45
- const resp = await fetch(url, { headers: { 'User-Agent': 'huaweicloud-devkit/1.0' } });
46
- const data = await resp.json();
47
- if (data?.encoding === 'base64' && data.content) {
48
- return JSON.parse(Buffer.from(data.content, 'base64').toString('utf8'));
49
- }
50
- return data;
51
- }
52
-
53
- function isGeneric(kw) {
54
- const k = kw.toLowerCase().trim();
55
- return GENERIC_KEYWORDS.has(k) || /华为云|huawei/i.test(k);
56
- }
57
-
58
- function expandKeywords(raw, cnEnMap) {
59
- if (!raw) return [[], []];
60
- const parts = raw.replace(/[,;]/g, ' ').split(/\s+/).filter(Boolean);
61
- const expanded = [...parts];
62
- for (const p of parts) {
63
- const lower = p.toLowerCase();
64
- if (cnEnMap[p]) expanded.push(cnEnMap[p]);
65
- for (const [cn, en] of Object.entries(cnEnMap)) {
66
- if (en === lower) expanded.push(cn);
67
- }
68
- }
69
- const unique = [...new Set(expanded)].sort();
70
- return [unique.filter((kw) => !isGeneric(kw)), unique.filter((kw) => isGeneric(kw))];
71
- }
72
-
73
- function scoreSkill(skill, specificKws, genericKws) {
74
- if (!specificKws.length && !genericKws.length) return [1, []];
75
- let total = 0;
76
- const matched = [];
77
- const nl = (skill.name || '').toLowerCase();
78
- const dl = (skill.description || '').toLowerCase();
79
- const sl = (skill.service || '').toLowerCase();
80
- const tl = (skill.triggers || []).map((t) => (t || '').toLowerCase());
81
-
82
- for (const kw of specificKws) {
83
- const k = kw.toLowerCase();
84
- let s = 0;
85
- if (nl.includes(k)) s += 10;
86
- else if (tl.some((t) => t.includes(k))) s += 8;
87
- else if (dl.includes(k)) s += 5;
88
- else if (sl.includes(k)) s += 3;
89
- if (s > 0) {
90
- total += s;
91
- matched.push(kw);
92
- }
93
- }
94
- for (const kw of genericKws) {
95
- const k = kw.toLowerCase();
96
- let s = 0;
97
- if (nl.includes(k)) s += 10;
98
- else if (tl.some((t) => t.includes(k))) s += 4;
99
- else if (dl.includes(k)) s += 2;
100
- else if (sl.includes(k)) s += 1;
101
- if (s > 0) {
102
- total += s;
103
- matched.push(kw);
104
- }
105
- }
106
- if (!specificKws.length && total === 0) {
107
- total = 1;
108
- if (dl.length > 20) total += 1;
109
- if (tl.length) total += 1;
110
- }
111
- return [total, matched];
112
- }
113
-
114
- async function main() {
115
- const keyword =
116
- process.argv
117
- .slice(2)
118
- .filter((a) => !a.startsWith('-'))
119
- .join(' ') || '';
120
- const catIdx = process.argv.indexOf('-c');
121
- const category = catIdx >= 0 ? process.argv[catIdx + 1] || '' : '';
122
-
123
- try {
124
- const [idx, cnEnMap] = await Promise.all([fetchJson(INDEX_URL, 'index'), fetchJson(CN_EN_MAP_URL, 'cn-en-map')]);
125
-
126
- if (!keyword && !category) {
127
- console.log(`Categories: ${(idx.categories || []).join(', ')}`);
128
- console.log('Usage: node search-skills.mjs <keyword> [-c <category>]');
129
- return;
130
- }
131
-
132
- const [specificKws, genericKws] = expandKeywords(keyword, cnEnMap);
133
- const hasSpecific = specificKws.length > 0;
134
-
135
- const results = [];
136
- for (const skill of idx.skills || []) {
137
- if (category && skill.category !== category) continue;
138
- const [score, matched] = scoreSkill(skill, specificKws, genericKws);
139
- if (hasSpecific && score === 0) continue;
140
- const desc = (skill.description || '').slice(0, 150);
141
- results.push({
142
- score,
143
- name: skill.name,
144
- category: skill.category,
145
- service: skill.service,
146
- description: desc + (skill.description?.length > 150 ? '...' : ''),
147
- triggers: (skill.triggers || []).slice(0, 5),
148
- matched,
149
- });
150
- }
151
- results.sort((a, b) => b.score - a.score);
152
-
153
- if (!results.length) {
154
- console.log(`No results. Try broader keywords, remove category filter, or switch CN↔EN.`);
155
- return;
156
- }
157
-
158
- console.log(`Found ${results.length} skill(s):`);
159
- for (const r of results) {
160
- console.log(
161
- ` [${r.score}pts] ${r.name} (${r.category}/${r.service})${r.matched.length ? ' matched: ' + r.matched.join(',') : ''}`,
162
- );
163
- console.log(` ${r.description}`);
164
- }
165
- } catch (e) {
166
- console.error(`Error: ${e.message}`);
167
- process.exit(1);
168
- }
169
- }
170
-
171
- main();