huaweicloud-devkit 0.1.25-dev.0 → 0.1.26-dev.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/README.md +5 -2
- package/integrations/opencode/opencode.json +1 -1
- package/integrations/opencode/skills/huaweicloud-core/SKILL.md +2 -0
- package/package.json +1 -1
- package/plugins/huaweicloud-core/.mcp.json +1 -1
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +48 -0
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +178 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +4 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +20 -3
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +28 -1
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +0 -1
- package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +2 -2
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +2 -11
- package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +4 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +14 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +5 -3
- package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +137 -0
- package/plugins/huaweicloud-core/src/safety-policy.mjs +26 -13
- package/plugins/huaweicloud-core/src/setup-cli.mjs +126 -65
- package/plugins/huaweicloud-core/src/tools.mjs +156 -1
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ npx --yes huaweicloud-devkit uninstall --target codearts # 卸载
|
|
|
76
76
|
```json
|
|
77
77
|
{
|
|
78
78
|
"mcp": {
|
|
79
|
-
"huaweicloud": {
|
|
79
|
+
"huaweicloud-devkit": {
|
|
80
80
|
"type": "local",
|
|
81
81
|
"command": ["node", "<路径>/plugins/huaweicloud-core/src/mcp-server.mjs"],
|
|
82
82
|
"enabled": true
|
|
@@ -123,7 +123,7 @@ Agent 技能是经过整理的指令和参考材料包,帮助 Agent 完成特
|
|
|
123
123
|
|
|
124
124
|
- **安全优先执行** — 所有 `hcloud` 命令执行前自动分类(读/写/密钥),写操作需用户明确批准。
|
|
125
125
|
- **输出脱敏** — 凭证形态的值(AK/SK、Token、密码)自动替换为 `***REDACTED***`。
|
|
126
|
-
- **
|
|
126
|
+
- **16 个结构化工具** — 技能搜索、CLI 检查、只读命令、区域发现、错误解释、Hook 风险检查等。
|
|
127
127
|
- **零运行时依赖** — 纯 Node.js(>= 20),无需 npm install。
|
|
128
128
|
|
|
129
129
|
详见 [MCP 工具表](#mcp-工具)。
|
|
@@ -154,6 +154,9 @@ Agent 技能是经过整理的指令和参考材料包,帮助 Agent 完成特
|
|
|
154
154
|
| CLI | `huaweicloud_run_readonly_command` | 执行只读命令并脱敏输出 |
|
|
155
155
|
| CLI | `huaweicloud_run_approved_command` | 经用户明确批准后执行写命令 |
|
|
156
156
|
| 安全 | `huaweicloud_show_profile_redacted` | 安全查看 KooCLI 配置(凭证脱敏) |
|
|
157
|
+
| 安全 | `huaweicloud_hook_check_command` | 执行前检查 Shell/KooCLI 命令风险 |
|
|
158
|
+
| 安全 | `huaweicloud_hook_check_artifacts` | 检查生成的代码、IaC、IAM/OBS 策略和配置文件风险 |
|
|
159
|
+
| 安全 | `huaweicloud_hook_check_deploy_plan` | 检查沙箱、预览环境和云资源部署计划风险 |
|
|
157
160
|
| 路由 | `huaweicloud_service_catalog` | 返回推荐的能力来源排序 |
|
|
158
161
|
| 排错 | `huaweicloud_explain_error` | 解释错误码并建议诊断步骤 |
|
|
159
162
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://opencode.ai/config.json",
|
|
3
3
|
"_note": "Merge this into ~/.config/opencode/opencode.jsonc. Replace the path with the absolute path to your mcp-server.mjs installation.",
|
|
4
4
|
"mcp": {
|
|
5
|
-
"huaweicloud": {
|
|
5
|
+
"huaweicloud-devkit": {
|
|
6
6
|
"type": "local",
|
|
7
7
|
"command": [
|
|
8
8
|
"node",
|
|
@@ -20,6 +20,8 @@ Use this skill as the OpenCode entry point. Prefer the full skill set under `plu
|
|
|
20
20
|
|
|
21
21
|
Never ask the user to paste credentials. Never read `.hcloud` or `.huaweicloud` files into context. Plan writes first, ask for explicit approval, then verify with read-only checks.
|
|
22
22
|
|
|
23
|
+
For commands that assemble Huawei Cloud parameters through variables, string concatenation, subshells, encoded payloads, or generated scripts, inspect the final expanded command text before execution. If OpenCode cannot determine the final values, ask for explicit review or run the planning/check tool on the expanded command.
|
|
24
|
+
|
|
23
25
|
## KooCLI Basics
|
|
24
26
|
|
|
25
27
|
- Install guide: `https://support.huaweicloud.com/qs-hcli/hcli_02_003.html`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26-dev.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": [
|
|
@@ -14,8 +14,10 @@ plugin hooks.
|
|
|
14
14
|
import json
|
|
15
15
|
import re
|
|
16
16
|
import sys
|
|
17
|
+
from pathlib import Path
|
|
17
18
|
|
|
18
19
|
DENY_PREFIX = "Huawei Cloud safety hook blocked this action: "
|
|
20
|
+
RULES_PATH = Path(__file__).resolve().parents[1] / "safety" / "rules" / "cloud-risk-rules.json"
|
|
19
21
|
|
|
20
22
|
CONFIG_FILE_RE = re.compile(r"(\.hcloud|\.huaweicloud|hcloud[/\\](config|credentials)|huaweicloud[/\\](config|credentials))", re.I)
|
|
21
23
|
ENV_DUMP_RE = re.compile(r"(env|printenv|Get-ChildItem\s+Env:|gci\s+Env:|dir\s+Env:).*(HUAWEICLOUD|HWC_|HCLOUD|OS_)", re.I)
|
|
@@ -63,6 +65,49 @@ def command_text(tool_input):
|
|
|
63
65
|
return json.dumps(tool_input)
|
|
64
66
|
|
|
65
67
|
|
|
68
|
+
def load_cloud_risk_rules():
|
|
69
|
+
try:
|
|
70
|
+
with RULES_PATH.open("r", encoding="utf-8") as file_obj:
|
|
71
|
+
catalog = json.load(file_obj)
|
|
72
|
+
return catalog.get("rules", [])
|
|
73
|
+
except Exception:
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def condition_matches(condition, text):
|
|
78
|
+
return re.search(condition.get("regex", r"a^"), text, re.I | re.M | re.S) is not None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def rule_matches(rule, text):
|
|
82
|
+
match = rule.get("match") or {}
|
|
83
|
+
all_conditions = match.get("all")
|
|
84
|
+
any_conditions = match.get("any")
|
|
85
|
+
none_conditions = match.get("none")
|
|
86
|
+
|
|
87
|
+
if isinstance(all_conditions, list):
|
|
88
|
+
for condition in all_conditions:
|
|
89
|
+
if not condition_matches(condition, text):
|
|
90
|
+
return False
|
|
91
|
+
if isinstance(any_conditions, list):
|
|
92
|
+
if not any(condition_matches(condition, text) for condition in any_conditions):
|
|
93
|
+
return False
|
|
94
|
+
if isinstance(none_conditions, list):
|
|
95
|
+
if any(condition_matches(condition, text) for condition in none_conditions):
|
|
96
|
+
return False
|
|
97
|
+
return isinstance(all_conditions, list) or isinstance(any_conditions, list)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def first_denied_command_rule(text):
|
|
101
|
+
for rule in load_cloud_risk_rules():
|
|
102
|
+
if "command" not in rule.get("stages", []):
|
|
103
|
+
continue
|
|
104
|
+
if rule.get("severity") != "deny":
|
|
105
|
+
continue
|
|
106
|
+
if rule_matches(rule, text):
|
|
107
|
+
return rule
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
66
111
|
def main():
|
|
67
112
|
try:
|
|
68
113
|
data = json.load(sys.stdin)
|
|
@@ -78,6 +123,9 @@ def main():
|
|
|
78
123
|
deny("dumping cloud credential environment variables is not allowed.")
|
|
79
124
|
if SECRET_READ_RE.search(text):
|
|
80
125
|
deny("direct secret value retrieval would put plaintext secrets into the agent context.")
|
|
126
|
+
denied_rule = first_denied_command_rule(text)
|
|
127
|
+
if denied_rule:
|
|
128
|
+
deny(f"{denied_rule.get('message')} Remediation: {denied_rule.get('remediation')}")
|
|
81
129
|
if tool_name == "Bash" and HCLOUD_RE.search(text) and WRITE_OPERATION_RE.search(text) and not READ_OPERATION_RE.search(text):
|
|
82
130
|
deny("unapproved Huawei Cloud write operations must be planned first and explicitly approved by the user.")
|
|
83
131
|
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.0",
|
|
3
|
+
"rules": [
|
|
4
|
+
{
|
|
5
|
+
"id": "hwc-command-credential-file",
|
|
6
|
+
"title": "Credential file read",
|
|
7
|
+
"category": "credential",
|
|
8
|
+
"severity": "deny",
|
|
9
|
+
"stages": ["command"],
|
|
10
|
+
"match": {
|
|
11
|
+
"any": [
|
|
12
|
+
{ "field": "text", "regex": "(^|\\s)(cat|type|Get-Content|gc|less|more)\\s+[^\\n]*(\\.hcloud|\\.huaweicloud)" },
|
|
13
|
+
{ "field": "text", "regex": "(hcloud|huaweicloud)[/\\\\](config|credentials)" }
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"message": "The command may read local Huawei Cloud credential or profile files into the agent context.",
|
|
17
|
+
"remediation": "Use huaweicloud_show_profile_redacted or user-side credential configuration instead of reading credential files."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "hwc-command-env-dump",
|
|
21
|
+
"title": "Cloud credential environment dump",
|
|
22
|
+
"category": "credential",
|
|
23
|
+
"severity": "deny",
|
|
24
|
+
"stages": ["command"],
|
|
25
|
+
"match": {
|
|
26
|
+
"all": [
|
|
27
|
+
{ "field": "text", "regex": "(^|\\s)(env|printenv|Get-ChildItem\\s+Env:|gci\\s+Env:|dir\\s+Env:)" },
|
|
28
|
+
{ "field": "text", "regex": "(HUAWEICLOUD|HWC_|HCLOUD|OS_)" }
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"message": "The command may print cloud credential environment variables.",
|
|
32
|
+
"remediation": "Inspect only required non-secret settings and redact secret-like values before returning output."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "hwc-command-secret-value-read",
|
|
36
|
+
"title": "Plaintext secret read",
|
|
37
|
+
"category": "secret",
|
|
38
|
+
"severity": "deny",
|
|
39
|
+
"stages": ["command"],
|
|
40
|
+
"match": {
|
|
41
|
+
"any": [
|
|
42
|
+
{ "field": "text", "regex": "(ShowSecretVersion|DownloadSecret|GetSecretValue)" },
|
|
43
|
+
{ "field": "text", "regex": "(secret_string|secret_binary|secretString|secretBinary)" }
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"message": "The command appears to retrieve plaintext secret values.",
|
|
47
|
+
"remediation": "Use runtime secret references, user-side inspection, or redacted metadata checks instead of returning secret values to the agent."
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "hwc-command-encoded-shell-exec",
|
|
51
|
+
"title": "Encoded payload piped to shell",
|
|
52
|
+
"category": "execution",
|
|
53
|
+
"severity": "deny",
|
|
54
|
+
"stages": ["command"],
|
|
55
|
+
"match": {
|
|
56
|
+
"all": [
|
|
57
|
+
{ "field": "text", "regex": "(base64\\s+(-d|--decode)|xxd\\s+-r|certutil\\s+-decode|FromBase64String|hex\\s*decode)" },
|
|
58
|
+
{ "field": "text", "regex": "(\\||;|&&)" },
|
|
59
|
+
{ "field": "text", "regex": "\\b(bash|sh|zsh|powershell|pwsh|cmd|python|node)\\b" }
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"message": "The command decodes an encoded payload and pipes it into an interpreter.",
|
|
63
|
+
"remediation": "Decode payloads into a reviewable file first, inspect the content, then execute only explicit reviewed commands."
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "hwc-network-public-admin-port",
|
|
67
|
+
"title": "Public admin port exposure",
|
|
68
|
+
"category": "public_exposure",
|
|
69
|
+
"severity": "deny",
|
|
70
|
+
"stages": ["command", "artifact", "deploy_plan"],
|
|
71
|
+
"match": {
|
|
72
|
+
"all": [
|
|
73
|
+
{ "field": "text", "regex": "(0\\.0\\.0\\.0\\s*/\\s*0|::\\s*/\\s*0|remote_ip_prefix\\s*[=:]\\s*0\\.0\\.0\\.0\\s*/\\s*0|cidr\\s*[=:]\\s*0\\.0\\.0\\.0\\s*/\\s*0)" },
|
|
74
|
+
{ "field": "text", "regex": "(port_range_min|port_range_max|from_port|to_port|port|\\b22\\b|\\b3389\\b|\\b3306\\b|\\b5432\\b|\\b6379\\b|\\b9200\\b)" }
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"message": "The change appears to expose an administrative or database port to the public internet.",
|
|
78
|
+
"remediation": "Restrict the source CIDR to a trusted range, use a bastion or VPN, and keep public preview endpoints behind authenticated HTTP services."
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "hwc-obs-anonymous-write",
|
|
82
|
+
"title": "OBS anonymous write",
|
|
83
|
+
"category": "public_exposure",
|
|
84
|
+
"severity": "deny",
|
|
85
|
+
"stages": ["command", "artifact", "deploy_plan"],
|
|
86
|
+
"match": {
|
|
87
|
+
"all": [
|
|
88
|
+
{ "field": "text", "regex": "(OBS|obs://|bucket|object|Statement|Principal)" }
|
|
89
|
+
],
|
|
90
|
+
"any": [
|
|
91
|
+
{ "field": "text", "regex": "((Anonymous|Everyone|\\\"Principal\\\"\\s*:\\s*\\\"\\*\\\"|AllUsers).*(PutObject|DeleteObject|PutBucketPolicy|FULL_CONTROL|WRITE|obs:object:PutObject|obs:bucket:PutBucketPolicy)|(PutObject|DeleteObject|PutBucketPolicy|FULL_CONTROL|WRITE|obs:object:PutObject|obs:bucket:PutBucketPolicy).*(Anonymous|Everyone|\\\"Principal\\\"\\s*:\\s*\\\"\\*\\\"|AllUsers))" },
|
|
92
|
+
{ "field": "text", "regex": "(-acl\\s*=?\\s*public-read-write|acl\\s*[=:]\\s*[\\\"']?public-read-write|public-write)" }
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
"message": "The change may grant anonymous write or full-control access to OBS data.",
|
|
96
|
+
"remediation": "Use private buckets by default. Grant object read only when required, and avoid anonymous write permissions."
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "hwc-functiongraph-public-no-auth",
|
|
100
|
+
"title": "Public FunctionGraph trigger without auth",
|
|
101
|
+
"category": "public_exposure",
|
|
102
|
+
"severity": "warn",
|
|
103
|
+
"stages": ["command", "artifact", "deploy_plan"],
|
|
104
|
+
"match": {
|
|
105
|
+
"all": [
|
|
106
|
+
{ "field": "text", "regex": "(FunctionGraph|APIG|CreateTrigger|CreateApi|DEDICATEDGATEWAY|API Gateway)" },
|
|
107
|
+
{ "field": "text", "regex": "(public|PUBLIC|0\\.0\\.0\\.0\\s*/\\s*0|auth[\\\"']?\\s*[=:]\\s*[\\\"']?(NONE|none|false)|security_authentication[\\\"']?\\s*[=:]\\s*[\\\"']?(NONE|none))" }
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
"message": "The generated FunctionGraph or API Gateway path may be publicly reachable without authentication.",
|
|
111
|
+
"remediation": "Require IAM, app authentication, JWT, or another explicit auth layer before exposing generated application endpoints."
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"id": "hwc-iam-admin-policy",
|
|
115
|
+
"title": "Broad IAM administrator grant",
|
|
116
|
+
"category": "iam",
|
|
117
|
+
"severity": "deny",
|
|
118
|
+
"stages": ["command", "artifact", "deploy_plan"],
|
|
119
|
+
"match": {
|
|
120
|
+
"all": [
|
|
121
|
+
{ "field": "text", "regex": "(IAM|policy|role|agency|Statement|Action)" },
|
|
122
|
+
{ "field": "text", "regex": "(\\\"Action\\\"\\s*:\\s*(\\\"(\\*|\\*:\\*)\\\"|\\[\\s*\\\"(\\*|\\*:\\*)\\\")|Action\\s*[=:]\\s*(\\*|\\*:\\*)|AdministratorAccess|FullAccess)" },
|
|
123
|
+
{ "field": "text", "regex": "(\\\"Effect\\\"\\s*:\\s*\\\"Allow\\\"|Effect\\s*[=:]\\s*Allow)" }
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
"message": "The change appears to grant broad administrator permissions.",
|
|
127
|
+
"remediation": "Use least-privilege service actions, resource constraints, and short-lived credentials for generated application workflows."
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"id": "hwc-destructive-delete-force",
|
|
131
|
+
"title": "Forced destructive operation",
|
|
132
|
+
"category": "destructive",
|
|
133
|
+
"severity": "deny",
|
|
134
|
+
"stages": ["command"],
|
|
135
|
+
"match": {
|
|
136
|
+
"all": [
|
|
137
|
+
{ "field": "text", "regex": "(Delete|BatchDelete|Remove|\\brm\\b|delete)" },
|
|
138
|
+
{ "field": "text", "regex": "(--force|-f|--recursive|-r|delete_publicip\\s*[=:]\\s*true)" }
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
"message": "The command combines deletion with force, recursive, or cascading deletion behavior.",
|
|
142
|
+
"remediation": "Show the exact resources to be deleted, confirm backups or recovery path, and require explicit user approval for the exact command."
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"id": "hwc-sandbox-missing-ttl",
|
|
146
|
+
"title": "Sandbox deployment missing cleanup metadata",
|
|
147
|
+
"category": "sandbox",
|
|
148
|
+
"severity": "warn",
|
|
149
|
+
"stages": ["artifact", "deploy_plan"],
|
|
150
|
+
"match": {
|
|
151
|
+
"all": [
|
|
152
|
+
{ "field": "text", "regex": "(sandbox|preview|temporary|ephemeral|dev environment|FunctionGraph|ECS|CCE|APIG)" },
|
|
153
|
+
{ "field": "text", "regex": "(Create|Deploy|Provision|resource|stack|environment)" }
|
|
154
|
+
],
|
|
155
|
+
"none": [
|
|
156
|
+
{ "field": "text", "regex": "(ttl|expires_at|expire_at|cleanup|owner|cost_center|auto_delete|auto-delete)" }
|
|
157
|
+
]
|
|
158
|
+
},
|
|
159
|
+
"message": "The preview or sandbox deployment does not show cleanup or ownership metadata.",
|
|
160
|
+
"remediation": "Add owner, purpose, expiration time, and cleanup command before creating preview cloud resources."
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"id": "hwc-cost-unbounded-scale",
|
|
164
|
+
"title": "Unbounded scale or high-cost resource",
|
|
165
|
+
"category": "cost",
|
|
166
|
+
"severity": "warn",
|
|
167
|
+
"stages": ["command", "artifact", "deploy_plan"],
|
|
168
|
+
"match": {
|
|
169
|
+
"any": [
|
|
170
|
+
{ "field": "text", "regex": "(max_instances|max_node_count|max_replica|desired\\s*[=:]\\s*[5-9][0-9]|replicas\\s*[=:]\\s*[5-9][0-9])" },
|
|
171
|
+
{ "field": "text", "regex": "(GPU|gpu|large|xlarge|charging_mode\\s*[=:]\\s*prePaid|period_type|period_num)" }
|
|
172
|
+
]
|
|
173
|
+
},
|
|
174
|
+
"message": "The generated plan may create high-cost or unbounded capacity.",
|
|
175
|
+
"remediation": "Use small preview defaults, explicit quotas, budget labels, and user approval before creating high-cost resources."
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
@@ -84,6 +84,7 @@ Abort if the result set is larger than `--limit` and ask the user to narrow the
|
|
|
84
84
|
| Insufficient resources | Stock depleted -> Change flavor or AZ |
|
|
85
85
|
| AuthFailure | Expired AK/SK -> hcloud configure init |
|
|
86
86
|
| APIGW.0802 / region permission | IAM user has no access to this region -> IAM console → User → Permissions → add region, or switch to another region |
|
|
87
|
+
| Cannot SSH (port 22 open) | SCP policy may be blocking SSH. Check `SYS.0403` errors in command output -> Use cloud-init/user_data for initial setup instead. See `references/create-instance.md` §Bootstrap |
|
|
87
88
|
|
|
88
89
|
## Security Considerations
|
|
89
90
|
|
|
@@ -98,9 +99,12 @@ Prefer these tools over raw hcloud CLI — they enforce safety policies:
|
|
|
98
99
|
|
|
99
100
|
- huaweicloud_list_operations service=ECS
|
|
100
101
|
- huaweicloud_run_readonly_command for discovery (auto-redacts output)
|
|
102
|
+
- huaweicloud_plan_cli_command for command planning (returns command text + safety classification)
|
|
101
103
|
- huaweicloud_run_approved_command for writes (requires exact command approval)
|
|
102
104
|
- huaweicloud_check_cli to verify hcloud is available
|
|
103
105
|
|
|
106
|
+
> **approvedCommand trap**: `huaweicloud_run_approved_command` validates that `approvedCommand` matches the planned command EXACTLY (including `<redacted>` placeholders). Always use the `command` field value returned by `huaweicloud_plan_cli_command` verbatim — never reconstruct or retype it. Mismatches cause rejection with "approvedCommand must exactly match the planned hcloud command."
|
|
107
|
+
|
|
104
108
|
## Without MCP (Fallback)
|
|
105
109
|
|
|
106
110
|
If MCP tools are NOT available (new install, session not restarted):
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 1. Discover flavors
|
|
6
6
|
hcloud ECS ListFlavors --cli-region=<region> --cli-output=json
|
|
7
|
+
Filter for `os_extra_specs.cond:operation:status == normal` — most results are abandoned. See references/flavors.md.
|
|
7
8
|
|
|
8
9
|
## 2. Find availability zones
|
|
9
10
|
hcloud ECS NovaListAvailabilityZones --cli-region=<region>
|
|
@@ -30,7 +31,7 @@ hcloud VPC ListSubnets --vpc_id=<vpc-id> --cli-region=<region>
|
|
|
30
31
|
If no VPC/subnet exists: load `huawei-vpc` skill → create VPC → create subnet (with DNS) → create security group → return here.
|
|
31
32
|
|
|
32
33
|
## 5. Create keypair (recommended over adminPass)
|
|
33
|
-
hcloud ECS NovaCreateKeypair --
|
|
34
|
+
hcloud ECS NovaCreateKeypair --keypair.name=<name>
|
|
34
35
|
Save the returned private key to a local file. The public key is auto-injected.
|
|
35
36
|
|
|
36
37
|
Password alternative:
|
|
@@ -43,7 +44,7 @@ hcloud ECS CreateServers --cli-region=<region> --server.name=<name> --server.fla
|
|
|
43
44
|
|
|
44
45
|
### Bootstrap with user_data (cloud-init)
|
|
45
46
|
|
|
46
|
-
Use `--server.user_data` to run a cloud-init script at first boot. The value must be **base64-encoded
|
|
47
|
+
Use `--server.user_data` to run a cloud-init script at first boot. The value must be **base64-encoded**. This is also the recommended bootstrap path when SCP policies block SSH access — user_data serves as the full deployment path, no SSH needed.
|
|
47
48
|
|
|
48
49
|
```bash
|
|
49
50
|
# Encode the script
|
|
@@ -61,7 +62,23 @@ hcloud ECS CreateServers ... --server.user_data=$user_data
|
|
|
61
62
|
|
|
62
63
|
> **Debugging**: If the script didn't run, check `/var/log/cloud-init-output.log` on the instance.
|
|
63
64
|
|
|
64
|
-
## 7. EIP (
|
|
65
|
+
## 7. EIP (two methods)
|
|
66
|
+
|
|
67
|
+
### Method A: Inline with CreateServers (Recommended)
|
|
68
|
+
Add EIP parameters to the `CreateServers` command in step 6:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
hcloud ECS CreateServers \
|
|
72
|
+
--server.publicip.eip.iptype=<type> \
|
|
73
|
+
--server.publicip.eip.bandwidth.sharetype=<share-type> \
|
|
74
|
+
--server.publicip.eip.bandwidth.size=<size> \
|
|
75
|
+
--server.publicip.eip.bandwidth.chargemode=traffic \
|
|
76
|
+
...
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> **Trap**: Parameter names differ from `EIP CreatePublicip`. Use `iptype` (not `type`), `sharetype` (not `share_type`), and `chargemode` (not `charging_mode`). Always verify with `hcloud ECS CreateServers --help`.
|
|
80
|
+
|
|
81
|
+
### Method B: Create and bind separately
|
|
65
82
|
hcloud EIP CreatePublicip --publicip.type=<type> --bandwidth.size=<size> --bandwidth.share_type=<share-type> --bandwidth.name=<name>
|
|
66
83
|
|
|
67
84
|
# Get the ECS network port ID
|
|
@@ -3,7 +3,20 @@
|
|
|
3
3
|
**Always discover flavors dynamically before recommending a specific flavor name.** Flavor availability varies by region and changes over time.
|
|
4
4
|
|
|
5
5
|
## Step 1: List available flavors
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
Use JMESPath to filter in-line — raw output returns hundreds of records and floods context:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Filter by family prefix (e.g. ac7), return name + specs only
|
|
11
|
+
hcloud ECS ListFlavors --cli-region=<region> --cli-output=json \
|
|
12
|
+
--cli-query="flavors[?contains(name, 'ac7')].{name:name, vcpus:vcpus, ram:ram}"
|
|
13
|
+
|
|
14
|
+
# Filter by vCPU range
|
|
15
|
+
hcloud ECS ListFlavors --cli-region=<region> --cli-output=json \
|
|
16
|
+
--cli-query="flavors[?vcpus >= '2' && vcpus <= '4'].{name:name, vcpus:vcpus, ram:ram}"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> Always use `--cli-query` with JMESPath to narrow results. Never run bare `ListFlavors` without filtering.
|
|
7
20
|
|
|
8
21
|
## Step 2: Filter by scenario
|
|
9
22
|
|
|
@@ -26,3 +39,17 @@ Flavor family names are region-dependent. Example discrepancies seen in testing:
|
|
|
26
39
|
- Other regions may have s6/m6/g6 families
|
|
27
40
|
|
|
28
41
|
Always run ListFlavors and pick from actual results.
|
|
42
|
+
|
|
43
|
+
## Step 3: Filter out abandoned / sold-out specs
|
|
44
|
+
|
|
45
|
+
`ListFlavors` returns ALL specs including abandoned ones. Before selecting a spec, check the `os_extra_specs` field in the JSON response:
|
|
46
|
+
|
|
47
|
+
| Field | Values | Meaning |
|
|
48
|
+
|-------|--------|---------|
|
|
49
|
+
| `os_extra_specs.cond:operation:status` | `normal`, `abandon`, `sellout` | Only `normal` specs can be created. `abandon` = deprecated, `sellout` = out of stock |
|
|
50
|
+
| `os_extra_specs.cond:operation:az` | e.g. `cn-north-4g(normal)` | Spec is available in this AZ. Multiple entries = multiple AZ support |
|
|
51
|
+
|
|
52
|
+
A flavor can be `normal` globally but `abandon` in specific AZs. Selecting an `abandon` or `sellout` spec will fail with **`Ecs.0019`** at creation time — there is no pre-flight validation in `ListFlavors`. If creation fails:
|
|
53
|
+
|
|
54
|
+
1. Switch to a different AZ: `hcloud ECS NovaListAvailabilityZones --cli-region=<region>`
|
|
55
|
+
2. Or switch to a different flavor family (e.g., `at7` → `ac7`)
|
|
@@ -63,7 +63,6 @@ Always discover parameters with `--help` before executing. These are the correct
|
|
|
63
63
|
| Create function | `CreateFunction` | references/create-function.md |
|
|
64
64
|
| Delete function | `DeleteFunction` | Strip `:latest` from URN |
|
|
65
65
|
| Invoke function | `InvokeFunction` | Requires body param (`--name=<value>` becomes event body). Use `--x_cff_request_version=v0` for raw output, `v1` for APIG-wrapped. |
|
|
66
|
-
| List runtimes | `ListRuntimes` | |
|
|
67
66
|
| Create trigger | `CreateFunctionTrigger` | references/triggers.md |
|
|
68
67
|
| List triggers | `ListFunctionTriggers` | |
|
|
69
68
|
| Delete trigger | `DeleteFunctionTrigger` | |
|
|
@@ -18,7 +18,7 @@ version: 1
|
|
|
18
18
|
|
|
19
19
|
## First-Time Setup
|
|
20
20
|
1. **Install KooCLI** using command above
|
|
21
|
-
2. **Accept privacy policy** (first run only): KooCLI requires one-time privacy agreement. Run `hcloud version`
|
|
21
|
+
2. **Accept privacy policy** (first run only): KooCLI requires one-time privacy agreement. Run `hcloud version` to read the agreement, then respond `y` to accept. Do not pipe `echo "y" |` — you must review the terms first.
|
|
22
22
|
3. **Configure credentials**: `hcloud configure init` (interactive, prompts for AK/SK/region safely)
|
|
23
23
|
4. **Verify**: `hcloud configure list` to confirm profile, then `hcloud ECS ListServersDetails --cli-region=cn-north-4`
|
|
24
24
|
5. For detailed auth guidance, see `huaweicloud-cli-and-auth` skill
|
|
@@ -43,7 +43,7 @@ hcloud configure list
|
|
|
43
43
|
| AK/SK must be kept secret | Never commit to git or share |
|
|
44
44
|
| Default region applies to all commands | Override with --cli-region= per command |
|
|
45
45
|
| Some services region-specific | Not all services available in all regions |
|
|
46
|
-
| Privacy policy blocks first run |
|
|
46
|
+
| Privacy policy blocks first run | Run `hcloud version` to read and accept the agreement. Review the terms before responding `y` |
|
|
47
47
|
|
|
48
48
|
## What Can I Do? (Quick Index)
|
|
49
49
|
| Goal | Skill |
|
|
@@ -36,22 +36,13 @@ Domain expertise for Huawei Cloud Object Storage Service (OBS). Covers bucket/ob
|
|
|
36
36
|
| Versioning is irreversible | Once enabled, cannot be disabled, only suspended |
|
|
37
37
|
| OBS uses AK/SK directly | NOT IAM tokens. Auth errors mean check AK/SK validity |
|
|
38
38
|
| Static website via CLI missing | KooCLI OBS lacks website config. Use REST API or console |
|
|
39
|
-
| **OBS needs separate cred config** | `hcloud configure` is NOT enough for OBS.
|
|
39
|
+
| **OBS needs separate cred config** | `hcloud configure` is NOT enough for OBS. Before any OBS operation, call `huaweicloud_setup_obs_config` to sync credentials from hcloud profile. |
|
|
40
40
|
| **obsutil interactive prompts** | `cp`/`rm` without `-f` causes "Please input (y/n)" → Agent hangs (TIMEOUT). Always use `-f` for non-interactive. |
|
|
41
41
|
| **Directory upload adds prefix** | `cp <dir>/ obs://<bucket>/ -r` puts files under `bucket/<dir>/...`. Use `-flat` for root-level files (static sites). Preview with `-dryRun` first. |
|
|
42
42
|
|
|
43
43
|
## OBS Credential Setup (Required Before First Use)
|
|
44
44
|
|
|
45
|
-
KooCLI OBS uses a separate config file (`~/.obsutilconfig`), NOT `~/.hcloud/config.json`.
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
# Preferred: interactive (safe, no AK/SK in shell history)
|
|
49
|
-
hcloud OBS config -i
|
|
50
|
-
# Follow prompts: AK, SK, endpoint (e.g. obs.cn-north-4.myhuaweicloud.com)
|
|
51
|
-
|
|
52
|
-
# Alternative: non-interactive (use with caution — AK/SK in shell history)
|
|
53
|
-
hcloud OBS config -e=<endpoint> -i=<AK> -k=<SK>
|
|
54
|
-
```
|
|
45
|
+
KooCLI OBS uses a separate config file (`~/.obsutilconfig`), NOT `~/.hcloud/config.json`. Call `huaweicloud_setup_obs_config` to automatically sync credentials from the active hcloud profile. No manual AK/SK entry needed.
|
|
55
46
|
|
|
56
47
|
## Common Workflows
|
|
57
48
|
|
|
@@ -10,6 +10,8 @@ version: 1
|
|
|
10
10
|
|
|
11
11
|
Always run `hcloud <Service> <Operation> --help` before constructing commands to discover exact parameter names and requirements.
|
|
12
12
|
|
|
13
|
+
> **Multi-version APIs**: KooCLI may print a warning like "ListVpcs有多个版本,默认使用该API版本v3" before the actual response. The text BEFORE the first `{` is the version selection notice — parse JSON starting from `{` only. This is normal behavior, not an error.
|
|
14
|
+
|
|
13
15
|
## Overview
|
|
14
16
|
|
|
15
17
|
Domain expertise for Huawei Cloud Virtual Private Cloud (VPC). Covers VPC/subnet lifecycle, security groups, EIP management, NAT gateways, VPN, and network ACLs.
|
|
@@ -27,6 +29,7 @@ Domain expertise for Huawei Cloud Virtual Private Cloud (VPC). Covers VPC/subnet
|
|
|
27
29
|
| **VPC params need nested prefix** | KooCLI 7.x VPC API uses `--vpc.<param>`, `--subnet.<param>`, `--security_group.<param>`. Example: `--vpc.name=xxx` NOT `--name=xxx` |
|
|
28
30
|
| **Security group needs no vpc_id** | VPC v3 API `CreateSecurityGroup` does NOT accept `vpc_id`. Security groups are region-level, not VPC-bound |
|
|
29
31
|
| Subnet DNS empty → ECS no DNS | DNS params (`--subnet.primary_dns`, `--subnet.secondary_dns`) marked optional but empty default breaks cloud-init domain resolution — `yum`/`apt` installs fail silently. Always set both. See `--help` for region-specific DNS IPs |
|
|
32
|
+
| SCP blocks 0.0.0.0/0 SG rules | If `CreateSecurityGroupRule` with `--remote_ip_prefix=0.0.0.0/0` fails with `SYS.0403`, an org-level SCP policy is denying wide-open rules. Narrow to a specific CIDR range (e.g., your office IP) instead |
|
|
30
33
|
|
|
31
34
|
## Common Workflows
|
|
32
35
|
|
|
@@ -55,7 +58,7 @@ Domain expertise for Huawei Cloud Virtual Private Cloud (VPC). Covers VPC/subnet
|
|
|
55
58
|
| VPC.0301: Bandwidth name invalid | PER type requires `--bandwidth.name`, even though `--help` marks it optional |
|
|
56
59
|
| EIP has no public IP after binding | May need AddIngressEipV2 for ELB-type resources (see huawei-apig) |
|
|
57
60
|
| ECS cloud-init fails silently (port 80/443 closed) | Subnet likely has no DNS. Check `hcloud VPC ShowSubnet --subnet_id=<id>` → `dnsList` empty? Rebuild subnet with `--subnet.primary_dns=<dns1> --subnet.secondary_dns=<dns2>`. DNS addresses per region: `hcloud VPC CreateSubnet --help` |
|
|
58
|
-
| SYS.0403 / SCP deny | Service Control Policy explicitly denies this operation — contact org admin to adjust SCP, or use an account/region without the restriction |
|
|
61
|
+
| SYS.0403 / SCP deny | Service Control Policy explicitly denies this operation — contact org admin to adjust SCP, or use an account/region without the restriction. If SSH is blocked, bootstrap via cloud-init user_data instead: see `huawei-ecs` — no SSH needed |
|
|
59
62
|
|
|
60
63
|
## Security Considerations
|
|
61
64
|
|
|
@@ -38,6 +38,20 @@ Use this skill before any Huawei Cloud action that may expose secrets, change re
|
|
|
38
38
|
- Approved tool path: use `huaweicloud_run_approved_command` only when the exact planned command has been shown and the user explicitly approved that exact command.
|
|
39
39
|
- After a write, verify with `huaweicloud_run_readonly_command` or another read-only check.
|
|
40
40
|
|
|
41
|
+
## Proactive Hook Checks
|
|
42
|
+
|
|
43
|
+
Before executing, deploying, or handing generated cloud artifacts to the user, run the matching check when available:
|
|
44
|
+
|
|
45
|
+
- `huaweicloud_hook_check_command` for shell or KooCLI command text.
|
|
46
|
+
- `huaweicloud_hook_check_artifacts` for generated policy, IaC, config, workflow, or deployment files.
|
|
47
|
+
- `huaweicloud_hook_check_deploy_plan` for sandbox, preview, FunctionGraph, ECS, CCE, APIG, OBS, IAM, or cost-affecting plans.
|
|
48
|
+
|
|
49
|
+
If the result is `deny`, repair the command, artifact, or deployment plan before execution. If the result is `warn`, show the warning to the user and repair or ask for explicit confirmation.
|
|
50
|
+
|
|
51
|
+
## Static Analysis Boundary
|
|
52
|
+
|
|
53
|
+
Hook checks inspect the command or plan text that is available before execution. If a command builds cloud parameters through shell variables, string concatenation, subshells, encoded payloads, or generated scripts, first expand the final command into reviewable text and run `huaweicloud_hook_check_command` on that final form. When the final values cannot be determined, stop and ask the user to review the expanded command or use `huaweicloud_plan_cli_command` before execution.
|
|
54
|
+
|
|
41
55
|
## Safe Alternatives
|
|
42
56
|
|
|
43
57
|
- Use redacted profile inspection instead of raw `hcloud configure show`.
|
|
@@ -15,7 +15,8 @@ function readFrames() {
|
|
|
15
15
|
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
16
16
|
if (headerEnd !== -1) {
|
|
17
17
|
useContentLengthFraming = true;
|
|
18
|
-
parseContentLengthFrame(headerEnd);
|
|
18
|
+
const consumed = parseContentLengthFrame(headerEnd);
|
|
19
|
+
if (!consumed) return;
|
|
19
20
|
continue;
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -37,15 +38,16 @@ function parseContentLengthFrame(headerEnd) {
|
|
|
37
38
|
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
38
39
|
if (!match) {
|
|
39
40
|
buffer = Buffer.alloc(0);
|
|
40
|
-
return;
|
|
41
|
+
return true;
|
|
41
42
|
}
|
|
42
43
|
const length = Number(match[1]);
|
|
43
44
|
const bodyStart = headerEnd + 4;
|
|
44
45
|
const bodyEnd = bodyStart + length;
|
|
45
|
-
if (buffer.length < bodyEnd) return;
|
|
46
|
+
if (buffer.length < bodyEnd) return false;
|
|
46
47
|
const body = buffer.subarray(bodyStart, bodyEnd).toString('utf8');
|
|
47
48
|
buffer = buffer.subarray(bodyEnd);
|
|
48
49
|
void handleMessage(JSON.parse(body));
|
|
50
|
+
return true;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
async function handleMessage(message) {
|