huaweicloud-devkit 1.1.3-next.1 → 1.1.3-next.3
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/.agents/plugins/marketplace.json +1 -1
- package/README.md +22 -6
- package/README.zh-CN.md +17 -7
- package/integrations/opencode/commands/huaweicloud-doctor.md +1 -1
- package/integrations/opencode/skills/huaweicloud-core/SKILL.md +1 -1
- package/package.json +7 -3
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -4
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +4 -4
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -4
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +4 -4
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -4
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +1 -1
- package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +25 -2
- package/plugins/huaweicloud-core/skills/huawei-iac/SKILL.md +36 -36
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +3 -1
- package/plugins/huaweicloud-core/src/auth/reconcile.mjs +7 -4
- package/plugins/huaweicloud-core/src/setup-cli.mjs +250 -16
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/huaweicloud-devkit)
|
|
7
|
+
[](https://github.com/huaweicloud/huaweicloud-devkit)
|
|
7
8
|
|
|
8
9
|
**[中文](README.zh-CN.md) | English**
|
|
9
10
|
|
|
@@ -31,7 +32,18 @@ Supports OpenCode, Codex, CodeArts Agent, WorkBuddy, DeepSeek Harness (DSH), Off
|
|
|
31
32
|
|
|
32
33
|
## Quick Start
|
|
33
34
|
|
|
34
|
-
> If `--target` is omitted, the installer auto-detects agents on your machine
|
|
35
|
+
> If `--target` is omitted, the installer auto-detects agents on your machine:
|
|
36
|
+
>
|
|
37
|
+
> - **None detected**: interactive terminals ask what you want (install to one
|
|
38
|
+
> explicit target / install to all / wire up a generic MCP agent);
|
|
39
|
+
> non-interactive shells error out with the supported target list.
|
|
40
|
+
> - **One detected**: installs directly to it.
|
|
41
|
+
> - **Multiple detected**: interactive terminals show a multi-select chooser;
|
|
42
|
+
> non-interactive shells error and point at `--target <agent>` / `--target all`.
|
|
43
|
+
> For a one-shot full setup, run `npx --yes huaweicloud-devkit install --target all`
|
|
44
|
+
> (Codex is skipped when its CLI is missing).
|
|
45
|
+
|
|
46
|
+
The commands below are global (they act on every agent):
|
|
35
47
|
|
|
36
48
|
```bash
|
|
37
49
|
npx --yes huaweicloud-devkit version # print CLI version and installed plugin versions per agent
|
|
@@ -51,7 +63,7 @@ npx --yes huaweicloud-devkit doctor --target opencode
|
|
|
51
63
|
npx --yes huaweicloud-devkit status --target opencode
|
|
52
64
|
npx --yes huaweicloud-devkit update --target opencode
|
|
53
65
|
npx --yes huaweicloud-devkit uninstall --target opencode
|
|
54
|
-
rm -rf ~/.npm/_npx/ # Linux/macOS
|
|
66
|
+
rm -rf ~/.npm/_npx/ # Linux/macOS; Windows: rmdir /s /q %LOCALAPPDATA%\npm-cache\_npx
|
|
55
67
|
```
|
|
56
68
|
|
|
57
69
|
### Codex
|
|
@@ -211,7 +223,7 @@ npx --yes huaweicloud-devkit install --target openclaw
|
|
|
211
223
|
npx --yes huaweicloud-devkit status --target openclaw
|
|
212
224
|
npx --yes huaweicloud-devkit update --target openclaw
|
|
213
225
|
npx --yes huaweicloud-devkit uninstall --target openclaw
|
|
214
|
-
rm -rf ~/.npm/_npx/ # Linux/macOS
|
|
226
|
+
rm -rf ~/.npm/_npx/ # Linux/macOS; Windows: rmdir /s /q %LOCALAPPDATA%\npm-cache\_npx
|
|
215
227
|
```
|
|
216
228
|
|
|
217
229
|
### AtomCode
|
|
@@ -256,7 +268,7 @@ If your agent supports `type: "remote"` (Streamable HTTP) instead of stdio, star
|
|
|
256
268
|
npx --yes huaweicloud-devkit-mcp --transport remote
|
|
257
269
|
```
|
|
258
270
|
|
|
259
|
-
It listens on `127.0.0.1:9528` by default
|
|
271
|
+
It listens on `127.0.0.1:9528` by default. Then connect with a remote config (opencode example):
|
|
260
272
|
|
|
261
273
|
```jsonc
|
|
262
274
|
{
|
|
@@ -312,7 +324,7 @@ npx --yes huaweicloud-devkit install --target all
|
|
|
312
324
|
### Update All Agents
|
|
313
325
|
|
|
314
326
|
```bash
|
|
315
|
-
npx huaweicloud-devkit version
|
|
327
|
+
npx --yes huaweicloud-devkit@latest version
|
|
316
328
|
npx --yes huaweicloud-devkit@latest update --target all
|
|
317
329
|
```
|
|
318
330
|
|
|
@@ -322,7 +334,7 @@ locally cached older one.
|
|
|
322
334
|
|
|
323
335
|
## What It Does
|
|
324
336
|
|
|
325
|
-
- **Guided cloud operations** — agents get step-by-step guidance for 20+ Huawei Cloud services (ECS, OBS, VPC, RDS, GaussDB, FunctionGraph, APIG, CCE, and more)
|
|
337
|
+
- **Guided cloud operations** — agents get step-by-step guidance for 20+ commonly used Huawei Cloud services (ECS, OBS, VPC, RDS, GaussDB, FunctionGraph, APIG, CCE, and more)
|
|
326
338
|
- **Safety-first execution** — all write operations require explicit user approval; credentials and secrets are automatically redacted from output
|
|
327
339
|
- **Pre-execution risk checks** — public exposure, credential leaks, and destructive operations are caught before they run
|
|
328
340
|
- **Regional awareness** — auto-discovers available regions and checks service availability before creating resources
|
|
@@ -332,6 +344,10 @@ locally cached older one.
|
|
|
332
344
|
|
|
333
345
|
ECS, OBS, VPC, IAM, RDS, GaussDB, FunctionGraph, APIG, CCE, SMN/DMS, ModelArts, Cloud Eye, CTS, DEW, Billing, CBR, WAF/AAD, DDS/DCS, Deployment, and Getting Started guides.
|
|
334
346
|
|
|
347
|
+
> Above is the pre-wired guidance list; the remaining 200+ Huawei Cloud services
|
|
348
|
+
> are still reachable via KooCLI / API / SDK routing (see capability-discovery
|
|
349
|
+
> and cli-and-auth meta-skills).
|
|
350
|
+
|
|
335
351
|
## Documentation
|
|
336
352
|
|
|
337
353
|
- [Architecture](docs/architecture.md)
|
package/README.zh-CN.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# HuaweiCloud DevKit
|
|
2
2
|
|
|
3
|
-
[](https://github.com/huaweicloud/huaweicloud-devkit/discussions)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/huaweicloud-devkit)
|
|
7
|
+
[](https://github.com/huaweicloud/huaweicloud-devkit)
|
|
7
8
|
|
|
8
9
|
**中文 | [English](README.md)**
|
|
9
10
|
|
|
@@ -31,7 +32,14 @@
|
|
|
31
32
|
|
|
32
33
|
## 快速开始
|
|
33
34
|
|
|
34
|
-
> 省略 `--target` 时,安装器会自动检测机器上的 agent
|
|
35
|
+
> 省略 `--target` 时,安装器会自动检测机器上的 agent:
|
|
36
|
+
>
|
|
37
|
+
> - **未检测到**:交互终端会询问你如何继续(指定 target 安装 / 全部安装 / 接入通用 MCP agent);非交互终端报错并列出支持列表。
|
|
38
|
+
> - **检测到单个**:直接安装到该 agent。
|
|
39
|
+
> - **检测到多个**:交互终端弹出多选;非交互终端报错并提示 `--target <agent>` 或 `--target all`。
|
|
40
|
+
> 需要一步全量安装时执行 `npx --yes huaweicloud-devkit install --target all`(Codex 缺少 CLI 时跳过)。
|
|
41
|
+
|
|
42
|
+
以下为全局命令(一次性作用于所有 agent):
|
|
35
43
|
|
|
36
44
|
```bash
|
|
37
45
|
npx --yes huaweicloud-devkit version # 查看 CLI 版本和各 agent 已安装的插件版本
|
|
@@ -51,7 +59,7 @@ npx --yes huaweicloud-devkit doctor --target opencode
|
|
|
51
59
|
npx --yes huaweicloud-devkit status --target opencode
|
|
52
60
|
npx --yes huaweicloud-devkit update --target opencode
|
|
53
61
|
npx --yes huaweicloud-devkit uninstall --target opencode
|
|
54
|
-
rm -rf ~/.npm/_npx/ # 仅 Linux/macOS;Windows
|
|
62
|
+
rm -rf ~/.npm/_npx/ # 仅 Linux/macOS;Windows:rmdir /s /q %LOCALAPPDATA%\npm-cache\_npx
|
|
55
63
|
```
|
|
56
64
|
|
|
57
65
|
### Codex
|
|
@@ -211,7 +219,7 @@ npx --yes huaweicloud-devkit install --target openclaw
|
|
|
211
219
|
npx --yes huaweicloud-devkit status --target openclaw
|
|
212
220
|
npx --yes huaweicloud-devkit update --target openclaw
|
|
213
221
|
npx --yes huaweicloud-devkit uninstall --target openclaw
|
|
214
|
-
rm -rf ~/.npm/_npx/ # 仅 Linux/macOS;Windows
|
|
222
|
+
rm -rf ~/.npm/_npx/ # 仅 Linux/macOS;Windows:rmdir /s /q %LOCALAPPDATA%\npm-cache\_npx
|
|
215
223
|
```
|
|
216
224
|
|
|
217
225
|
### AtomCode
|
|
@@ -256,7 +264,7 @@ npx --yes huaweicloud-devkit uninstall --target atomcode
|
|
|
256
264
|
npx --yes huaweicloud-devkit-mcp --transport remote
|
|
257
265
|
```
|
|
258
266
|
|
|
259
|
-
默认监听 `127.0.0.1:9528
|
|
267
|
+
默认监听 `127.0.0.1:9528`。随后以远程方式连接(以 opencode 为例):
|
|
260
268
|
|
|
261
269
|
```jsonc
|
|
262
270
|
{
|
|
@@ -312,7 +320,7 @@ npx --yes huaweicloud-devkit install --target all
|
|
|
312
320
|
### 更新所有 Agent
|
|
313
321
|
|
|
314
322
|
```bash
|
|
315
|
-
npx huaweicloud-devkit version
|
|
323
|
+
npx --yes huaweicloud-devkit@latest version
|
|
316
324
|
npx --yes huaweicloud-devkit@latest update --target all
|
|
317
325
|
```
|
|
318
326
|
|
|
@@ -320,7 +328,7 @@ npx --yes huaweicloud-devkit@latest update --target all
|
|
|
320
328
|
|
|
321
329
|
## 功能特性
|
|
322
330
|
|
|
323
|
-
- **引导式云操作** — Agent 获得 20+
|
|
331
|
+
- **引导式云操作** — Agent 获得 20+ 常用华为云服务的分步操作指引(ECS、OBS、VPC、RDS、GaussDB、FunctionGraph、APIG、CCE 等)
|
|
324
332
|
- **安全优先执行** — 所有写操作需用户明确批准;凭证和密钥自动脱敏
|
|
325
333
|
- **执行前风险检查** — 公网暴露、凭证泄露、破坏性操作在执行前即被拦截
|
|
326
334
|
- **区域感知** — 自动发现可用区域,创建资源前检查服务可用性
|
|
@@ -330,6 +338,8 @@ npx --yes huaweicloud-devkit@latest update --target all
|
|
|
330
338
|
|
|
331
339
|
ECS、OBS、VPC、IAM、RDS、GaussDB、FunctionGraph、APIG、CCE、SMN/DMS、ModelArts、Cloud Eye、CTS、DEW、Billing、CBR、WAF/AAD、DDS/DCS、Deployment,以及入门指南。
|
|
332
340
|
|
|
341
|
+
> 以上为预置指引的服务列表;其余 200+ 华为云服务仍可通过 KooCLI / API / SDK 路由调用(见 capability-discovery 与 cli-and-auth 元技能)。
|
|
342
|
+
|
|
333
343
|
## 文档
|
|
334
344
|
|
|
335
345
|
- [架构](docs/architecture.md)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: Self-check Huawei Cloud DevKit readiness — hcloud, MCP, skills, auth.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
Use HuaweiCloud
|
|
5
|
+
Use HuaweiCloud DevKit tools and skills to perform a comprehensive readiness check:
|
|
6
6
|
|
|
7
7
|
1. Check if KooCLI `hcloud` is installed: `huaweicloud_check_cli`. If not, guide user to install.
|
|
8
8
|
2. Check if hcloud has active credentials: `hcloud configure list` (redacted).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: huaweicloud-core
|
|
3
|
-
description: OpenCode entry skill for HuaweiCloud
|
|
3
|
+
description: OpenCode entry skill for HuaweiCloud DevKit. Use when a developer asks OpenCode to use Huawei Cloud Skills, KooCLI, APIs, SDKs, future MCP tools, or low-priority Terraform guidance.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Huawei Cloud Core For OpenCode
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"mcpName": "io.github.huaweicloud/huaweicloud-devkit",
|
|
4
|
-
"version": "1.1.3-next.
|
|
4
|
+
"version": "1.1.3-next.3",
|
|
5
5
|
"kooCliVersion": "7.2.12",
|
|
6
6
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
7
7
|
"type": "module",
|
|
@@ -33,10 +33,11 @@
|
|
|
33
33
|
"test": "node --test \"test/*.test.mjs\"",
|
|
34
34
|
"format": "prettier --write .",
|
|
35
35
|
"format:check": "prettier --check .",
|
|
36
|
-
"lint:md": "markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#.git\" \"#test/**\" \"#docs/**\"",
|
|
36
|
+
"lint:md": "markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#.git\" \"#test/**\" \"#docs/**\" \"#.superpowers/**\"",
|
|
37
37
|
"lint:js": "eslint .",
|
|
38
38
|
"lint": "npm run lint:md && npm run lint:js",
|
|
39
|
-
"validate": "node ./scripts/validate-package.mjs",
|
|
39
|
+
"validate": "node ./scripts/validate-package.mjs && node ./scripts/sync-readme-badge.mjs",
|
|
40
|
+
"badge:sync": "node ./scripts/sync-readme-badge.mjs --write",
|
|
40
41
|
"pack:verify": "node ./scripts/pack-verify.mjs",
|
|
41
42
|
"postinstall": "node ./bin/dsh-postinstall.cjs"
|
|
42
43
|
},
|
|
@@ -54,6 +55,9 @@
|
|
|
54
55
|
"markdownlint-cli2": "^0.23.2",
|
|
55
56
|
"prettier": "^3.9.6"
|
|
56
57
|
},
|
|
58
|
+
"overrides": {
|
|
59
|
+
"smol-toml": "1.8.0"
|
|
60
|
+
},
|
|
57
61
|
"keywords": [
|
|
58
62
|
"huaweicloud",
|
|
59
63
|
"huawei-cloud",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"interface": {
|
|
3
|
-
"displayName": "HuaweiCloud
|
|
4
|
-
"shortDescription": "HuaweiCloud
|
|
5
|
-
"longDescription": "HuaweiCloud
|
|
3
|
+
"displayName": "HuaweiCloud DevKit",
|
|
4
|
+
"shortDescription": "HuaweiCloud DevKit - Huawei Cloud guidance, CLI/API/SDK routing, MCP tools, and safety for coding agents.",
|
|
5
|
+
"longDescription": "HuaweiCloud DevKit helps coding agents choose and use Huawei Cloud Skills, KooCLI, APIs, SDKs, and MCP tools with less context, safer command execution, and more accurate cloud implementation decisions.",
|
|
6
6
|
"developerName": "HuaweiCloud Mate",
|
|
7
7
|
"category": "Cloud",
|
|
8
8
|
"capabilities": ["Read", "Interactive"],
|
|
@@ -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.1.3-next.
|
|
20
|
+
"version": "1.1.3-next.3",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"interface": {
|
|
3
|
-
"shortDescription": "HuaweiCloud
|
|
3
|
+
"shortDescription": "HuaweiCloud DevKit - Huawei Cloud guidance, CLI/API/SDK routing, MCP tools, and safety for coding agents.",
|
|
4
4
|
"developerName": "HuaweiCloud Mate",
|
|
5
5
|
"capabilities": ["Read", "Interactive"],
|
|
6
6
|
"brandColor": "#C7000B",
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
"Check my Huawei Cloud CLI setup safely.",
|
|
14
14
|
"Plan this Huawei Cloud API or SDK task."
|
|
15
15
|
],
|
|
16
|
-
"longDescription": "HuaweiCloud
|
|
16
|
+
"longDescription": "HuaweiCloud DevKit helps coding agents choose and use Huawei Cloud Skills, KooCLI, APIs, SDKs, and MCP tools with less context, safer command execution, and more accurate cloud implementation decisions.",
|
|
17
17
|
"websiteURL": "https://github.com/huaweicloud/HuaweiCloud-Devkit",
|
|
18
|
-
"displayName": "HuaweiCloud
|
|
18
|
+
"displayName": "HuaweiCloud DevKit"
|
|
19
19
|
},
|
|
20
20
|
"mcpServers": "./.mcp.json",
|
|
21
21
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
22
22
|
"skills": "./skills/",
|
|
23
|
-
"version": "1.1.3-next.
|
|
23
|
+
"version": "1.1.3-next.3",
|
|
24
24
|
"author": {
|
|
25
25
|
"name": "HuaweiCloud Mate",
|
|
26
26
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"interface": {
|
|
3
|
-
"displayName": "HuaweiCloud
|
|
4
|
-
"shortDescription": "HuaweiCloud
|
|
5
|
-
"longDescription": "HuaweiCloud
|
|
3
|
+
"displayName": "HuaweiCloud DevKit",
|
|
4
|
+
"shortDescription": "HuaweiCloud DevKit - Huawei Cloud guidance, CLI/API/SDK routing, MCP tools, and safety for coding agents.",
|
|
5
|
+
"longDescription": "HuaweiCloud DevKit helps coding agents choose and use Huawei Cloud Skills, KooCLI, APIs, SDKs, and MCP tools with less context, safer command execution, and more accurate cloud implementation decisions.",
|
|
6
6
|
"developerName": "HuaweiCloud Mate",
|
|
7
7
|
"category": "Cloud",
|
|
8
8
|
"capabilities": ["Read", "Interactive"],
|
|
@@ -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.1.3-next.
|
|
20
|
+
"version": "1.1.3-next.3",
|
|
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.1.3-next.
|
|
3
|
+
"version": "1.1.3-next.3",
|
|
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",
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"skills": "./skills/",
|
|
14
14
|
"mcpServers": "./.mcp.json",
|
|
15
15
|
"interface": {
|
|
16
|
-
"displayName": "HuaweiCloud
|
|
17
|
-
"shortDescription": "HuaweiCloud
|
|
18
|
-
"longDescription": "HuaweiCloud
|
|
16
|
+
"displayName": "HuaweiCloud DevKit",
|
|
17
|
+
"shortDescription": "HuaweiCloud DevKit - Huawei Cloud guidance, CLI/API/SDK routing, MCP tools, and safety for coding agents.",
|
|
18
|
+
"longDescription": "HuaweiCloud DevKit helps coding agents choose and use Huawei Cloud Skills, KooCLI, APIs, SDKs, and MCP tools with less context, safer command execution, and more accurate cloud implementation decisions.",
|
|
19
19
|
"developerName": "HuaweiCloud Mate",
|
|
20
20
|
"category": "Cloud",
|
|
21
21
|
"capabilities": ["Read", "Interactive"],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.3-next.
|
|
3
|
+
"version": "1.1.3-next.3",
|
|
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",
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"skills": "./skills/",
|
|
14
14
|
"mcpServers": "./.mcp.json",
|
|
15
15
|
"interface": {
|
|
16
|
-
"displayName": "HuaweiCloud
|
|
17
|
-
"shortDescription": "HuaweiCloud
|
|
18
|
-
"longDescription": "HuaweiCloud
|
|
16
|
+
"displayName": "HuaweiCloud DevKit",
|
|
17
|
+
"shortDescription": "HuaweiCloud DevKit - Huawei Cloud guidance, CLI/API/SDK routing, MCP tools, and safety for coding agents.",
|
|
18
|
+
"longDescription": "HuaweiCloud DevKit helps coding agents choose and use Huawei Cloud Skills, KooCLI, APIs, SDKs, and MCP tools with less context, safer command execution, and more accurate cloud implementation decisions.",
|
|
19
19
|
"developerName": "HuaweiCloud Mate",
|
|
20
20
|
"category": "Cloud",
|
|
21
21
|
"capabilities": ["Read", "Interactive"],
|
|
@@ -43,7 +43,7 @@ def load_policy():
|
|
|
43
43
|
SECRET_READ_RE = re.compile("|".join(re.escape(op) for op in blocked_secrets), re.I)
|
|
44
44
|
write_prefixes = policy.get("writeOperationPrefixes", [])
|
|
45
45
|
if write_prefixes:
|
|
46
|
-
WRITE_OPERATION_RE = re.compile(r"
|
|
46
|
+
WRITE_OPERATION_RE = re.compile(r"(^|[A-Za-z0-9])(" + "|".join(write_prefixes) + r")\w*", re.I)
|
|
47
47
|
except Exception:
|
|
48
48
|
pass
|
|
49
49
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"id": "huaweicloud-devkit",
|
|
4
4
|
"displayName": "HuaweiCloud DevKit",
|
|
5
|
-
"version": "1.1.3-next.
|
|
5
|
+
"version": "1.1.3-next.3",
|
|
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.",
|
|
@@ -231,17 +231,40 @@
|
|
|
231
231
|
"any": [
|
|
232
232
|
{
|
|
233
233
|
"field": "text",
|
|
234
|
-
"regex": "hcloud\\s+\\w+\\s+(Delete\\w
|
|
234
|
+
"regex": "hcloud\\s+\\w+\\s+(\\w*Delete\\w*|\\w*Detach\\w*|\\w*Remove\\w*|Dissociate\\w*|Unassign\\w*|Revoke\\w*)\\b"
|
|
235
235
|
},
|
|
236
236
|
{
|
|
237
237
|
"field": "text",
|
|
238
|
-
"regex": "hcloud\\s+\\w+\\s+(BatchDelete\\w
|
|
238
|
+
"regex": "hcloud\\s+\\w+\\s+(\\w*BatchDelete\\w*|\\w*BatchRemove\\w*)\\b"
|
|
239
|
+
}
|
|
240
|
+
],
|
|
241
|
+
"none": [
|
|
242
|
+
{
|
|
243
|
+
"field": "text",
|
|
244
|
+
"regex": "DeleteProtection"
|
|
239
245
|
}
|
|
240
246
|
]
|
|
241
247
|
},
|
|
242
248
|
"message": "This hcloud command will delete, detach, or remove cloud resources. The operation may be irreversible.",
|
|
243
249
|
"remediation": "List the resources to be affected first, confirm with the user, and require explicit approval before executing any destructive operation."
|
|
244
250
|
},
|
|
251
|
+
{
|
|
252
|
+
"id": "hwc-destructive-reset-operation",
|
|
253
|
+
"title": "Destructive resource reset operation",
|
|
254
|
+
"category": "destructive",
|
|
255
|
+
"severity": "warn",
|
|
256
|
+
"stages": ["command"],
|
|
257
|
+
"match": {
|
|
258
|
+
"any": [
|
|
259
|
+
{
|
|
260
|
+
"field": "text",
|
|
261
|
+
"regex": "hcloud\\s+\\w+\\s+(Batch)?Reset\\w*\\b"
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
},
|
|
265
|
+
"message": "This hcloud command resets a cloud resource (password, metadata, or instance state). This may disrupt access or mutate the resource.",
|
|
266
|
+
"remediation": "Confirm the reset target with the user and require explicit approval before executing."
|
|
267
|
+
},
|
|
245
268
|
{
|
|
246
269
|
"id": "hwc-destructive-delete-cascade",
|
|
247
270
|
"title": "Cascading cloud resource deletion",
|
|
@@ -21,16 +21,16 @@ Always run `hcloud <Service> <Operation> --help` before constructing commands to
|
|
|
21
21
|
|
|
22
22
|
**Proactive by design**: any deployment intent ("部署一个博客", "搭一套环境", "买个服务器") automatically triggers stages 1-5 BEFORE anything is created. The user NEVER needs to ask for an architecture proposal, cost estimate, or risk warnings - they are built into the flow. Only stages 6-8 (create / verify / manage) wait for explicit user confirmation at the approval gates.
|
|
23
23
|
|
|
24
|
-
| Stage | What happens | Hard rule
|
|
25
|
-
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
-
| 1. Scale assessment | **FIRST inspect the project code** (clone remote repos to a temp dir, or read the local path): tech stack, data services (Redis/MySQL/SQLite...), build artifacts, existing Dockerfile/k8s manifests → then classify or ask scale | **NEVER present deployment options (sandbox/ECS/OBS...) before the codebase has been inspected** - options without code analysis are guesses. If scale is not stated or clearly inferable, ASK - present the 3 tiers as options with what each includes and its monthly cost band (see `references/architectures.md`), and let the user choose. Never assume a tier. The scale question and the hosting-target question are SEPARATE questions on separate axes. FunctionGraph filesystem is ephemeral - never recommend SQLite/local files for persistent data
|
|
27
|
-
| 2. Frontend hosting choice | Present BOTH options: (a) OBS+CDN+DNS custom domain, (b) ECS+EIP | For SPA/static frontends (Vue/React dist), **OBS hosting must always appear as an option** with cost comparison against same-host Nginx - do not silently merge the frontend into a compute node. For option (a) three prerequisites: domain ownership verification (CDN requires a DNS record or file upload first - `CDN.00010185` otherwise), ICP filing check, DNS hosting check; domain registration link: https://www.huaweicloud.com/product/domain.html
|
|
28
|
-
| 3. Architecture proposal | ASCII topology tree with dependencies | User confirms before continuing. **Architecture decision points must be surfaced as explicit choices, never silently made**: (a) app depends on Redis/MySQL/Kafka etc. → managed service (DCS/RDS/DMS, billed) vs same-host install (cheap, ops burden) - state the trade-off and ask; (b) repo ships a Dockerfile / SWR image / k8s manifests → offer container path (Docker on ECS or CCE) alongside source-build path; (c) frontend hosting layer (Nginx same-host vs OBS+CDN). Silent defaults here are a correctness failure
|
|
29
|
-
| 4. Parameter discovery | `ListFlavors` / `ListImages` / `huaweicloud_list_regions` | Never hardcode flavor/image names
|
|
30
|
-
| 5. Cost + balance gate | Price every resource via BSS `ListOnDemandResourceRatings`, then query balance | Balance = 0 → the FIRST message must state: "您的余额为 0,按时长计费的资源(ECS/EIP/RDS
|
|
31
|
-
| 6. Provision | deployment_id, session state, batch approval, ordered creation | See "Provisioning Rules" below
|
|
32
|
-
| 7. Verify | `curl -I` the site / API; expect 200 | DNS CNAME may take minutes to propagate - not an error
|
|
33
|
-
| 8. Manage | Session-scoped status query and destroy | See "Destroy Rules" below
|
|
24
|
+
| Stage | What happens | Hard rule |
|
|
25
|
+
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
+
| 1. Scale assessment | **FIRST inspect the project code** (clone remote repos to a temp dir, or read the local path): tech stack, data services (Redis/MySQL/SQLite...), build artifacts, existing Dockerfile/k8s manifests → then classify or ask scale | **NEVER present deployment options (sandbox/ECS/OBS...) before the codebase has been inspected** - options without code analysis are guesses. If scale is not stated or clearly inferable, ASK - present the 3 tiers as options with what each includes and its monthly cost band (see `references/architectures.md`), and let the user choose. Never assume a tier. The scale question and the hosting-target question are SEPARATE questions on separate axes. FunctionGraph filesystem is ephemeral - never recommend SQLite/local files for persistent data |
|
|
27
|
+
| 2. Frontend hosting choice | Present BOTH options: (a) OBS+CDN+DNS custom domain, (b) ECS+EIP | For SPA/static frontends (Vue/React dist), **OBS hosting must always appear as an option** with cost comparison against same-host Nginx - do not silently merge the frontend into a compute node. For option (a) three prerequisites: domain ownership verification (CDN requires a DNS record or file upload first - `CDN.00010185` otherwise), ICP filing check, DNS hosting check; domain registration link: https://www.huaweicloud.com/product/domain.html |
|
|
28
|
+
| 3. Architecture proposal | ASCII topology tree with dependencies | User confirms before continuing. **Architecture decision points must be surfaced as explicit choices, never silently made**: (a) app depends on Redis/MySQL/Kafka etc. → managed service (DCS/RDS/DMS, billed) vs same-host install (cheap, ops burden) - state the trade-off and ask; (b) repo ships a Dockerfile / SWR image / k8s manifests → offer container path (Docker on ECS or CCE) alongside source-build path; (c) frontend hosting layer (Nginx same-host vs OBS+CDN). Silent defaults here are a correctness failure |
|
|
29
|
+
| 4. Parameter discovery | `ListFlavors` / `ListImages` / `huaweicloud_list_regions` | Never hardcode flavor/image names |
|
|
30
|
+
| 5. Cost + balance gate | Price every resource via BSS `ListOnDemandResourceRatings`, then query balance | Balance = 0 → the FIRST message must state: "您的余额为 0,按时长计费的资源(ECS/EIP/RDS 等)下单将失败(Ecs.7000),请先充值或使用免费额度"。充值入口:https://account.huaweicloud.com/usercenter/?region=cn-north-4#/userindex/balanceRecharge 。Remedy priority: (1) FIRST claim the one-time incentive voucher (huaweicloud_voucher_status → huaweicloud_voucher_claim) to offset usage fees; (2) then instruct the user to top up balance (via the link above) if pay-per-use orders are needed, otherwise orders fail with `Ecs.7000`; (3) free-tier resources (FunctionGraph, small OBS) proceed without any balance. Balance > 0 → report how long it can sustain (balance ÷ monthly estimate). **权限降级(实测)**:IAM 用户无 `billing:balance:view` 时余额查询报 `CBC.0151` → 不阻塞流程,明确告知"余额无法程序化查询,请自行核对;若下单报 Ecs.7000 即余额不足",领券后继续。定价编码逐服务查证,见 `references/resource-catalog.md` § Pricing |
|
|
31
|
+
| 6. Provision | deployment_id, session state, batch approval, ordered creation | See "Provisioning Rules" below |
|
|
32
|
+
| 7. Verify | `curl -I` the site / API; expect 200 | DNS CNAME may take minutes to propagate - not an error |
|
|
33
|
+
| 8. Manage | Session-scoped status query and destroy | See "Destroy Rules" below |
|
|
34
34
|
|
|
35
35
|
## Provisioning Rules (Stage 6)
|
|
36
36
|
|
|
@@ -52,32 +52,32 @@ Execution: show the to-be-deleted list for final confirmation → delete in **re
|
|
|
52
52
|
|
|
53
53
|
## Critical Warnings
|
|
54
54
|
|
|
55
|
-
| Trap | Why / Rule
|
|
56
|
-
| ---------------------------------------------------- |
|
|
57
|
-
| Stale `securityToken` poisons every hcloud call | Symptom: every API returns `APIGW.0301 Incorrect IAM authentication information` while OBS/obsutil still works. `configure set` refuses empty values and auth-sync never clears the field. Fix: `hcloud configure delete --cli-profile=default`, then re-run auth init with AK/SK only (no token)
|
|
58
|
-
| OBS writes bypass the REST write-prefix policy | `mb` / `cp` / `rm` / `chattri` are obsutil-style commands. ALWAYS route them through `huaweicloud_plan_cli_command` → approval → `huaweicloud_run_approved_command`. Never treat them as read-only
|
|
59
|
-
| `OBS mb` flag rules differ from cp/rm | `mb` does NOT accept `-f` (flag parse error) and REQUIRES explicit `-location=<region>` even with a regional endpoint (else `IllegalLocationConstraintException`)
|
|
60
|
-
| Duplicate `OBS mb` is idempotent success | Same-account same-name bucket creation returns success, not an error - never treat a second create as failure. OBS deletes are also idempotent (deleting a nonexistent object succeeds)
|
|
61
|
-
| Bucket ACL does not cascade to objects | Every uploaded object needs `-acl=public-read` at upload time (`-f` IS valid on cp/rm)
|
|
62
|
-
| OBS static website bucket name must equal the domain | e.g. bucket `www.example.com` for domain `www.example.com`
|
|
63
|
-
| VPC v3 API differs from older docs | v3 `CreateSecurityGroup` has NO `vpc_id` (account-scoped); `CreateSecurityGroupRule` uses `multiport=80` not `port_range_min/max`; `DeleteSubnet` requires `vpc_id` too. Always `--help` first
|
|
64
|
-
| Public exposure is auto-blocked | Risk rules deny `0.0.0.0/0` port rules at plan time. For test/dev use a narrower `remote_ip_prefix` (e.g. the VPC CIDR) or get user consent for a documented exception
|
|
65
|
-
| ECS CreateServers hidden required params | Besides flavor/image/nics/az it also needs `--server.vpcid` and `--server.root_volume.volumetype` - order submission fails without them
|
|
66
|
-
| FunctionGraph runtime/code enums are strict | runtime must be exact (`Node.js18.15`, `Python3.9`, `Go1.x`, ...); inline code is the PAIR `--package=default --code_type=inline` (`--package=inline` → `FSS.1006`); TIMER triggers use `--event_data.schedule_type=Rate --event_data.schedule=1m` (`@every` style → `FSS.1109`); DeleteFunction takes `--function_urn` WITHOUT `:latest`
|
|
67
|
-
| Keypairs: `NovaCreateKeypair`, not CreateKeypair | `ECS CreateKeypair` does NOT exist - use `hcloud ECS NovaCreateKeypair`; delete takes `--keypair_name` (not `--keypair.name`). The API-generated `private_key` is MASKED in tool output - for SSH access create the keypair in the console or import the user's own public key, then bind with `--server.key_name=<n>`
|
|
68
|
-
| Long inline args break MCP JSON | Values >~2KB inline (e.g. `--server.user_data=<base64 cloud-init>`) fail JSON parsing silently. Write a local JSON file and pass `--cli-jsonInput=<file>` (see `references/resource-catalog.md` header note)
|
|
69
|
-
| EIP quota fails at RUN stage, not at plan | `EIP.7905 Quota exceeded` appears only after plan+approval - pre-check quota during stage 5 (and count precisely: `ListPublicips --limit=50` truncates, a "50/50" read on a 59-EIP account is wrong). Released quotas may lag minutes before `EIP.7905` clears
|
|
70
|
-
| Zero balance blocks order submission | ECS creation fails with `Ecs.7000 Insufficient account balance` when cash + voucher accounts are 0. This is the stage-5 gate materialized: stop, mark `partial`, tell the user pay-by-duration resources need a
|
|
71
|
-
| CDN mainland acceleration requires ICP filing | Unregistered domains cannot go live on CDN - check before stage 3. CDN create ALSO requires domain ownership verification first (`CDN.00010185 Verify domain ownership failed` - user must add a DNS record or upload a verification file)
|
|
72
|
-
| CDN is a global service in KooCLI | CDN operations only accept `--cli-region=cn-north-1` or `ap-southeast-1` - never the resource region
|
|
73
|
-
| OBS static website hosting has NO CLI path | `chattri` only sets ACL/storage-class; obsutil has no website command. Options: console setup, or CDN OBS-origin with `--domain.sources.1.enable_obs_web_hosting=1`. Without website hosting, verify via direct object URLs (still public HTTP 200)
|
|
74
|
-
| DNS hosting location | `hcloud DNS ListPublicZones` decides: hosted in Huawei Cloud → auto-create CNAME record set; hosted elsewhere → output the CNAME value for manual setup at the user's DNS provider
|
|
75
|
-
| EIP bills when idle; stopped ECS still bills | Include in cost estimate; destroy unattached EIPs
|
|
76
|
-
| ECS delete defaults leak resources | `--delete_publicip` and `--delete_volume` are false by default - set `true` when tearing down
|
|
77
|
-
| EIP binding type is non-obvious | `--publicip.associate_instance_type=PORT` (not INSTANCE)
|
|
78
|
-
| State is session-only | Temp-dir file, dies with the session. Never promise cross-session deploy management
|
|
79
|
-
| State file must be UTF-8 WITHOUT BOM | PowerShell `Set-Content -Encoding utf8` adds a BOM that breaks JSON parsers - write state with Node or another BOM-less writer
|
|
80
|
-
| HTTPS needs a certificate | CDN serves plain HTTP by default; SSL cert (upload or SCM) required for HTTPS
|
|
55
|
+
| Trap | Why / Rule |
|
|
56
|
+
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
57
|
+
| Stale `securityToken` poisons every hcloud call | Symptom: every API returns `APIGW.0301 Incorrect IAM authentication information` while OBS/obsutil still works. `configure set` refuses empty values and auth-sync never clears the field. Fix: `hcloud configure delete --cli-profile=default`, then re-run auth init with AK/SK only (no token) |
|
|
58
|
+
| OBS writes bypass the REST write-prefix policy | `mb` / `cp` / `rm` / `chattri` are obsutil-style commands. ALWAYS route them through `huaweicloud_plan_cli_command` → approval → `huaweicloud_run_approved_command`. Never treat them as read-only |
|
|
59
|
+
| `OBS mb` flag rules differ from cp/rm | `mb` does NOT accept `-f` (flag parse error) and REQUIRES explicit `-location=<region>` even with a regional endpoint (else `IllegalLocationConstraintException`) |
|
|
60
|
+
| Duplicate `OBS mb` is idempotent success | Same-account same-name bucket creation returns success, not an error - never treat a second create as failure. OBS deletes are also idempotent (deleting a nonexistent object succeeds) |
|
|
61
|
+
| Bucket ACL does not cascade to objects | Every uploaded object needs `-acl=public-read` at upload time (`-f` IS valid on cp/rm) |
|
|
62
|
+
| OBS static website bucket name must equal the domain | e.g. bucket `www.example.com` for domain `www.example.com` |
|
|
63
|
+
| VPC v3 API differs from older docs | v3 `CreateSecurityGroup` has NO `vpc_id` (account-scoped); `CreateSecurityGroupRule` uses `multiport=80` not `port_range_min/max`; `DeleteSubnet` requires `vpc_id` too. Always `--help` first |
|
|
64
|
+
| Public exposure is auto-blocked | Risk rules deny `0.0.0.0/0` port rules at plan time. For test/dev use a narrower `remote_ip_prefix` (e.g. the VPC CIDR) or get user consent for a documented exception |
|
|
65
|
+
| ECS CreateServers hidden required params | Besides flavor/image/nics/az it also needs `--server.vpcid` and `--server.root_volume.volumetype` - order submission fails without them |
|
|
66
|
+
| FunctionGraph runtime/code enums are strict | runtime must be exact (`Node.js18.15`, `Python3.9`, `Go1.x`, ...); inline code is the PAIR `--package=default --code_type=inline` (`--package=inline` → `FSS.1006`); TIMER triggers use `--event_data.schedule_type=Rate --event_data.schedule=1m` (`@every` style → `FSS.1109`); DeleteFunction takes `--function_urn` WITHOUT `:latest` |
|
|
67
|
+
| Keypairs: `NovaCreateKeypair`, not CreateKeypair | `ECS CreateKeypair` does NOT exist - use `hcloud ECS NovaCreateKeypair`; delete takes `--keypair_name` (not `--keypair.name`). The API-generated `private_key` is MASKED in tool output - for SSH access create the keypair in the console or import the user's own public key, then bind with `--server.key_name=<n>` |
|
|
68
|
+
| Long inline args break MCP JSON | Values >~2KB inline (e.g. `--server.user_data=<base64 cloud-init>`) fail JSON parsing silently. Write a local JSON file and pass `--cli-jsonInput=<file>` (see `references/resource-catalog.md` header note) |
|
|
69
|
+
| EIP quota fails at RUN stage, not at plan | `EIP.7905 Quota exceeded` appears only after plan+approval - pre-check quota during stage 5 (and count precisely: `ListPublicips --limit=50` truncates, a "50/50" read on a 59-EIP account is wrong). Released quotas may lag minutes before `EIP.7905` clears |
|
|
70
|
+
| Zero balance blocks order submission | ECS creation fails with `Ecs.7000 Insufficient account balance` when cash + voucher accounts are 0. This is the stage-5 gate materialized: stop, mark `partial`, tell the user pay-by-duration resources need a balance top-up. Free-tier resources (FunctionGraph, small OBS) DO work at 0 balance - the gate applies to order-submission resources only |
|
|
71
|
+
| CDN mainland acceleration requires ICP filing | Unregistered domains cannot go live on CDN - check before stage 3. CDN create ALSO requires domain ownership verification first (`CDN.00010185 Verify domain ownership failed` - user must add a DNS record or upload a verification file) |
|
|
72
|
+
| CDN is a global service in KooCLI | CDN operations only accept `--cli-region=cn-north-1` or `ap-southeast-1` - never the resource region |
|
|
73
|
+
| OBS static website hosting has NO CLI path | `chattri` only sets ACL/storage-class; obsutil has no website command. Options: console setup, or CDN OBS-origin with `--domain.sources.1.enable_obs_web_hosting=1`. Without website hosting, verify via direct object URLs (still public HTTP 200) |
|
|
74
|
+
| DNS hosting location | `hcloud DNS ListPublicZones` decides: hosted in Huawei Cloud → auto-create CNAME record set; hosted elsewhere → output the CNAME value for manual setup at the user's DNS provider |
|
|
75
|
+
| EIP bills when idle; stopped ECS still bills | Include in cost estimate; destroy unattached EIPs |
|
|
76
|
+
| ECS delete defaults leak resources | `--delete_publicip` and `--delete_volume` are false by default - set `true` when tearing down |
|
|
77
|
+
| EIP binding type is non-obvious | `--publicip.associate_instance_type=PORT` (not INSTANCE) |
|
|
78
|
+
| State is session-only | Temp-dir file, dies with the session. Never promise cross-session deploy management |
|
|
79
|
+
| State file must be UTF-8 WITHOUT BOM | PowerShell `Set-Content -Encoding utf8` adds a BOM that breaks JSON parsers - write state with Node or another BOM-less writer |
|
|
80
|
+
| HTTPS needs a certificate | CDN serves plain HTTP by default; SSL cert (upload or SCM) required for HTTPS |
|
|
81
81
|
|
|
82
82
|
## MCP Tools
|
|
83
83
|
|
|
@@ -43,7 +43,9 @@ export function globalCredentialsPath() {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export function obsConfigPath() {
|
|
46
|
-
|
|
46
|
+
// obsutil reads its config from a fixed location (~/.obsutilconfig), independent
|
|
47
|
+
// of HUAWEICLOUD_HOME. HCLOUD_OBS_CONFIG_PATH exists solely for hermetic test injection.
|
|
48
|
+
return process.env.HCLOUD_OBS_CONFIG_PATH || join(homedir(), '.obsutilconfig');
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export function readGlobalCredentials() {
|
|
@@ -16,8 +16,11 @@ import { redactSecrets } from '../safety-policy.mjs';
|
|
|
16
16
|
|
|
17
17
|
export { hasRuntimeCredentials };
|
|
18
18
|
|
|
19
|
-
function
|
|
20
|
-
|
|
19
|
+
export function kooCliConfigPath() {
|
|
20
|
+
// KooCLI keeps its config at a fixed location (~/.hcloud/config.json),
|
|
21
|
+
// independent of HUAWEICLOUD_HOME (that env only relocates devkit's own S1/S3).
|
|
22
|
+
// HCLOUD_CONFIG_PATH exists solely for hermetic test injection.
|
|
23
|
+
return process.env.HCLOUD_CONFIG_PATH || join(homedir(), '.hcloud', 'config.json');
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export function fingerprint(ak, sk) {
|
|
@@ -38,7 +41,7 @@ export function isManualModified(path) {
|
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
export function readKooCliProfiles() {
|
|
41
|
-
const configPath =
|
|
44
|
+
const configPath = kooCliConfigPath();
|
|
42
45
|
if (!existsSync(configPath)) return { error: 'KooCLI config not found' };
|
|
43
46
|
try {
|
|
44
47
|
const raw = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
@@ -129,7 +132,7 @@ export function scanState() {
|
|
|
129
132
|
store: 'S2-current',
|
|
130
133
|
source: 'KooCLI current profile',
|
|
131
134
|
fingerprint: currentFp,
|
|
132
|
-
manualModified: isManualModified(kooCli.configPath ||
|
|
135
|
+
manualModified: isManualModified(kooCli.configPath || kooCliConfigPath()),
|
|
133
136
|
});
|
|
134
137
|
}
|
|
135
138
|
if (s1Fingerprint && s3Fingerprint && s1Fingerprint !== s3Fingerprint) {
|
|
@@ -1047,7 +1047,7 @@ async function installCodexDesktop() {
|
|
|
1047
1047
|
|
|
1048
1048
|
// Register in personal marketplace (Codex discovers plugins from ~/.agents/plugins/marketplace.json)
|
|
1049
1049
|
ensureCodexMarketplaceEntry();
|
|
1050
|
-
console.log(' \x1b[33m请到插件 → 个人 → HuaweiCloud
|
|
1050
|
+
console.log(' \x1b[33m请到插件 → 个人 → HuaweiCloud DevKit → 安装\x1b[0m');
|
|
1051
1051
|
|
|
1052
1052
|
// Clean up old install locations from pre-marketplace era
|
|
1053
1053
|
removeIfExists(join(homedir(), '.agents', 'skills'));
|
|
@@ -3009,7 +3009,7 @@ function opencodeStatus() {
|
|
|
3009
3009
|
}
|
|
3010
3010
|
}
|
|
3011
3011
|
|
|
3012
|
-
function
|
|
3012
|
+
function detectAgents() {
|
|
3013
3013
|
const checks = [
|
|
3014
3014
|
['opencode', () => existsSync(join(homedir(), '.config', 'opencode'))],
|
|
3015
3015
|
['codex-desktop', () => existsSync(join(homedir(), '.codex'))],
|
|
@@ -3028,7 +3028,11 @@ function autoDetectTarget() {
|
|
|
3028
3028
|
['openclaw', () => existsSync(join(homedir(), '.openclaw'))],
|
|
3029
3029
|
['atomcode', () => existsSync(atomcodeHome())],
|
|
3030
3030
|
];
|
|
3031
|
-
|
|
3031
|
+
return checks.filter(([, check]) => check()).map(([name]) => name);
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
function autoDetectTarget() {
|
|
3035
|
+
const detected = detectAgents();
|
|
3032
3036
|
if (detected.length === 0) {
|
|
3033
3037
|
console.error('No supported agent detected.');
|
|
3034
3038
|
console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
|
|
@@ -3039,6 +3043,208 @@ function autoDetectTarget() {
|
|
|
3039
3043
|
return 'all';
|
|
3040
3044
|
}
|
|
3041
3045
|
|
|
3046
|
+
const AGENT_LABELS = {
|
|
3047
|
+
opencode: 'OpenCode',
|
|
3048
|
+
'codex-desktop': 'Codex Desktop',
|
|
3049
|
+
codearts: 'CodeArts',
|
|
3050
|
+
'codearts-work': 'CodeArts Work',
|
|
3051
|
+
workbuddy: 'WorkBuddy',
|
|
3052
|
+
dsh: 'DSH',
|
|
3053
|
+
officeace: 'OfficeAce',
|
|
3054
|
+
hermes: 'Hermes',
|
|
3055
|
+
openclaw: 'OpenClaw',
|
|
3056
|
+
atomcode: 'AtomCode',
|
|
3057
|
+
};
|
|
3058
|
+
|
|
3059
|
+
function promptSelectAgents(detected) {
|
|
3060
|
+
if (!process.stdin.isTTY) {
|
|
3061
|
+
console.error(
|
|
3062
|
+
`Multiple agents detected (${detected.join(', ')}). Cannot prompt in a non-interactive shell — ` +
|
|
3063
|
+
'specify `--target <agent>` or `--target all`.',
|
|
3064
|
+
);
|
|
3065
|
+
return Promise.resolve(null);
|
|
3066
|
+
}
|
|
3067
|
+
const question = [
|
|
3068
|
+
'Multiple agents detected. Select which to install (multi-select):',
|
|
3069
|
+
...detected.map((name, i) => ` ${i + 1}) ${AGENT_LABELS[name] || name}`),
|
|
3070
|
+
'Enter numbers separated by commas/spaces, "all" for every detected, Enter to install all detected, or "0" to cancel: ',
|
|
3071
|
+
].join('\n');
|
|
3072
|
+
return new Promise((resolve) => {
|
|
3073
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3074
|
+
rl.question(question, (a) => {
|
|
3075
|
+
rl.close();
|
|
3076
|
+
const answer = (a || '').trim().toLowerCase();
|
|
3077
|
+
if (answer === '0' || answer === 'n' || answer === 'no' || answer === 'q') {
|
|
3078
|
+
resolve(null);
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
if (answer === '' || answer === 'all') {
|
|
3082
|
+
resolve(detected.slice());
|
|
3083
|
+
return;
|
|
3084
|
+
}
|
|
3085
|
+
const parts = answer.split(/[\s,,]+/).filter(Boolean);
|
|
3086
|
+
const indices = new Set();
|
|
3087
|
+
for (const part of parts) {
|
|
3088
|
+
const n = Number(part);
|
|
3089
|
+
if (Number.isSafeInteger(n) && n >= 1 && n <= detected.length) {
|
|
3090
|
+
indices.add(n - 1);
|
|
3091
|
+
} else {
|
|
3092
|
+
console.error(`Invalid selection: "${part}". Installing all detected.`);
|
|
3093
|
+
resolve(detected.slice());
|
|
3094
|
+
return;
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
resolve([...indices].map((i) => detected[i]));
|
|
3098
|
+
});
|
|
3099
|
+
});
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
const MCP_ENTRY = {
|
|
3103
|
+
command: 'npx',
|
|
3104
|
+
args: ['-y', '-p', 'huaweicloud-devkit', 'huaweicloud-devkit-mcp'],
|
|
3105
|
+
};
|
|
3106
|
+
|
|
3107
|
+
function promptZeroDetect() {
|
|
3108
|
+
if (!process.stdin.isTTY) {
|
|
3109
|
+
console.error('No supported agent detected.');
|
|
3110
|
+
console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
|
|
3111
|
+
console.error('Use --target <agent> to specify.');
|
|
3112
|
+
return Promise.resolve({ abort: true });
|
|
3113
|
+
}
|
|
3114
|
+
const question = [
|
|
3115
|
+
'No supported agent detected.',
|
|
3116
|
+
'What would you like to do?',
|
|
3117
|
+
' 1) Install to a specific agent (e.g. codex)',
|
|
3118
|
+
' 2) Install to all supported agents (--target all)',
|
|
3119
|
+
' 3) Wire up a generic MCP agent (Claude Code / Cursor / custom MCP client)',
|
|
3120
|
+
' 0) Exit',
|
|
3121
|
+
'Enter choice: ',
|
|
3122
|
+
].join('\n');
|
|
3123
|
+
return new Promise((resolve) => {
|
|
3124
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3125
|
+
const ask = () => {
|
|
3126
|
+
rl.question(question, (a) => {
|
|
3127
|
+
const answer = (a || '').trim().toLowerCase();
|
|
3128
|
+
if (answer === '1') {
|
|
3129
|
+
rl.question('Enter agent target (e.g. codex): ', (t) => {
|
|
3130
|
+
const val = (t || '').trim().toLowerCase();
|
|
3131
|
+
rl.close();
|
|
3132
|
+
if (val === 'all' || SUPPORTED_AGENT_TARGETS.includes(val)) {
|
|
3133
|
+
resolve({ explicit: true, target: val });
|
|
3134
|
+
} else {
|
|
3135
|
+
console.error(`Unknown target: ${val}`);
|
|
3136
|
+
console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
|
|
3137
|
+
resolve({ abort: true });
|
|
3138
|
+
}
|
|
3139
|
+
});
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
if (answer === '2') {
|
|
3143
|
+
rl.close();
|
|
3144
|
+
resolve({ explicit: true, target: 'all' });
|
|
3145
|
+
return;
|
|
3146
|
+
}
|
|
3147
|
+
if (answer === '3') {
|
|
3148
|
+
rl.close();
|
|
3149
|
+
resolve({ mcp: true });
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
if (answer === '' || answer === '0' || answer === 'n' || answer === 'no' || answer === 'q') {
|
|
3153
|
+
rl.close();
|
|
3154
|
+
resolve({ abort: true });
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3157
|
+
console.error(`Invalid choice: "${answer}"`);
|
|
3158
|
+
ask();
|
|
3159
|
+
});
|
|
3160
|
+
};
|
|
3161
|
+
ask();
|
|
3162
|
+
});
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
function printMCPConfigSnippet(reason) {
|
|
3166
|
+
console.log(` ${reason}`);
|
|
3167
|
+
console.log(' Add this to your agent MCP config (stdio):');
|
|
3168
|
+
const snippet = JSON.stringify({ mcpServers: { 'huaweicloud-devkit': MCP_ENTRY } }, null, 2)
|
|
3169
|
+
.split('\n')
|
|
3170
|
+
.map((line) => ` ${line}`)
|
|
3171
|
+
.join('\n');
|
|
3172
|
+
console.log(snippet);
|
|
3173
|
+
console.log(' Or run the remote server and use a remote (Streamable HTTP) config:');
|
|
3174
|
+
console.log(' npx --yes huaweicloud-devkit-mcp --transport remote # listens on 127.0.0.1:9528');
|
|
3175
|
+
console.log(' See README → "Other Agents" for details.');
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
function configureMCPAgent(targetFile, agentLabel) {
|
|
3179
|
+
let config = {};
|
|
3180
|
+
let existed = false;
|
|
3181
|
+
if (existsSync(targetFile)) {
|
|
3182
|
+
existed = true;
|
|
3183
|
+
try {
|
|
3184
|
+
config = JSON.parse(readFileSync(targetFile, 'utf8'));
|
|
3185
|
+
} catch {
|
|
3186
|
+
console.error(` [${agentLabel}] ${targetFile} is not valid JSON; leaving it untouched.`);
|
|
3187
|
+
return false;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
config.mcpServers = config.mcpServers || {};
|
|
3191
|
+
if (config.mcpServers['huaweicloud-devkit']) {
|
|
3192
|
+
console.log(` [${agentLabel}] mcpServers.huaweicloud-devkit already configured; skipping.`);
|
|
3193
|
+
return true;
|
|
3194
|
+
}
|
|
3195
|
+
if (existed) copyFileSync(targetFile, `${targetFile}.bak`);
|
|
3196
|
+
config.mcpServers['huaweicloud-devkit'] = { ...MCP_ENTRY };
|
|
3197
|
+
mkdirSync(dirname(targetFile), { recursive: true });
|
|
3198
|
+
writeFileSync(targetFile, JSON.stringify(config, null, 2), 'utf8');
|
|
3199
|
+
console.log(` [${agentLabel}] MCP server configured in ${targetFile}. Restart the session to apply.`);
|
|
3200
|
+
return true;
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
function configureGenericMCP() {
|
|
3204
|
+
console.log('Configuring a generic MCP agent (MCP tools only; skills/hooks are not installed)...');
|
|
3205
|
+
const targets = [];
|
|
3206
|
+
const claudeFile = process.env.CLAUDE_CONFIG_DIR
|
|
3207
|
+
? join(process.env.CLAUDE_CONFIG_DIR, '.claude.json')
|
|
3208
|
+
: join(homedir(), '.claude.json');
|
|
3209
|
+
if (existsSync(claudeFile)) targets.push(['Claude Code', claudeFile]);
|
|
3210
|
+
const cursorFile = join(homedir(), '.cursor', 'mcp.json');
|
|
3211
|
+
if (existsSync(cursorFile) || existsSync(join(homedir(), '.cursor'))) {
|
|
3212
|
+
targets.push(['Cursor', cursorFile]);
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
if (targets.length === 0) {
|
|
3216
|
+
printMCPConfigSnippet('No known MCP agent detected; here is a config snippet you can paste:');
|
|
3217
|
+
return false;
|
|
3218
|
+
}
|
|
3219
|
+
let anyConfigured = false;
|
|
3220
|
+
for (const [label, file] of targets) {
|
|
3221
|
+
if (configureMCPAgent(file, label)) anyConfigured = true;
|
|
3222
|
+
}
|
|
3223
|
+
if (!anyConfigured) {
|
|
3224
|
+
console.log(' Nothing to configure for the detected MCP agents.');
|
|
3225
|
+
}
|
|
3226
|
+
return anyConfigured;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
async function resolveInstallTarget() {
|
|
3230
|
+
const idx = process.argv.indexOf('--target');
|
|
3231
|
+
if (idx >= 0) {
|
|
3232
|
+
const val = (process.argv[idx + 1] || '').toLowerCase();
|
|
3233
|
+
if (val === 'all' || SUPPORTED_AGENT_TARGETS.includes(val)) {
|
|
3234
|
+
return { explicit: true, target: val };
|
|
3235
|
+
}
|
|
3236
|
+
console.error(`Unknown target: ${val}`);
|
|
3237
|
+
console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
|
|
3238
|
+
process.exit(1);
|
|
3239
|
+
}
|
|
3240
|
+
const detected = detectAgents();
|
|
3241
|
+
if (detected.length === 1) return { explicit: true, target: detected[0] };
|
|
3242
|
+
if (detected.length === 0) return promptZeroDetect();
|
|
3243
|
+
const chosen = await promptSelectAgents(detected);
|
|
3244
|
+
if (!chosen) return { abort: true };
|
|
3245
|
+
return { explicit: false, subset: chosen };
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3042
3248
|
function parseTarget() {
|
|
3043
3249
|
const idx = process.argv.indexOf('--target');
|
|
3044
3250
|
if (idx < 0) return autoDetectTarget();
|
|
@@ -3084,13 +3290,37 @@ function writeInstallMarker(target) {
|
|
|
3084
3290
|
}
|
|
3085
3291
|
|
|
3086
3292
|
async function cmdInstall() {
|
|
3087
|
-
const target = parseTarget();
|
|
3088
3293
|
console.log(BANNER);
|
|
3089
|
-
|
|
3294
|
+
const plan = await resolveInstallTarget();
|
|
3295
|
+
if (plan.abort) {
|
|
3296
|
+
process.exitCode = 1;
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
3299
|
+
if (plan.mcp) {
|
|
3300
|
+
const ok = configureGenericMCP();
|
|
3301
|
+
process.exitCode = ok ? 0 : 1;
|
|
3302
|
+
return;
|
|
3303
|
+
}
|
|
3304
|
+
const hasExplicitTarget = plan.explicit;
|
|
3305
|
+
const target = plan.explicit ? plan.target : null;
|
|
3306
|
+
const selected = new Set(plan.explicit ? [] : plan.subset);
|
|
3307
|
+
|
|
3308
|
+
if (hasExplicitTarget) {
|
|
3309
|
+
console.log(`Installing HuaweiCloud DevKit for ${target}...\n`);
|
|
3310
|
+
} else {
|
|
3311
|
+
console.log(`Installing HuaweiCloud DevKit to ${[...selected].join(', ')}...\n`);
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3090
3314
|
checkNode();
|
|
3091
3315
|
checkForUpdate();
|
|
3092
3316
|
const installFailures = [];
|
|
3093
3317
|
|
|
3318
|
+
function shouldInstall(name) {
|
|
3319
|
+
if (name === 'codex') return hasExplicitTarget && (target === 'codex' || target === 'all');
|
|
3320
|
+
if (hasExplicitTarget) return target === name || target === 'all';
|
|
3321
|
+
return selected.has(name);
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3094
3324
|
async function runInstallStep(stepTarget, title, fn) {
|
|
3095
3325
|
console.log(title);
|
|
3096
3326
|
try {
|
|
@@ -3108,37 +3338,37 @@ async function cmdInstall() {
|
|
|
3108
3338
|
}
|
|
3109
3339
|
}
|
|
3110
3340
|
|
|
3111
|
-
if (
|
|
3341
|
+
if (shouldInstall('opencode')) {
|
|
3112
3342
|
await runInstallStep('opencode', '[OpenCode]', installOpenCode);
|
|
3113
3343
|
}
|
|
3114
|
-
if (
|
|
3344
|
+
if (shouldInstall('codex-desktop')) {
|
|
3115
3345
|
await runInstallStep('codex-desktop', '\n[Codex Desktop]', installCodexDesktop);
|
|
3116
3346
|
}
|
|
3117
|
-
if (
|
|
3347
|
+
if (shouldInstall('codearts')) {
|
|
3118
3348
|
await runInstallStep('codearts', '\n[CodeArts]', installCodeArts);
|
|
3119
3349
|
}
|
|
3120
|
-
if (
|
|
3350
|
+
if (shouldInstall('codearts-work')) {
|
|
3121
3351
|
await runInstallStep('codearts-work', '\n[CodeArts Work]', installCodeArtsWork);
|
|
3122
3352
|
}
|
|
3123
|
-
if (
|
|
3353
|
+
if (shouldInstall('workbuddy')) {
|
|
3124
3354
|
await runInstallStep('workbuddy', '\n[WorkBuddy]', installWorkBuddy);
|
|
3125
3355
|
}
|
|
3126
|
-
if (
|
|
3356
|
+
if (shouldInstall('dsh')) {
|
|
3127
3357
|
await runInstallStep('dsh', '\n[DSH]', installDsh);
|
|
3128
3358
|
}
|
|
3129
|
-
if (
|
|
3359
|
+
if (shouldInstall('officeace')) {
|
|
3130
3360
|
await runInstallStep('officeace', '\n[OfficeAce]', installOfficeAce);
|
|
3131
3361
|
}
|
|
3132
|
-
if (
|
|
3362
|
+
if (shouldInstall('hermes')) {
|
|
3133
3363
|
await runInstallStep('hermes', '\n[Hermes Agent]', installHermes);
|
|
3134
3364
|
}
|
|
3135
|
-
if (
|
|
3365
|
+
if (shouldInstall('openclaw')) {
|
|
3136
3366
|
await runInstallStep('openclaw', '\n[OpenClaw]', installOpenClaw);
|
|
3137
3367
|
}
|
|
3138
|
-
if (
|
|
3368
|
+
if (shouldInstall('atomcode')) {
|
|
3139
3369
|
await runInstallStep('atomcode', '\n[AtomCode]', installAtomCode);
|
|
3140
3370
|
}
|
|
3141
|
-
if (
|
|
3371
|
+
if (shouldInstall('codex')) {
|
|
3142
3372
|
await runInstallStep('codex', '\n[Codex]', () => {
|
|
3143
3373
|
if (!hasCodexCLI()) {
|
|
3144
3374
|
if (target === 'codex') {
|
|
@@ -4068,6 +4298,10 @@ async function cmdReinstall() {
|
|
|
4068
4298
|
let confirmed = false;
|
|
4069
4299
|
async function confirm(msg) {
|
|
4070
4300
|
if (confirmed) return true;
|
|
4301
|
+
if (!process.stdin.isTTY) {
|
|
4302
|
+
console.log('Non-interactive shell: skipping confirmation (declined).');
|
|
4303
|
+
return false;
|
|
4304
|
+
}
|
|
4071
4305
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
4072
4306
|
return new Promise((ok) => {
|
|
4073
4307
|
rl.question(`${msg} [y/N] `, (a) => {
|