huaweicloud-devkit 1.1.7 → 1.1.8-next.1
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 +1 -1
- package/README.zh-CN.md +1 -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 +9 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +15 -2
- package/plugins/huaweicloud-core/src/auth/service.mjs +36 -0
- package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +23 -5
- package/plugins/huaweicloud-core/src/detect-framework.mjs +41 -5
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +78 -8
- package/plugins/huaweicloud-core/src/hcloud-probe.mjs +10 -2
- package/plugins/huaweicloud-core/src/mcp-server.mjs +9 -0
- package/plugins/huaweicloud-core/src/preflight.mjs +125 -0
- 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 +214 -27
- package/plugins/huaweicloud-core/src/search-market.mjs +40 -1
- package/plugins/huaweicloud-core/src/setup-cli.mjs +57 -17
- package/plugins/huaweicloud-core/src/tools.mjs +307 -20
- 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
|
|
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
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.
|
|
@@ -785,7 +785,7 @@ check_port <nodePort>
|
|
|
785
785
|
| SPA/SSG/Cross-platform | nginx port (from `framework.detect`) | Kill old process, then configure nginx |
|
|
786
786
|
| SSR | nginx public port + Node app port | Kill old processes on both ports |
|
|
787
787
|
|
|
788
|
-
If the port cannot be freed (different user/process),
|
|
788
|
+
If the port cannot be freed (different user/process), the `deploy_nginx` tool auto-increments to the next available port (up to 10 attempts). For SSR (proxy) mode, nginx listens on the resolved `targetPort`, and the Node app follows the returned `nodePort` field — bind the Node process to the `nodePort` that `deploy_nginx` returns, and update all subsequent nginx config and DevBridge references accordingly.
|
|
789
789
|
|
|
790
790
|
#### Configure Nginx
|
|
791
791
|
|
|
@@ -806,7 +806,7 @@ Use `huaweicloud_sandbox_deploy_nginx` to write the correct template, fix direct
|
|
|
806
806
|
- `port` — listen port from framework detection
|
|
807
807
|
- `project` — project dir name under `/workspace`
|
|
808
808
|
- `output_dir` — build output dir relative to `/workspace/<project>`
|
|
809
|
-
- `node_port` — Node.js app port for SSR proxy.
|
|
809
|
+
- `node_port` — Node.js app port for SSR proxy. When omitted it defaults to the resolved nginx listen port plus one (`targetPort + 1`), and the result includes `nodePort` so you know which port to bind the Node process to. When the nginx port auto-increments, `nodePort` follows it.
|
|
810
810
|
- `public_port` — public listen port for SSR proxy (optional, defaults to `port`)
|
|
811
811
|
|
|
812
812
|
The tool automatically handles:
|
|
@@ -834,7 +834,7 @@ If the status code is not 2xx/3xx:
|
|
|
834
834
|
### Step 6: Start the App [REQUIRED]
|
|
835
835
|
|
|
836
836
|
- **Static (SPA/SSG/cross-platform)**: nginx is already serving. Skip.
|
|
837
|
-
- **SSR**: `PORT=<nodePort>` prefix is REQUIRED before `<serveCmd>`. nginx `proxy_pass` targets `<nodePort>`, not `<port>` — the two must differ. `deployNginx` returns `nodePort` in its result (defaults to
|
|
837
|
+
- **SSR**: `PORT=<nodePort>` prefix is REQUIRED before `<serveCmd>`. nginx `proxy_pass` targets `<nodePort>`, not `<port>` — the two must differ. `deployNginx` returns `nodePort` in its result (defaults to the resolved `targetPort + 1` for proxy type). Always use the returned `port`/`nodePort` values — they follow any auto-increment.
|
|
838
838
|
|
|
839
839
|
**Runtime environment variables**: SSR apps often need `DATABASE_URL`, `NEXTAUTH_URL`, `NEXTAUTH_SECRET`, etc. at runtime. Before starting, verify env vars from Step 4a are still loaded, and re-source `.env` if the shell session was reset:
|
|
840
840
|
|
|
@@ -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
|
|
|
@@ -65,7 +65,14 @@ One-liner (installs `latest`, may drift from v7.2.12):
|
|
|
65
65
|
curl -sSL https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
**No restart needed after install**: DevKit locates `hcloud` dynamically on every
|
|
69
|
+
call — it checks `HCLOUD_BIN`, then the fixed install dirs (`~/.local/bin/hcloud`
|
|
70
|
+
on Linux, `/usr/local/bin/hcloud` on macOS, `~/hcloud/hcloud.exe` on Windows),
|
|
71
|
+
then PATH. Once `install-hcloud` or the background auto-install writes the binary
|
|
72
|
+
to one of those dirs, the very next `check_cli` / `run_*` call uses it. Do not
|
|
73
|
+
tell users to restart the agent.
|
|
74
|
+
|
|
75
|
+
Agent processes find executables through `PATH`; `HCLOUD_BIN` overrides it. DevKit re-locates hcloud from its **fixed install dirs** on every call, so a freshly installed binary (`~/.local/bin` on Linux, `/usr/local/bin` on macOS, `~/hcloud` on Windows) or a re-set `HCLOUD_BIN` is picked up by the next `check_cli` / `run_*` call without restarting the agent. A custom `PATH` entry is only honored via `HCLOUD_BIN` or the PATH locator fallback, and shell `PATH` changes take effect only for processes started after the update.
|
|
69
76
|
|
|
70
77
|
## Configure Credentials Outside Chat
|
|
71
78
|
|
|
@@ -84,7 +91,13 @@ Agent processes find executables through `PATH`. If OpenCode/Codex cannot find `
|
|
|
84
91
|
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
92
|
3. Ask the user to configure credentials outside the agent conversation when setup is needed.
|
|
86
93
|
4. Inspect profile and region only through redacted tooling.
|
|
87
|
-
5.
|
|
94
|
+
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:
|
|
95
|
+
1. `hcloud --help` → available services
|
|
96
|
+
2. `hcloud <Service> --help` → operations (use `huaweicloud_list_operations` or step 2/3 directly)
|
|
97
|
+
3. `hcloud <Service> <Operation> --help` → exact parameter names, required flags, and value formats
|
|
98
|
+
4. Execute only after the help text confirms the operation and its parameters
|
|
99
|
+
- 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.
|
|
100
|
+
- Example: ECS instance listing is commonly `ECS ListServersDetails`; ECS creation is commonly `ECS CreateServers`; image lookup may be under `IMS GlanceShowImage`.
|
|
88
101
|
6. Use `--cli-output=json` for machine-readable responses when supported.
|
|
89
102
|
7. For resource operations, include `--cli-region`, `--cli-profile`, and service-specific project information when required.
|
|
90
103
|
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,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"icons": [
|
|
6
6
|
{
|
|
7
7
|
"id": "codeartsdoer",
|
|
8
|
-
"name": "
|
|
8
|
+
"name": "华为云码道CodeArts代码智能体",
|
|
9
9
|
"category": "人工智能",
|
|
10
10
|
"subcategory": "智能体",
|
|
11
11
|
"description": "更懂你编码需求的AI研发专家",
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"id": "modelarts",
|
|
44
|
-
"name": "魔坊(ModelArts
|
|
44
|
+
"name": "魔坊(ModelArts)",
|
|
45
45
|
"category": "人工智能",
|
|
46
46
|
"subcategory": "模型开发平台",
|
|
47
47
|
"description": "“数-训-推-强”,一站式AI开发平台",
|
|
48
48
|
"product_url": "https://www.huaweicloud.com/product/modelarts.html",
|
|
49
49
|
"logo": {
|
|
50
|
-
"source_url": "https://res-static.hc-cdn.cn/cloudbu-site/public/
|
|
50
|
+
"source_url": "https://res-static.hc-cdn.cn/cloudbu-site/public/%E6%96%B0%E7%89%88/CF057503-AE1E-48CA-E8C1-1919F51A1261.png",
|
|
51
51
|
"local_path": "icons/assets/logo/modelarts.png"
|
|
52
52
|
},
|
|
53
53
|
"architecture": {
|
|
@@ -155,8 +155,8 @@
|
|
|
155
155
|
"description": "高效敏捷安全的一站式企业级智能体平台",
|
|
156
156
|
"product_url": "https://www.huaweicloud.com/product/agentarts.html",
|
|
157
157
|
"logo": {
|
|
158
|
-
"source_url": "https://res-static.hc-cdn.cn/cloudbu-site/
|
|
159
|
-
"local_path": "icons/assets/logo/agentarts.
|
|
158
|
+
"source_url": "https://res-static.hc-cdn.cn/cloudbu-site/china/zh-cn/%E6%99%BA%E6%9E%9CAgentArts/AgentArts-%E5%AE%98%E7%BD%91%E4%BA%A7%E5%93%81%E9%A1%B5%E5%88%87%E5%9B%BE/bannerlogo.png",
|
|
159
|
+
"local_path": "icons/assets/logo/agentarts.png"
|
|
160
160
|
},
|
|
161
161
|
"architecture": {
|
|
162
162
|
"status": "planned",
|
|
@@ -165,6 +165,24 @@
|
|
|
165
165
|
"aliases": ["agentarts"],
|
|
166
166
|
"tags": ["人工智能", "智能体平台", "agentarts"]
|
|
167
167
|
},
|
|
168
|
+
{
|
|
169
|
+
"id": "officeace",
|
|
170
|
+
"name": "果办OfficeAce 办公智能体",
|
|
171
|
+
"category": "人工智能",
|
|
172
|
+
"subcategory": "智能体",
|
|
173
|
+
"description": "专业办公级AI助手,PPT一键生成、专家团研讨,安全高效随时待命",
|
|
174
|
+
"product_url": "https://www.huaweicloud.com/product/agentarts/officeace.html",
|
|
175
|
+
"logo": {
|
|
176
|
+
"source_url": "https://res-static.hc-cdn.cn/cloudbu-site/china/zh-cn/yunying/product/OfficeAce-v2.svg",
|
|
177
|
+
"local_path": "icons/assets/logo/officeace.svg"
|
|
178
|
+
},
|
|
179
|
+
"architecture": {
|
|
180
|
+
"status": "planned",
|
|
181
|
+
"local_path": "icons/assets/architecture/officeace.svg"
|
|
182
|
+
},
|
|
183
|
+
"aliases": ["officeace", "office-ace"],
|
|
184
|
+
"tags": ["人工智能", "智能体", "办公", "officeace"]
|
|
185
|
+
},
|
|
168
186
|
{
|
|
169
187
|
"id": "pangu-nlp",
|
|
170
188
|
"name": "LLM大模型",
|
|
@@ -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
|
}
|