huaweicloud-devkit 1.1.7-next.2 → 1.1.8-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/README.zh-CN.md +13 -1
- package/package.json +2 -1
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +19 -1
- package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/safety/policy.json +1 -1
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +57 -2
- package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +19 -16
- package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +6 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +7 -1
- package/plugins/huaweicloud-core/src/auth/service.mjs +36 -0
- package/plugins/huaweicloud-core/src/detect-framework.mjs +41 -5
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +46 -7
- package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +94 -1
- package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +1 -1
- package/plugins/huaweicloud-core/src/safety-policy.mjs +165 -33
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +181 -10
- package/plugins/huaweicloud-core/src/search-market.mjs +40 -1
- package/plugins/huaweicloud-core/src/setup-cli.mjs +34 -0
- package/plugins/huaweicloud-core/src/tools.mjs +266 -17
- package/rules/huawei-agent-rules.mdc +78 -0
package/README.md
CHANGED
|
@@ -4,7 +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)
|
|
8
8
|
|
|
9
9
|
**[中文](README.zh-CN.md) | English**
|
|
10
10
|
|
|
@@ -332,6 +332,18 @@ npx --yes huaweicloud-devkit@latest update --target all
|
|
|
332
332
|
config. Always keep `@latest` so npm fetches the newest version instead of a
|
|
333
333
|
locally cached older one.
|
|
334
334
|
|
|
335
|
+
### Configure Proxy
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
npx --yes huaweicloud-devkit proxy init # configure proxy (interactive)
|
|
339
|
+
npx --yes huaweicloud-devkit proxy show # show effective proxy settings
|
|
340
|
+
npx --yes huaweicloud-devkit proxy clear # remove proxy configuration
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Configures HTTP/HTTPS proxy for connections to Huawei Cloud services (e.g. enterprise intranet environments). Settings are saved to `~/.config/huaweicloud/proxy.json`.
|
|
344
|
+
|
|
345
|
+
Environment variables `HTTPS_PROXY`, `HTTP_PROXY`, and `NO_PROXY` take precedence over the file configuration — use `proxy show` to inspect the effective settings.
|
|
346
|
+
|
|
335
347
|
## What It Does
|
|
336
348
|
|
|
337
349
|
- **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)
|
package/README.zh-CN.md
CHANGED
|
@@ -4,7 +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)
|
|
8
8
|
|
|
9
9
|
**中文 | [English](README.md)**
|
|
10
10
|
|
|
@@ -326,6 +326,18 @@ npx --yes huaweicloud-devkit@latest update --target all
|
|
|
326
326
|
|
|
327
327
|
`update` 是增量更新——只刷新已安装的文件,不动配置文件。请务必保留 `@latest`,确保 npm 获取最新版本而非本地缓存的旧版本。
|
|
328
328
|
|
|
329
|
+
### 配置代理
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
npx --yes huaweicloud-devkit proxy init # 配置代理(交互式)
|
|
333
|
+
npx --yes huaweicloud-devkit proxy show # 查看生效的代理设置
|
|
334
|
+
npx --yes huaweicloud-devkit proxy clear # 删除代理配置
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
为连接华为云服务配置 HTTP/HTTPS 代理(如企业内网环境)。配置保存在 `~/.config/huaweicloud/proxy.json`。
|
|
338
|
+
|
|
339
|
+
环境变量 `HTTPS_PROXY`、`HTTP_PROXY`、`NO_PROXY` 优先级高于文件配置——可使用 `proxy show` 查看最终生效的设置。
|
|
340
|
+
|
|
329
341
|
## 功能特性
|
|
330
342
|
|
|
331
343
|
- **引导式云操作** — Agent 获得 20+ 常用华为云服务的分步操作指引(ECS、OBS、VPC、RDS、GaussDB、FunctionGraph、APIG、CCE 等)
|
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.
|
|
4
|
+
"version": "1.1.8-next.0",
|
|
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",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"cordis.patch.yml",
|
|
10
10
|
"bin",
|
|
11
11
|
".agents",
|
|
12
|
+
"rules",
|
|
12
13
|
"plugins/huaweicloud-core",
|
|
13
14
|
"integrations/hermes",
|
|
14
15
|
"integrations/opencode",
|
|
@@ -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.
|
|
20
|
+
"version": "1.1.8-next.0",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -20,7 +20,7 @@
|
|
|
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.
|
|
23
|
+
"version": "1.1.8-next.0",
|
|
24
24
|
"author": {
|
|
25
25
|
"name": "HuaweiCloud Mate",
|
|
26
26
|
"url": "https://github.com/huaweicloud"
|
|
@@ -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.
|
|
20
|
+
"version": "1.1.8-next.0",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8-next.0",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8-next.0",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -51,7 +51,23 @@ def load_policy():
|
|
|
51
51
|
load_policy()
|
|
52
52
|
|
|
53
53
|
ENV_DUMP_RE = re.compile(
|
|
54
|
-
r"(env|printenv|Get-ChildItem\s+Env:|gci\s+Env:|dir\s+Env:).*(HUAWEICLOUD|HWC_|HCLOUD|OS_)", re.I,
|
|
54
|
+
r"(env|printenv|Get-ChildItem\s+Env:|gci\s+Env:|dir\s+Env:).*(HUAWEICLOUD|HWC_|HCLOUD|HW_|OS_)", re.I,
|
|
55
|
+
)
|
|
56
|
+
# Credential variable references bypass the env-command gate above: HW_ is
|
|
57
|
+
# the plugin's own documented credential prefix (HW_ACCESS_KEY/HW_SECRET_KEY/
|
|
58
|
+
# HW_SECURITY_TOKEN), cloud environments also expose the AWS-style
|
|
59
|
+
# HUAWEICLOUD_SECRET_ACCESS_KEY name, and `echo $HW_SECRET_KEY` /
|
|
60
|
+
# `printenv HW_ACCESS_KEY` / `echo $HUAWEICLOUD_SECRET_ACCESS_KEY` previously
|
|
61
|
+
# fell through to allow (#561 D4-2, #770 D4-2).
|
|
62
|
+
#
|
|
63
|
+
# The negative lookbehind exempts literal-NAME references — backslash-escaped
|
|
64
|
+
# (\$HW_*) or single-quoted ('$HW_*', which the shell never expands) —
|
|
65
|
+
# while unescaped `$HW_*` is a potential expansion/dump regardless of the
|
|
66
|
+
# command. Aligned with safety-policy.mjs L419-423.
|
|
67
|
+
CRED_VAR_REF_RE = re.compile(
|
|
68
|
+
r"(?<!['\\])\$\{?(?:HUAWEICLOUD|HWC|HW|OS)_(?:SECRET_ACCESS_KEY|ACCESS_KEY|SECRET_KEY|SECURITY_TOKEN)"
|
|
69
|
+
r"|(?:^|\s)printenv\s+(?:HUAWEICLOUD|HWC|HW|OS)_(?:SECRET_ACCESS_KEY|ACCESS_KEY|SECRET_KEY|SECURITY_TOKEN)",
|
|
70
|
+
re.I,
|
|
55
71
|
)
|
|
56
72
|
HCLOUD_RE = re.compile(r"(^|\s)hcloud(\.exe)?\s+", re.I)
|
|
57
73
|
READ_OPERATION_RE = re.compile(r"\b(List|Show|Get|Describe|NovaList|NovaShow)\w*", re.I)
|
|
@@ -177,6 +193,8 @@ def evaluate(tool_name, tool_input):
|
|
|
177
193
|
return "reading Huawei Cloud credential/profile files can expose AK/SK or tokens. Use redacted toolkit tools."
|
|
178
194
|
if ENV_DUMP_RE.search(text):
|
|
179
195
|
return "dumping cloud credential environment variables is not allowed."
|
|
196
|
+
if CRED_VAR_REF_RE.search(text):
|
|
197
|
+
return "printing cloud credential environment variables is not allowed."
|
|
180
198
|
if SECRET_READ_RE and SECRET_READ_RE.search(text):
|
|
181
199
|
return "direct secret value retrieval would put plaintext secrets into the agent context."
|
|
182
200
|
denied_rule = first_denied_command_rule(text)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"id": "huaweicloud-devkit",
|
|
4
4
|
"displayName": "HuaweiCloud DevKit",
|
|
5
|
-
"version": "1.1.
|
|
5
|
+
"version": "1.1.8-next.0",
|
|
6
6
|
"family": "bundle-plugin",
|
|
7
7
|
"bundleFormat": "codex",
|
|
8
8
|
"description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"huaweicloud[/\\\\](config|credentials)"
|
|
24
24
|
],
|
|
25
25
|
"blockedConfigureSubcommands": ["show", "get", "list", "export", "init"],
|
|
26
|
-
"blockedSecretOperations": ["ShowSecretVersion", "DownloadSecret", "GetSecretValue"],
|
|
26
|
+
"blockedSecretOperations": ["ShowSecret", "ShowSecretVersion", "DownloadSecret", "GetSecretValue"],
|
|
27
27
|
"writeOperationPrefixes": [
|
|
28
28
|
"Create",
|
|
29
29
|
"BatchCreate",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"field": "text",
|
|
39
|
-
"regex": "(HUAWEICLOUD|HWC_|HCLOUD|OS_)"
|
|
39
|
+
"regex": "(HUAWEICLOUD|HWC_|HCLOUD|HW_|OS_)"
|
|
40
40
|
}
|
|
41
41
|
]
|
|
42
42
|
},
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"any": [
|
|
54
54
|
{
|
|
55
55
|
"field": "text",
|
|
56
|
-
"regex": "(ShowSecretVersion|DownloadSecret|GetSecretValue)"
|
|
56
|
+
"regex": "(ShowSecretVersion|ShowSecret|DownloadSecret|GetSecretValue)"
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"field": "text",
|
|
@@ -64,6 +64,23 @@
|
|
|
64
64
|
"message": "The command appears to retrieve plaintext secret values.",
|
|
65
65
|
"remediation": "Use runtime secret references, user-side inspection, or redacted metadata checks instead of returning secret values to the agent."
|
|
66
66
|
},
|
|
67
|
+
{
|
|
68
|
+
"id": "hwc-command-adminpass-exposure",
|
|
69
|
+
"title": "Plaintext adminPass exposure",
|
|
70
|
+
"category": "secret",
|
|
71
|
+
"severity": "warn",
|
|
72
|
+
"stages": ["artifact", "deploy_plan"],
|
|
73
|
+
"match": {
|
|
74
|
+
"any": [
|
|
75
|
+
{
|
|
76
|
+
"field": "text",
|
|
77
|
+
"regex": "(adminPass|admin_pass|admin-password)"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"message": "The artifact or plan contains a plaintext adminPass parameter. The password may be exposed in version control, logs, or agent context.",
|
|
82
|
+
"remediation": "Use userData cloud-init, KMS/CSMS secret references, or encrypted password injection instead of embedding adminPass as plaintext in deployment files."
|
|
83
|
+
},
|
|
67
84
|
{
|
|
68
85
|
"id": "hwc-command-sts-credential",
|
|
69
86
|
"title": "Temporary credential exposure",
|
|
@@ -200,6 +217,23 @@
|
|
|
200
217
|
"message": "The change appears to grant broad administrator permissions.",
|
|
201
218
|
"remediation": "Use least-privilege service actions, resource constraints, and short-lived credentials for generated application workflows."
|
|
202
219
|
},
|
|
220
|
+
{
|
|
221
|
+
"id": "hwc-iam-highrisk-write",
|
|
222
|
+
"title": "High-risk IAM write operation",
|
|
223
|
+
"category": "iam",
|
|
224
|
+
"severity": "warn",
|
|
225
|
+
"stages": ["command"],
|
|
226
|
+
"match": {
|
|
227
|
+
"any": [
|
|
228
|
+
{
|
|
229
|
+
"field": "text",
|
|
230
|
+
"regex": "hcloud\\s+IAM\\s+(CreateUser|CreatePolicy|CreateRole|CreateAgency)\\b"
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
},
|
|
234
|
+
"message": "This hcloud command creates or modifies IAM principals (users, policies, roles, or agencies). These operations can grant elevated privileges.",
|
|
235
|
+
"remediation": "Confirm the principal name, scope, and attached permissions with the user before creating IAM users, policies, roles, or agencies."
|
|
236
|
+
},
|
|
203
237
|
{
|
|
204
238
|
"id": "hwc-destructive-delete-force",
|
|
205
239
|
"title": "Forced destructive operation",
|
|
@@ -334,6 +368,27 @@
|
|
|
334
368
|
"message": "The generated plan may create high-cost or unbounded capacity.",
|
|
335
369
|
"remediation": "Use small preview defaults, explicit quotas, budget labels, and user approval before creating high-cost resources."
|
|
336
370
|
},
|
|
371
|
+
{
|
|
372
|
+
"id": "hwc-command-secret-in-arg",
|
|
373
|
+
"title": "Plaintext secret in command argument",
|
|
374
|
+
"category": "secret",
|
|
375
|
+
"severity": "warn",
|
|
376
|
+
"stages": ["command"],
|
|
377
|
+
"match": {
|
|
378
|
+
"all": [
|
|
379
|
+
{
|
|
380
|
+
"field": "text",
|
|
381
|
+
"regex": "\\bhcloud\\b"
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"field": "text",
|
|
385
|
+
"regex": "(--adminPass|--password|--secret|--token)\\s*[=:]\\s*[^\\s<]"
|
|
386
|
+
}
|
|
387
|
+
]
|
|
388
|
+
},
|
|
389
|
+
"message": "The command passes a plaintext password or secret as a direct CLI argument, which may leak into shell history, logs, or process listings.",
|
|
390
|
+
"remediation": "Use environment variables, DEW secret references, or interactive prompts instead of passing secrets as command-line arguments."
|
|
391
|
+
},
|
|
337
392
|
{
|
|
338
393
|
"id": "hwc-sandbox-destructive-command",
|
|
339
394
|
"title": "Destructive command in sandbox terminal",
|
|
@@ -16,25 +16,28 @@ Domain expertise for billing queries (BSS). Covers cost tracking, bill details,
|
|
|
16
16
|
|
|
17
17
|
## Critical Warnings
|
|
18
18
|
|
|
19
|
-
| Trap
|
|
20
|
-
|
|
|
21
|
-
| Bills delayed ~24h
|
|
22
|
-
| BSS Admin role needed
|
|
23
|
-
| Currency conversion varies
|
|
24
|
-
| Region fixed to cn-north-1
|
|
25
|
-
| English catalog missing BSS
|
|
26
|
-
|
|
|
19
|
+
| Trap | Why |
|
|
20
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| Bills delayed ~24h | Yesterday's costs may not appear until the next day |
|
|
22
|
+
| BSS Admin role needed | IAM user must have BSS Administrator or Finance role |
|
|
23
|
+
| Currency conversion varies | Cross-region costs use daily exchange rates |
|
|
24
|
+
| Region fixed to cn-north-1 | BSS operations only support `--cli-region=cn-north-1` in KooCLI. This is a KooCLI metadata limitation — the billing data itself covers all regions. |
|
|
25
|
+
| English catalog missing BSS | `Unsupported service: BSS` under the default `en` language is a KooCLI catalog gap, not a missing service. devkit detects it and advises the supported fix `hcloud configure set --cli-lang=cn` (switch is global-only; KooCLI has no per-command `--cli-lang` flag). Running with Chinese mode from the start (see Prerequisites) avoids the issue entirely. |
|
|
26
|
+
| Partner vs Customer-level APIs | BSS splits operations by caller identity. Customer-level APIs (`ShowCustomerAccountBalances`, `ListCustomerBillsFeeRecords`, `ListCustomerCouponChangeRecords`) work with a normal IAM user. Partner-level APIs (`ListQuotaCoupons`, `ListSubCustomerCoupons`) require a partner/dealer identity. |
|
|
27
|
+
| APIGW.0301 is identity, not domain-id | `APIGW.0301: invalid x-security-token` on a BSS Partner-level API means the caller identity is not a partner — a customer credential is rejected. This is NOT a missing `cli-domain-id` and NOT invalid AK/SK. Switch to the Customer-level API below that matches the current identity. |
|
|
27
28
|
|
|
28
29
|
## Common Workflows
|
|
29
30
|
|
|
30
|
-
| Task
|
|
31
|
-
|
|
|
32
|
-
| List costs
|
|
33
|
-
| List customer bills
|
|
34
|
-
|
|
|
35
|
-
| List
|
|
36
|
-
|
|
|
37
|
-
| List conversions
|
|
31
|
+
| Task | Identity | Operation |
|
|
32
|
+
| -------------------------- | -------- | ---------------------------------------------------------------------- |
|
|
33
|
+
| List costs | Customer | `ListCosts --cli-region=cn-north-1 --project_id=<p>` |
|
|
34
|
+
| List customer bills | Customer | `ListCustomerBillsFeeRecords --cli-region=cn-north-1 --project_id=<p>` |
|
|
35
|
+
| Show account balance | Customer | `ShowCustomerAccountBalances --cli-region=cn-north-1` |
|
|
36
|
+
| List coupon change records | Customer | `ListCustomerCouponChangeRecords --cli-region=cn-north-1` |
|
|
37
|
+
| List resource usage | Customer | `ListResourceUsage --cli-region=cn-north-1 --project_id=<p>` |
|
|
38
|
+
| List conversions | Customer | `ListConversions --cli-region=cn-north-1 --project_id=<p>` |
|
|
39
|
+
| List sub-customers | Partner | `ListConsumeSubCustomers --cli-region=cn-north-1 --project_id=<p>` |
|
|
40
|
+
| List quota coupons | Partner | `ListQuotaCoupons --cli-region=cn-north-1` (APIGW.0301 for customers) |
|
|
38
41
|
|
|
39
42
|
Discover exact parameters with `--help` before executing any command. All BSS operations are read-only.
|
|
40
43
|
|
|
@@ -69,5 +69,5 @@ Discover operation parameters with `--help` before executing any write operation
|
|
|
69
69
|
For advanced workflows (notebook lifecycle, training job diagnosis,
|
|
70
70
|
hyperparameter tuning), search the marketplace:
|
|
71
71
|
`huaweicloud_search_marketplace` query="ModelArts notebook/training/diagnosis"
|
|
72
|
-
→ install via `
|
|
72
|
+
→ install via the marketplace tool's `installCommand` (HTTP ZIP download, no git required) and its `installHint` on failure.
|
|
73
73
|
→ load and execute.
|
|
@@ -852,6 +852,10 @@ If the status code is not 2xx/3xx:
|
|
|
852
852
|
|
|
853
853
|
### Step 7: Expose via DevBridge [REQUIRED — deployment incomplete without this]
|
|
854
854
|
|
|
855
|
+
**Preferred: call `huaweicloud_sandbox_expose_tunnel` with the ACTUAL port returned by `deploy_nginx` (`{"port": <port>}`)** — it authenticates automatically (probing AK/SK vs API-Key builds), pre-cleans stale tunnels, starts the host, extracts the public URL, and health-checks it before returning. If it reports `ok: false` with an API-Key hint, create the key and re-run `sandbox_credentials`, then call it again. The tool's returned `publicUrl` is the deployment result.
|
|
856
|
+
|
|
857
|
+
**Manual fallback** (only when the tool is unavailable or must be debugged by hand): follow [Expose the deployed app](#expose-the-deployed-app-public-url) below.
|
|
858
|
+
|
|
855
859
|
When `deploy_check` returns `nextStep: expose_via_devbridge`, it also returns an executable `remediation` string — run those devbridge commands in the sandbox (after `source /tmp/hw_creds.sh`). Until a tunnel exists, `deploy_check` returns no `publicUrl`; do not fabricate a URL.
|
|
856
860
|
|
|
857
861
|
Follow the standard [Expose the deployed app](#expose-the-deployed-app-public-url) procedure. The app is already running on the detected port — only DevBridge tunnel setup is needed.
|
|
@@ -860,6 +864,8 @@ Use `exec_with_session` to background DevBridge. For SSR, DevBridge tunnels the
|
|
|
860
864
|
|
|
861
865
|
**Pre-flight**: always run `devbridge delete-all` before creating a new tunnel to prevent `10006: quota exceeded` from accumulated stale tunnels. If you still get quota error, list tunnels with `devbridge list` (0.2.x plain table; 0.1.x legacy `-j` JSON), delete stale ones, and retry.
|
|
862
866
|
|
|
867
|
+
> **Concurrency warning**: `devbridge delete-all` and `pkill -f "devbridge host"` are GLOBAL operations scoped to the whole sandbox — if multiple sessions share the same sandbox, pre-cleaning can kill tunnels published by other sessions and invalidate URLs you handed out earlier. For shared sandboxes, prefer `devbridge list` and delete only stale/your-own tunnels instead of blanket `delete-all`, and re-expose only after confirming the target port is still served.
|
|
868
|
+
|
|
863
869
|
Extract the tunnel URL from DevBridge output. The public URL has the form `https://<id>-<port>.devbridge-s2.hwtunnel.com`. **Return this URL to the developer as the deployment result.**
|
|
864
870
|
|
|
865
871
|
#### Cross-platform H5 QR code
|
|
@@ -21,7 +21,7 @@ Use this skill to turn vague developer intent into a precise Huawei Cloud capabi
|
|
|
21
21
|
8. Treat Terraform as a secondary V1 path for reviewed IaC, not the default.
|
|
22
22
|
9. When no built-in devkit skill matches:
|
|
23
23
|
a. Search: use `huaweicloud_search_marketplace` to find a matching skill in the community marketplace
|
|
24
|
-
b. Install:
|
|
24
|
+
b. Install: use the `installCommand` returned by the marketplace tool — it downloads the skill repo ZIP over HTTP and copies the skill folder into the agent skills directory. **No git is required** (`npx skills add` needs git on PATH, which is often absent on Windows Server/sandbox environments). Replace the `<your-agent-skills-dir>` placeholder with this agent's skills directory (Claude Code `~/.claude/skills`, OpenCode `~/.config/opencode/skills`, Cursor `~/.cursor/skills`) before running. If the command reports `SKILL_SRC_NOT_FOUND` (mis-pathed skill location) or the HTTP download itself fails, follow the returned `installHint`: download `https://github.com/huaweicloud/huaweicloud-skills/archive/refs/heads/master.zip` manually, extract it, copy the skill folder, and confirm to the user how it was installed. Never auto-install git just to fetch a skill.
|
|
25
25
|
c. Execute: load the installed skill and follow its procedure
|
|
26
26
|
10. When the deliverable is a PPT, architecture diagram (draw.io), or frontend page that needs official Huawei Cloud service logos, use the `huaweicloud_get_service_icon` MCP tool to get logo URLs from the official Icons library instead of guessing or hotlinking unofficial images.
|
|
27
27
|
|
|
@@ -84,7 +84,13 @@ Agent processes find executables through `PATH`. If OpenCode/Codex cannot find `
|
|
|
84
84
|
2. **KooCLI first-run privacy agreement**: On a fresh KooCLI install, `hcloud` blocks with `同意并继续使用(y)/不同意并退出(N)` and fails with `[USE_ERROR]您输入的是无效字符` in non-interactive mode. Detection: check command output for these strings. Ask the user: "KooCLI needs to accept its privacy agreement. May I accept it on your behalf?" If the user agrees, run `huaweicloud_run_readonly_command` with `args=["version"]` and `stdin="y\n"`. This accepts the agreement once, after which hcloud works normally.
|
|
85
85
|
3. Ask the user to configure credentials outside the agent conversation when setup is needed.
|
|
86
86
|
4. Inspect profile and region only through redacted tooling.
|
|
87
|
-
5.
|
|
87
|
+
5. **Always discover exact operation names and parameters with `--help` BEFORE constructing the command.** Never guess from general knowledge — parameter names change across API versions. The discovery chain is:
|
|
88
|
+
1. `hcloud --help` → available services
|
|
89
|
+
2. `hcloud <Service> --help` → operations (use `huaweicloud_list_operations` or step 2/3 directly)
|
|
90
|
+
3. `hcloud <Service> <Operation> --help` → exact parameter names, required flags, and value formats
|
|
91
|
+
4. Execute only after the help text confirms the operation and its parameters
|
|
92
|
+
- Service skills may skip steps 1-2 when they already provide the correct service and operation names, but step 3 (`<Operation> --help`) is never skippable before planning a write operation or a command with parameters.
|
|
93
|
+
- Example: ECS instance listing is commonly `ECS ListServersDetails`; ECS creation is commonly `ECS CreateServers`; image lookup may be under `IMS GlanceShowImage`.
|
|
88
94
|
6. Use `--cli-output=json` for machine-readable responses when supported.
|
|
89
95
|
7. For resource operations, include `--cli-region`, `--cli-profile`, and service-specific project information when required.
|
|
90
96
|
8. Classify every command before running it:
|
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
globalCredentialsPath,
|
|
9
9
|
isPlaceholder,
|
|
10
10
|
obsConfigPath,
|
|
11
|
+
parseStsExpiry,
|
|
11
12
|
readCodeArtsCredentials,
|
|
12
13
|
readGlobalCredentials,
|
|
14
|
+
resolveCredentialsWithRuntime,
|
|
13
15
|
writeLastSync,
|
|
14
16
|
writeObsConfig,
|
|
15
17
|
} from './credentials.mjs';
|
|
@@ -162,18 +164,52 @@ export function computeOnboarding({ credentials, reconciled } = {}) {
|
|
|
162
164
|
return { needsSetup: true, scenario, reason, message, steps, accountHint };
|
|
163
165
|
}
|
|
164
166
|
|
|
167
|
+
// Compute temporary-STS expiry info for auth_status. Permanent AK/SK creds carry
|
|
168
|
+
// no security token and therefore have no expiry — we return null so callers only
|
|
169
|
+
// see expiry for the platform-injected temporary STS case.
|
|
170
|
+
function computeStsExpiry() {
|
|
171
|
+
let creds;
|
|
172
|
+
try {
|
|
173
|
+
creds = resolveCredentialsWithRuntime({ allowMissing: true });
|
|
174
|
+
} catch {
|
|
175
|
+
creds = null;
|
|
176
|
+
}
|
|
177
|
+
if (!creds?.ak || !creds?.sk || !creds.securityToken) return null;
|
|
178
|
+
const expiresAt = parseStsExpiry({ securityToken: creds.securityToken });
|
|
179
|
+
if (expiresAt === null) return { status: 'unknown', expiresAt: null };
|
|
180
|
+
const remainingMs = expiresAt - Date.now();
|
|
181
|
+
const expiresAtUtc = new Date(expiresAt).toISOString();
|
|
182
|
+
const expiresAtLocal = new Date(expiresAt).toString();
|
|
183
|
+
let status = 'valid';
|
|
184
|
+
if (remainingMs <= 0) status = 'expired';
|
|
185
|
+
else if (remainingMs <= 5 * 60 * 1000) status = 'expiring_soon';
|
|
186
|
+
return { expiresAt, expiresAtUtc, expiresAtLocal, remainingMs, status };
|
|
187
|
+
}
|
|
188
|
+
|
|
165
189
|
export function getAuthStatus(target = 'all') {
|
|
166
190
|
const credentials = readGlobalCredentials();
|
|
167
191
|
const reconciled = { ...exportStateForStatus(), runtimeActive: hasRuntimeCredentials() };
|
|
168
192
|
const hcloud = probeHcloud();
|
|
169
193
|
const s4Creds = readCodeArtsCredentials();
|
|
170
194
|
const onboarding = computeOnboarding({ credentials, reconciled });
|
|
195
|
+
const stsExpiry = computeStsExpiry();
|
|
196
|
+
const credentialPanel = {
|
|
197
|
+
sts: stsExpiry, // 临时 STS(MCP==沙箱同源)
|
|
198
|
+
s1: {
|
|
199
|
+
configured: Boolean(credentials?.ak && credentials?.sk),
|
|
200
|
+
fingerprint: credentials?.ak ? fingerprint(credentials.ak, credentials.sk) : null,
|
|
201
|
+
persistent: true, // 永久,无过期
|
|
202
|
+
},
|
|
203
|
+
activeSource: stsExpiry ? 'temporary-sts' : credentials?.ak ? 's1-persistent' : 'none',
|
|
204
|
+
};
|
|
171
205
|
return {
|
|
172
206
|
target,
|
|
173
207
|
credentialsConfigured: Boolean(credentials?.ak && credentials?.sk),
|
|
174
208
|
credentialsPath: globalCredentialsPath(),
|
|
175
209
|
mcpSettingsConfigured: Boolean(s4Creds?.ak && s4Creds?.sk),
|
|
176
210
|
mcpSettingsSource: s4Creds ? 'codearts' : null,
|
|
211
|
+
stsExpiry,
|
|
212
|
+
credentialPanel,
|
|
177
213
|
obsConfigured: existsSync(obsConfigPath()),
|
|
178
214
|
obsConfigPath: obsConfigPath(),
|
|
179
215
|
kooCliInstalled: hcloud.installed,
|
|
@@ -282,7 +282,8 @@ export function detectFramework(projectPath) {
|
|
|
282
282
|
if (
|
|
283
283
|
existsSync(join(projectPath, 'next.config.js')) ||
|
|
284
284
|
existsSync(join(projectPath, 'next.config.mjs')) ||
|
|
285
|
-
existsSync(join(projectPath, 'next.config.ts'))
|
|
285
|
+
existsSync(join(projectPath, 'next.config.ts')) ||
|
|
286
|
+
existsSync(join(projectPath, 'next.config.cjs'))
|
|
286
287
|
) {
|
|
287
288
|
return frameworkResult(FRAMEWORKS.nextjs, pm, projectPath);
|
|
288
289
|
}
|
|
@@ -290,7 +291,9 @@ export function detectFramework(projectPath) {
|
|
|
290
291
|
if (
|
|
291
292
|
existsSync(join(projectPath, 'nuxt.config.js')) ||
|
|
292
293
|
existsSync(join(projectPath, 'nuxt.config.mjs')) ||
|
|
293
|
-
existsSync(join(projectPath, 'nuxt.config.ts'))
|
|
294
|
+
existsSync(join(projectPath, 'nuxt.config.ts')) ||
|
|
295
|
+
existsSync(join(projectPath, 'nuxt.config.cjs')) ||
|
|
296
|
+
existsSync(join(projectPath, 'nuxt.config.mts'))
|
|
294
297
|
) {
|
|
295
298
|
return frameworkResult(FRAMEWORKS.nuxt, pm, projectPath);
|
|
296
299
|
}
|
|
@@ -315,7 +318,8 @@ export function detectFramework(projectPath) {
|
|
|
315
318
|
if (
|
|
316
319
|
existsSync(join(projectPath, 'docusaurus.config.js')) ||
|
|
317
320
|
existsSync(join(projectPath, 'docusaurus.config.ts')) ||
|
|
318
|
-
existsSync(join(projectPath, 'docusaurus.config.mjs'))
|
|
321
|
+
existsSync(join(projectPath, 'docusaurus.config.mjs')) ||
|
|
322
|
+
existsSync(join(projectPath, 'docusaurus.config.cjs'))
|
|
319
323
|
) {
|
|
320
324
|
return frameworkResult(FRAMEWORKS.docusaurus, pm, projectPath);
|
|
321
325
|
}
|
|
@@ -336,7 +340,9 @@ export function detectFramework(projectPath) {
|
|
|
336
340
|
if (
|
|
337
341
|
existsSync(join(projectPath, 'vite.config.js')) ||
|
|
338
342
|
existsSync(join(projectPath, 'vite.config.mjs')) ||
|
|
339
|
-
existsSync(join(projectPath, 'vite.config.ts'))
|
|
343
|
+
existsSync(join(projectPath, 'vite.config.ts')) ||
|
|
344
|
+
existsSync(join(projectPath, 'vite.config.cjs')) ||
|
|
345
|
+
existsSync(join(projectPath, 'vite.config.mts'))
|
|
340
346
|
) {
|
|
341
347
|
return frameworkResult(FRAMEWORKS.vite, pm, projectPath);
|
|
342
348
|
}
|
|
@@ -358,9 +364,39 @@ export function detectFramework(projectPath) {
|
|
|
358
364
|
}
|
|
359
365
|
}
|
|
360
366
|
|
|
361
|
-
if (
|
|
367
|
+
if (
|
|
368
|
+
existsSync(join(projectPath, 'index.html')) ||
|
|
369
|
+
existsSync(join(projectPath, 'public', 'index.html')) ||
|
|
370
|
+
existsSync(join(projectPath, 'src', 'index.html'))
|
|
371
|
+
) {
|
|
362
372
|
return frameworkResult(FRAMEWORKS.static, pm, projectPath);
|
|
363
373
|
}
|
|
364
374
|
|
|
375
|
+
if (pkg) {
|
|
376
|
+
const entryPoints = [
|
|
377
|
+
'src/main.js',
|
|
378
|
+
'src/main.ts',
|
|
379
|
+
'src/main.jsx',
|
|
380
|
+
'src/main.tsx',
|
|
381
|
+
'src/App.vue',
|
|
382
|
+
'src/app.vue',
|
|
383
|
+
'src/main.vue',
|
|
384
|
+
'src/index.js',
|
|
385
|
+
'src/index.ts',
|
|
386
|
+
];
|
|
387
|
+
const hasEntry = entryPoints.some((p) => existsSync(join(projectPath, p)));
|
|
388
|
+
if (hasEntry) {
|
|
389
|
+
if ('vue' in deps || '@vue/cli-service' in deps) {
|
|
390
|
+
return frameworkResult(FRAMEWORKS.vueCli, pm, projectPath);
|
|
391
|
+
}
|
|
392
|
+
if ('react-scripts' in deps) {
|
|
393
|
+
return frameworkResult(FRAMEWORKS.cra, pm, projectPath);
|
|
394
|
+
}
|
|
395
|
+
if ('svelte' in deps || 'solid-js' in deps) {
|
|
396
|
+
return frameworkResult(FRAMEWORKS.vite, pm, projectPath);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
365
401
|
return null;
|
|
366
402
|
}
|
|
@@ -6,7 +6,7 @@ import { dirname, join } from 'node:path';
|
|
|
6
6
|
|
|
7
7
|
import { classifyHcloudArgs, redactSecrets, assertAllowed } from './safety-policy.mjs';
|
|
8
8
|
import { getProxySettings } from './proxy/proxy-config.mjs';
|
|
9
|
-
import {
|
|
9
|
+
import { resolveHcloudCommand } from './hcloud-probe.mjs';
|
|
10
10
|
import { parseStsExpiry, resolveCredentialsWithRuntime } from './auth/credentials.mjs';
|
|
11
11
|
|
|
12
12
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
@@ -391,6 +391,24 @@ function appendLangHint(result, service, metaDir) {
|
|
|
391
391
|
};
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
+
// Returns true when the current credentials are a TEMPORARY STS set that has
|
|
395
|
+
// already expired (per HW_STS_EXPIRES_AT / token expiry). Lets us fail fast with
|
|
396
|
+
// a clear CREDENTIAL_EXPIRED instead of letting a doomed IAM call return a
|
|
397
|
+
// misleading error. Permanent AK/SK (no token) and unparseable expiry are never
|
|
398
|
+
// treated as expired.
|
|
399
|
+
function isTemporaryStsExpired() {
|
|
400
|
+
let creds;
|
|
401
|
+
try {
|
|
402
|
+
creds = resolveCredentialsWithRuntime({ allowMissing: true });
|
|
403
|
+
} catch {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
if (!creds?.securityToken) return false;
|
|
407
|
+
const expiry = parseStsExpiry({ securityToken: creds.securityToken });
|
|
408
|
+
if (expiry === null) return false; // unknown → don't block
|
|
409
|
+
return expiry <= Date.now();
|
|
410
|
+
}
|
|
411
|
+
|
|
394
412
|
export async function runHcloud(args, options = {}) {
|
|
395
413
|
const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
|
|
396
414
|
const plan = {
|
|
@@ -399,6 +417,21 @@ export async function runHcloud(args, options = {}) {
|
|
|
399
417
|
};
|
|
400
418
|
assertAllowed(plan.classification);
|
|
401
419
|
|
|
420
|
+
// A: fail fast when the platform-injected temporary STS is already expired —
|
|
421
|
+
// otherwise hcloud would make a doomed IAM round trip and surface a misleading
|
|
422
|
+
// error (e.g. "缺少 cli-domain-id"). Permanent AK/SK are unaffected.
|
|
423
|
+
if (isTemporaryStsExpired()) {
|
|
424
|
+
return {
|
|
425
|
+
ok: false,
|
|
426
|
+
code: 'CREDENTIAL_EXPIRED',
|
|
427
|
+
error:
|
|
428
|
+
'Temporary STS credentials have expired. Re-login or start a new session so the platform injects fresh credentials, then retry.',
|
|
429
|
+
stderr: '',
|
|
430
|
+
stdout: '',
|
|
431
|
+
plan,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
402
435
|
// Link B: when the runtime/environment carries live temporary STS credentials
|
|
403
436
|
// (security token set), KooCLI/obsutil would not see them (they read S2/S3 only
|
|
404
437
|
// and R3 forbids writing them to disk). Inject per-command so hcloud actually
|
|
@@ -430,6 +463,8 @@ async function runHcloudOnceWithRetries(plan, options) {
|
|
|
430
463
|
if (result.ok || attempt >= maxRetries || !isRetryableNetworkError(result)) {
|
|
431
464
|
const merged = {
|
|
432
465
|
...result,
|
|
466
|
+
ok: result.ok ?? false,
|
|
467
|
+
exitCode: result.exitCode ?? null,
|
|
433
468
|
retries: attempt,
|
|
434
469
|
attempts: attempt + 1,
|
|
435
470
|
};
|
|
@@ -460,15 +495,15 @@ async function runtimeCurrentMismatchWarning() {
|
|
|
460
495
|
}
|
|
461
496
|
}
|
|
462
497
|
|
|
463
|
-
function discoverHcloudPath() {
|
|
464
|
-
return findHcloudBin();
|
|
465
|
-
}
|
|
466
|
-
|
|
467
498
|
function runHcloudOnce(plan, options) {
|
|
468
499
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
469
500
|
const forceKillAfterMs = options.forceKillAfterMs ?? DEFAULT_FORCE_KILL_AFTER_MS;
|
|
470
|
-
const executable
|
|
471
|
-
|
|
501
|
+
const { executable: resolvedExec, argsPrefix } = resolveHcloudCommand({
|
|
502
|
+
executable: options.executable || options.env?.HCLOUD_BIN,
|
|
503
|
+
executableArgs: options.executableArgs,
|
|
504
|
+
});
|
|
505
|
+
const executable = resolvedExec || 'hcloud';
|
|
506
|
+
const executableArgs = argsPrefix;
|
|
472
507
|
const cwd = options.cwd || undefined;
|
|
473
508
|
const stdin = options.stdin ?? 'y\n';
|
|
474
509
|
const childOptions = { ...options };
|
|
@@ -540,6 +575,8 @@ function runHcloudOnce(plan, options) {
|
|
|
540
575
|
ok: false,
|
|
541
576
|
code: 'TIMEOUT',
|
|
542
577
|
error: `hcloud command timed out after ${timeoutMs} ms.`,
|
|
578
|
+
exitCode: null,
|
|
579
|
+
signal: null,
|
|
543
580
|
stdout: redactOutput(stdout),
|
|
544
581
|
stderr: redactOutput(stderr),
|
|
545
582
|
plan,
|
|
@@ -558,6 +595,8 @@ function runHcloudOnce(plan, options) {
|
|
|
558
595
|
ok: false,
|
|
559
596
|
code: 'SPAWN_ERROR',
|
|
560
597
|
error: error.message,
|
|
598
|
+
exitCode: null,
|
|
599
|
+
signal: null,
|
|
561
600
|
plan,
|
|
562
601
|
});
|
|
563
602
|
});
|