huaweicloud-devkit 1.1.0-next.15 → 1.1.0-next.17

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 CHANGED
@@ -14,6 +14,14 @@ Supports OpenCode, Codex, CodeArts Agent, WorkBuddy, DeepSeek Harness (DSH), Off
14
14
 
15
15
  - Node.js >= 22
16
16
 
17
+ > **China mainland users**: If you experience slow downloads or connection issues with the default npm registry, configure the Huawei Cloud npm mirror:
18
+ >
19
+ > ```bash
20
+ > npm config set registry https://mirrors.huaweicloud.com/repository/npm/
21
+ > ```
22
+ >
23
+ > Restore the default registry: `npm config delete registry`
24
+
17
25
  ## Quick Start
18
26
 
19
27
  > If `--target` is omitted, the installer auto-detects agents on your machine. When multiple agents are detected, **all of them** will be installed. Specify `--target` to control which agent receives the install.
@@ -68,6 +76,23 @@ npx --yes huaweicloud-devkit uninstall --target codearts
68
76
 
69
77
  > **Sandbox mode**: CodeArts defaults to sandbox mode which blocks KooCLI. `install-hcloud` detects this and shows how to resolve it — install KooCLI outside the sandbox terminal, or disable sandbox mode in CodeArts settings (Settings → Chats → Agents Terminal Command Running Mode → Auto Running).
70
78
 
79
+ ### CodeArts Work
80
+
81
+ ```bash
82
+ npx --yes huaweicloud-devkit install --target codearts-work
83
+ ```
84
+
85
+ **Restart the session** after installation.
86
+
87
+ ```bash
88
+ npx --yes huaweicloud-devkit doctor --target codearts-work
89
+ npx --yes huaweicloud-devkit status --target codearts-work
90
+ npx --yes huaweicloud-devkit update --target codearts-work
91
+ npx --yes huaweicloud-devkit uninstall --target codearts-work
92
+ ```
93
+
94
+ > **CodeArts Work** (CodeArts Space, appId: `com.codearts.work`) uses user-level config at `%USERPROFILE%\.codeartswork\`. No project-level `.codeartswork` directory is created.
95
+
71
96
  ### WorkBuddy
72
97
 
73
98
  ```bash
package/README.zh-CN.md CHANGED
@@ -14,6 +14,14 @@
14
14
 
15
15
  - Node.js >= 22
16
16
 
17
+ > **国内用户**:使用默认 npm 镜像时可能遇到下载缓慢或连接失败的问题,建议配置华为云 npm 镜像:
18
+ >
19
+ > ```bash
20
+ > npm config set registry https://mirrors.huaweicloud.com/repository/npm/
21
+ > ```
22
+ >
23
+ > 恢复默认镜像:`npm config delete registry`
24
+
17
25
  ## 快速开始
18
26
 
19
27
  > 省略 `--target` 时,安装器会自动检测机器上的 agent,检测到多个时**全部安装**。建议始终指定 `--target` 以明确安装目标。
@@ -68,6 +76,23 @@ npx --yes huaweicloud-devkit uninstall --target codearts
68
76
 
69
77
  > **沙箱模式**:码道默认沙箱模式会阻止 KooCLI 运行。`install-hcloud` 自动检测并给出指引——请在码道外终端安装使用 KooCLI,或在码道设置中关闭沙箱模式(设置 → 对话流 → 智能体 终端命令运行模式 → 自动运行)。
70
78
 
79
+ ### CodeArts Work(码道工作空间)
80
+
81
+ ```bash
82
+ npx --yes huaweicloud-devkit install --target codearts-work
83
+ ```
84
+
85
+ 安装后**重启会话**。
86
+
87
+ ```bash
88
+ npx --yes huaweicloud-devkit doctor --target codearts-work
89
+ npx --yes huaweicloud-devkit status --target codearts-work
90
+ npx --yes huaweicloud-devkit update --target codearts-work
91
+ npx --yes huaweicloud-devkit uninstall --target codearts-work
92
+ ```
93
+
94
+ > **CodeArts Work**(工作空间,appId: `com.codearts.work`)使用用户级配置 `%USERPROFILE%\.codeartswork\`,不创建项目级目录。
95
+
71
96
  ### WorkBuddy
72
97
 
73
98
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.0-next.15",
3
+ "version": "1.1.0-next.17",
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": [
@@ -49,6 +49,8 @@
49
49
  "agent",
50
50
  "codex",
51
51
  "opencode",
52
+ "codearts",
53
+ "codearts-work",
52
54
  "officeace",
53
55
  "office-claw",
54
56
  "workbuddy",
@@ -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.0-next.15",
20
+ "version": "1.1.0-next.17",
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.0-next.15",
23
+ "version": "1.1.0-next.17",
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.0-next.15",
20
+ "version": "1.1.0-next.17",
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.0-next.15",
3
+ "version": "1.1.0-next.17",
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.0-next.15",
3
+ "version": "1.1.0-next.17",
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",
@@ -138,6 +138,31 @@ def first_denied_command_rule(text):
138
138
  return None
139
139
 
140
140
 
141
+ def evaluate(tool_name, tool_input):
142
+ """Run the full safety check and return a raw block reason, or None to allow.
143
+
144
+ Shared by the CLI shell (``main``) and the Hermes Python-plugin hook, which
145
+ reuses this module via ``importlib`` so a single implementation enforces the
146
+ policy everywhere. The returned reason does NOT include ``DENY_PREFIX`` —
147
+ callers decide the exact output shape.
148
+ """
149
+ text = command_text(tool_input)
150
+
151
+ if CONFIG_FILE_RE and CONFIG_FILE_RE.search(text):
152
+ return "reading Huawei Cloud credential/profile files can expose AK/SK or tokens. Use redacted toolkit tools."
153
+ if ENV_DUMP_RE.search(text):
154
+ return "dumping cloud credential environment variables is not allowed."
155
+ if SECRET_READ_RE and SECRET_READ_RE.search(text):
156
+ return "direct secret value retrieval would put plaintext secrets into the agent context."
157
+ denied_rule = first_denied_command_rule(text)
158
+ if denied_rule:
159
+ return f"{denied_rule.get('message')} Remediation: {denied_rule.get('remediation')}"
160
+ if tool_name in ("Bash", "terminal") and HCLOUD_RE.search(text) and WRITE_OPERATION_RE.search(text) and not READ_OPERATION_RE.search(text):
161
+ return "unapproved Huawei Cloud write operations must be planned first and explicitly approved by the user."
162
+
163
+ return None
164
+
165
+
141
166
  def main():
142
167
  try:
143
168
  data = json.load(sys.stdin)
@@ -145,20 +170,12 @@ def main():
145
170
  allow()
146
171
 
147
172
  tool_name = data.get("tool_name", "")
148
- text = command_text(data.get("tool_input", {}))
173
+ tool_input = data.get("tool_input", {})
149
174
  hermes = "hook_event_name" in data
150
175
 
151
- if CONFIG_FILE_RE and CONFIG_FILE_RE.search(text):
152
- deny("reading Huawei Cloud credential/profile files can expose AK/SK or tokens. Use redacted toolkit tools.", hermes)
153
- if ENV_DUMP_RE.search(text):
154
- deny("dumping cloud credential environment variables is not allowed.", hermes)
155
- if SECRET_READ_RE and SECRET_READ_RE.search(text):
156
- deny("direct secret value retrieval would put plaintext secrets into the agent context.", hermes)
157
- denied_rule = first_denied_command_rule(text)
158
- if denied_rule:
159
- deny(f"{denied_rule.get('message')} Remediation: {denied_rule.get('remediation')}", hermes)
160
- if tool_name in ("Bash", "terminal") and HCLOUD_RE.search(text) and WRITE_OPERATION_RE.search(text) and not READ_OPERATION_RE.search(text):
161
- deny("unapproved Huawei Cloud write operations must be planned first and explicitly approved by the user.", hermes)
176
+ reason = evaluate(tool_name, tool_input)
177
+ if reason:
178
+ deny(reason, hermes)
162
179
 
163
180
  allow()
164
181
 
@@ -2,7 +2,7 @@
2
2
  "name": "huaweicloud-devkit",
3
3
  "id": "huaweicloud-devkit",
4
4
  "displayName": "HuaweiCloud DevKit",
5
- "version": "1.1.0-next.15",
5
+ "version": "1.1.0-next.17",
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.",
@@ -42,14 +42,14 @@ Domain expertise for Huawei Cloud Identity and Access Management (IAM). Covers u
42
42
 
43
43
  ## Common Workflows
44
44
 
45
- | Task | Command | Steps |
46
- | ------------------------ | ---------------------------------------------------------------------------------------------- | ----------------------------- |
47
- | List users | hcloud IAM ListUsers | references/iam-ops.md |
48
- | Create group | hcloud IAM CreateGroup --group.name=<name> | references/iam-ops.md |
49
- | Create custom policy | hcloud IAM CreateCustomPolicy --policy.name=<n> --policy.document=<json> | references/policy-examples.md |
50
- | Create agency | hcloud IAM CreateAgency --agency.name=<n> --agency.domain_id=<id> --agency.trust_policy=<json> | references/agency.md |
51
- | Get temporary credential | hcloud STS GetTemporaryCredential --agency_name=<n> --domain_id=<id> --duration_seconds=3600 | references/sts.md |
52
- | Attach policy | hcloud IAM AttachGroupPolicy --group_id=<id> --policy_id=<id> | references/iam-ops.md |
45
+ | Task | Command | Steps |
46
+ | ------------------------ | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
47
+ | List users | hcloud IAM KeystoneListUsers --cli-region=<r> | references/iam-ops.md |
48
+ | Create group | hcloud IAM CreateGroupV5 --group_name=<name> | references/iam-ops.md |
49
+ | Create custom policy | hcloud IAM CreateCloudServiceCustomPolicy --role.display_name=<n> --role.type=<type> --role.policy.Statement.1.Effect=Allow | references/policy-examples.md |
50
+ | Create agency | hcloud IAM CreateAgency --agency.name=<n> --agency.domain_id=<id> --agency.trust_domain_name=<domain> | references/agency.md |
51
+ | Get temporary credential | hcloud STS AssumeAgency --agency_urn=<urn> --agency_session_name=<n> --duration_seconds=3600 | references/sts.md |
52
+ | Attach policy to group | hcloud IAM AttachGroupPolicyV5 --group_id=<id> --policy_id=<id> | references/iam-ops.md |
53
53
 
54
54
  ## Policy Examples (Least Privilege)
55
55
 
@@ -88,10 +88,10 @@ FunctionGraph needs an agency to access VPC resources (RDS, DCS, etc.):
88
88
 
89
89
  ```bash
90
90
  # 1. Create agency with FunctionGraph trust
91
- hcloud IAM CreateAgency --agency_name=<name> --trust_domain_name=functiongraph
91
+ hcloud IAM CreateAgency --agency.name=<name> --agency.trust_domain_name=functiongraph
92
92
 
93
- # 2. Attach VPC Administrator role
94
- hcloud IAM GrantRoleToAgency --agency_name=<name> --role_id=<role-id>
93
+ # 2. Grant role to agency
94
+ hcloud IAM AssociateAgencyWithDomainPermission --agency_id=<id> --role_id=<role-id>
95
95
 
96
96
  # 3. Use in CreateFunction
97
97
  hcloud FunctionGraph CreateFunction ... --app_xrole=<agency-name>
@@ -55,7 +55,7 @@ hcloud RDS <Operation> --cli-region=<region> [--key=value ...]
55
55
  hcloud RDS ListInstances --cli-region=<r>
56
56
  hcloud RDS ListFlavors --database_name=<engine> --cli-region=<r>
57
57
  hcloud RDS ListDatastores --database_name=<engine> --cli-region=<r>
58
- hcloud RDS ListEngineFlavors --instance_id=<id> --cli-region=<r>
58
+ hcloud RDS ListEngineFlavors --instance_id=<id> --availability_zone_ids=<az> --ha_mode=<mode> --cli-region=<r>
59
59
  ```
60
60
 
61
61
  ### Create Instance
@@ -150,7 +150,8 @@ hcloud RDS CreateRestoreInstance --instance_id=<id> --backup_id=<id> --name=<new
150
150
  ## Read Replicas
151
151
 
152
152
  ```bash
153
- hcloud RDS CreateReadReplica --replica_of_id=<primary-id> --name=<name> --flavor_ref=<id> --volume.type=<vol-type> --volume.size=<size> --cli-region=<r>
153
+ # Create a read replica via CreateInstance with --replica_of_id
154
+ hcloud RDS CreateInstance --replica_of_id=<primary-id> --name=<name> --flavor_ref=<id> --volume.type=<vol-type> --volume.size=<size> --cli-region=<r>
154
155
  ```
155
156
 
156
157
  ## Fault Diagnosis
@@ -159,7 +160,7 @@ hcloud RDS CreateReadReplica --replica_of_id=<primary-id> --name=<name> --flavor
159
160
  hcloud RDS ListErrorLogs --instance_id=<id> --start_date=2024-01-01T00:00:00Z --end_date=2024-01-31T23:59:59Z --cli-region=<r>
160
161
  hcloud RDS ListSlowLogs --instance_id=<id> --start_date=... --end_date=... --cli-region=<r>
161
162
  hcloud RDS ShowReplicationStatus --instance_id=<id> --cli-region=<r>
162
- hcloud RDS ListInstanceDiagnosis --cli-region=<r>
163
+ hcloud RDS ListInstanceDiagnosis --engine=<engine> --cli-region=<r>
163
164
  ```
164
165
 
165
166
  ## Connecting
@@ -253,7 +253,7 @@ fi
253
253
 
254
254
  ```bash
255
255
  # Step A: List all tunnels (both active and stale)
256
- devbridge ls --all
256
+ devbridge list -j
257
257
  # Step B: Remove all stale tunnels
258
258
  devbridge delete-all
259
259
  # Step C: Retry tunnel creation
@@ -265,6 +265,7 @@ This eliminates the most common deployment failure — historical tunnels from p
265
265
 
266
266
  - The public URL has the form `https://<id>-<port>.cn-north-4-bridge.myhuaweicloud.com` (from the `Tunnel URL:` line).
267
267
  - **Return this URL to the developer as the deployment result link.** Keep the host process running (do not close the session before handing over the URL).
268
+ - Tunnel `description` (`-d`) accepts only Chinese characters, letters, and digits (0-64). Symbols such as `-`/`_`/spaces are rejected (`Invalid tunnel description`).
268
269
  - Internal docs: https://huaweicloud.github.io/devspace-devbridge/
269
270
 
270
271
  **No local downgrade**: if the tunnel tooling cannot be installed in the sandbox, STOP and report a generic error ("无法生成访问地址") without technical detail. Never install it on the developer's local machine — a local install would defeat the purpose of sandbox deployment.
@@ -751,7 +752,7 @@ Follow the standard [Expose the deployed app](#expose-the-deployed-app-public-ur
751
752
 
752
753
  Use `exec_with_session` to background DevBridge. For SSR, DevBridge tunnels the nginx public port (not the Node port directly).
753
754
 
754
- **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 ls --all`, delete stale ones, and retry.
755
+ **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 -j`, delete stale ones, and retry.
755
756
 
756
757
  Extract the tunnel URL from DevBridge output. The public URL has the form `https://<id>-<port>.cn-north-4-bridge.myhuaweicloud.com`. **Return this URL to the developer as the deployment result.**
757
758
 
@@ -8,7 +8,7 @@ version: 1
8
8
 
9
9
  **STOP - Do not answer from general knowledge.** Follow the procedure below.
10
10
 
11
- Always run `hcloud SMN <Operation> --help` or `hcloud DMS <Operation> --help` before constructing commands.
11
+ Always run `hcloud SMN <Operation> --help`, `hcloud Kafka <Operation> --help`, `hcloud RabbitMQ <Operation> --help`, or `hcloud RocketMQ <Operation> --help` before constructing commands.
12
12
 
13
13
  ## Overview
14
14
 
@@ -38,21 +38,28 @@ Domain expertise for SMN (Simple Message Notification) and DMS (Distributed Mess
38
38
 
39
39
  ## DMS
40
40
 
41
- ### Service Types
41
+ > **DMS 是产品统一品牌**,涵盖 Kafka、RabbitMQ、RocketMQ 三种引擎。在 KooCLI 中,三种引擎被拆分为独立服务,**不存在 `hcloud DMS` 命令**。
42
42
 
43
- | Type | KooCLI Service |
44
- | -------- | -------------- |
45
- | Kafka | `hcloud DMS` |
46
- | RabbitMQ | `hcloud DMS` |
47
- | RocketMQ | `hcloud DMS` |
43
+ ### Engine → KooCLI Service Mapping
44
+
45
+ | Engine | KooCLI Service | Description |
46
+ | -------- | ----------------- | -------------------- |
47
+ | Kafka | `hcloud Kafka` | 高吞吐分布式消息队列 |
48
+ | RabbitMQ | `hcloud RabbitMQ` | AMQP 消息代理 |
49
+ | RocketMQ | `hcloud RocketMQ` | 低延迟金融级消息队列 |
48
50
 
49
51
  ### Common Workflows
50
52
 
51
- | Task | Operation |
52
- | --------------------- | -------------------------------------------------- |
53
- | List instances | `ListInstances --cli-region=<r> --project_id=<p>` |
54
- | Create Kafka instance | `CreateInstance --cli-region=<r> --project_id=<p>` |
55
- | List Kafka topics | `ListTopics --cli-region=<r> --project_id=<p>` |
53
+ | Task | Service | Operation |
54
+ | ------------------------ | ---------- | ------------------------------------------------------------------ |
55
+ | List Kafka instances | `Kafka` | `ListInstances --cli-region=<r> --project_id=<p>` |
56
+ | List RabbitMQ instances | `RabbitMQ` | `ListInstancesDetails --cli-region=<r> --project_id=<p>` |
57
+ | List RocketMQ instances | `RocketMQ` | `ListInstances --cli-region=<r> --project_id=<p>` |
58
+ | Create Kafka instance | `Kafka` | `CreatePostPaidKafkaInstance --cli-region=<r> --project_id=<p>` |
59
+ | Create RabbitMQ instance | `RabbitMQ` | `CreatePostPaidInstanceByEngine --cli-region=<r> --project_id=<p>` |
60
+ | Create RocketMQ instance | `RocketMQ` | `CreateInstanceByEngine --cli-region=<r> --project_id=<p>` |
61
+ | List Kafka topics | `Kafka` | `ListInstanceTopics --cli-region=<r> --project_id=<p>` |
62
+ | List RocketMQ topics | `RocketMQ` | `ListRocketInstanceTopics --cli-region=<r> --project_id=<p>` |
56
63
 
57
64
  Discover exact parameters with `--help` before executing any command.
58
65
 
@@ -29,8 +29,8 @@ Domain expertise for WAF and Anti-DDoS. Covers WAF policy/rules, AAD protection,
29
29
 
30
30
  | Task | Operation |
31
31
  | ------------------- | --------------------------------------------------------------- |
32
- | List WAF instances | `ListInstances --cli-region=<r> --project_id=<p>` |
33
- | List policies | `ListPolicies --cli-region=<r> --project_id=<p>` |
32
+ | List WAF instances | `ListCompositeHosts --cli-region=<r> --project_id=<p>` |
33
+ | List policies | `ListPolicy --cli-region=<r> --project_id=<p>` |
34
34
  | Create custom rule | `BatchCreateCustomRule --cli-region=<r> --project_id=<p>` |
35
35
  | Create IP blacklist | `BatchCreateWhiteblackipRule --cli-region=<r> --project_id=<p>` |
36
36
  | Create CC rule | `BatchCreateCcRule --cli-region=<r> --project_id=<p>` |
@@ -39,8 +39,11 @@ Domain expertise for WAF and Anti-DDoS. Covers WAF policy/rules, AAD protection,
39
39
  ### AAD
40
40
 
41
41
  ```bash
42
- hcloud AAD ListInstances --cli-region=<r> --project_id=<p>
43
- hcloud AAD CreateInstance --cli-region=<r> --project_id=<p>
42
+ # List AAD instances (requires --cli-region=cn-north-4 or ap-southeast-1)
43
+ hcloud AAD ListInstance --cli-region=<r> --project_id=<p>
44
+
45
+ # List DDoS attack events
46
+ hcloud AAD ListDDoSAttackEvent --cli-region=<r> --project_id=<p>
44
47
  ```
45
48
 
46
49
  ## Troubleshooting
@@ -125,13 +125,13 @@ hcloud <Service> <Op> --cli-debug=true
125
125
 
126
126
  Credentials are resolved in this order (highest priority first):
127
127
 
128
- | Priority | Source | Mechanism | Persistence |
129
- | -------- | ----------------------- | ---------------------------------------------------------- | ------------------------------- |
130
- | 1 | Runtime credentials | `huaweicloud_auth_init` tool | Memory (cleared on MCP restart) |
131
- | 2 | Environment variables | `HW_ACCESS_KEY` / `HW_SECRET_KEY` | MCP process lifetime |
132
- | 3 | CodeArts project config | `.codeartsdoer/mcp/mcp_settings.json` (project, then user) | File |
133
- | 4 | Global config file | `~/.config/huaweicloud/credentials.json` | Permanent |
134
- | 5 | KooCLI profile | `~/.hcloud/config.json` (KooCLI only) | Permanent |
128
+ | Priority | Source | Mechanism | Persistence |
129
+ | -------- | ------------------------ | ------------------------------------------------------------------------------------------------------ | ------------------------------- |
130
+ | 1 | Runtime credentials | `huaweicloud_auth_init` tool | Memory (cleared on MCP restart) |
131
+ | 2 | Environment variables | `HW_ACCESS_KEY` / `HW_SECRET_KEY` | MCP process lifetime |
132
+ | 3 | CodeArts / CodeArts Work | `.codeartsdoer/mcp/mcp_settings.json` (project → user) or `.codeartswork/mcp/mcp_settings.json` (user) | File |
133
+ | 4 | Global config file | `~/.config/huaweicloud/credentials.json` | Permanent |
134
+ | 5 | KooCLI profile | `~/.hcloud/config.json` (KooCLI only) | Permanent |
135
135
 
136
136
  When switching accounts within the same Agent session, use `huaweicloud_auth_init` to set runtime credentials. This overrides all other sources for the current MCP process.
137
137
 
@@ -836,7 +836,7 @@ fi`;
836
836
  try {
837
837
  const tunnelCheck = await execOneShot(
838
838
  workspaceId,
839
- 'devbridge ls 2>/dev/null | grep -q "Tunnel URL" && echo "ACTIVE" || echo "INACTIVE"',
839
+ 'devbridge list -j 2>/dev/null | grep -q \'"tunnelId"\' && echo "ACTIVE" || echo "INACTIVE"',
840
840
  username,
841
841
  10000,
842
842
  );
@@ -912,7 +912,7 @@ export async function deployCheck(
912
912
  `fi`,
913
913
  ``,
914
914
  `TOTAL=$((TOTAL+1))`,
915
- `if devbridge ls 2>/dev/null | grep -q "Tunnel URL"; then`,
915
+ `if devbridge list -j 2>/dev/null | grep -q '"tunnelId"'; then`,
916
916
  ` echo "devbridge_tunnel:PASS"`,
917
917
  ` PASS=$((PASS+1))`,
918
918
  `else`,
@@ -920,8 +920,9 @@ export async function deployCheck(
920
920
  `fi`,
921
921
  ``,
922
922
  `TOTAL=$((TOTAL+1))`,
923
- `TUNNEL_URL=$(devbridge ls 2>/dev/null | grep -oP "Tunnel URL: \\Khttps://[^ ]+")`,
924
- `if [ -n "$TUNNEL_URL" ]; then`,
923
+ `TUNNEL_ID=$(devbridge list -j 2>/dev/null | grep -oP '"tunnelId":\\s*"\\K[^"]+' | head -1)`,
924
+ `TUNNEL_URL="https://\${TUNNEL_ID}-${port}.cn-north-4-bridge.myhuaweicloud.com"`,
925
+ `if [ -n "$TUNNEL_ID" ] && [ -n "$TUNNEL_URL" ]; then`,
925
926
  ` HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$TUNNEL_URL" 2>/dev/null || echo "000")`,
926
927
  ` if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "304" ]; then`,
927
928
  ` echo "tunnel_url_accessible:PASS ($TUNNEL_URL -> $HTTP_CODE)"`,
@@ -2108,6 +2108,14 @@ function hermesPluginsDir() {
2108
2108
  return join(hermesHomeDir(), 'huaweicloud-plugins');
2109
2109
  }
2110
2110
 
2111
+ function hermesPythonPluginsDir() {
2112
+ return join(hermesHomeDir(), 'plugins');
2113
+ }
2114
+
2115
+ function hermesSafetyPluginDir() {
2116
+ return join(hermesPythonPluginsDir(), 'huaweicloud-safety');
2117
+ }
2118
+
2111
2119
  function hermesConfigFile() {
2112
2120
  return join(hermesHomeDir(), 'config.yaml');
2113
2121
  }
@@ -2186,17 +2194,26 @@ function removeHermesMcpConfigBlock() {
2186
2194
  console.log(' MCP config cleaned');
2187
2195
  }
2188
2196
 
2197
+ function hermesHookScript() {
2198
+ return join(hermesPluginsDir(), 'hooks', 'huaweicloud-safety.py').replace(/\\/g, '/');
2199
+ }
2200
+
2201
+ function hermesHookCommand() {
2202
+ const pythonBin = process.platform === 'win32' ? 'python' : 'python3';
2203
+ return `${pythonBin} ${hermesHookScript()}`;
2204
+ }
2205
+
2189
2206
  function ensureHermesHooksConfig() {
2190
2207
  const configPath = hermesConfigFile();
2191
- const pluginDest = hermesPluginsDir();
2192
- const hookScript = join(pluginDest, 'hooks', 'huaweicloud-safety.py').replace(/\\/g, '/');
2208
+ const hookCommand = hermesHookCommand();
2193
2209
 
2194
2210
  const blockLines = [
2195
2211
  'hooks:',
2196
2212
  ' pre_tool_call:',
2197
2213
  ' - matcher: "terminal"',
2198
- ` command: "python3 ${hookScript}"`,
2214
+ ` command: "${hookCommand}"`,
2199
2215
  ' timeout: 5',
2216
+ ' fail_closed: true',
2200
2217
  ];
2201
2218
  const block = blockLines.join('\n');
2202
2219
 
@@ -2205,10 +2222,21 @@ function ensureHermesHooksConfig() {
2205
2222
  try {
2206
2223
  existing = readFileSync(configPath, 'utf8');
2207
2224
  } catch {}
2208
- if (existing.includes('hooks:') && existing.includes('huaweicloud-safety.py')) {
2225
+ if (
2226
+ existing.includes('hooks:') &&
2227
+ existing.includes(`command: "${hookCommand}"`) &&
2228
+ existing.includes('fail_closed: true')
2229
+ ) {
2209
2230
  console.log(` Hooks config unchanged: ${configPath}`);
2210
2231
  return false;
2211
2232
  }
2233
+ removeHermesHooksConfigBlock();
2234
+ existing = '';
2235
+ if (existsSync(configPath)) {
2236
+ try {
2237
+ existing = readFileSync(configPath, 'utf8');
2238
+ } catch {}
2239
+ }
2212
2240
  }
2213
2241
  mkdirSync(dirname(configPath), { recursive: true });
2214
2242
  const newContent = existing ? `${existing.trimEnd()}\n\n${block}\n` : `${block}\n`;
@@ -2217,6 +2245,154 @@ function ensureHermesHooksConfig() {
2217
2245
  return true;
2218
2246
  }
2219
2247
 
2248
+ function hermesHookScriptMtime() {
2249
+ const scriptPath = hermesHookScript();
2250
+ try {
2251
+ return statSync(scriptPath).mtime.toISOString();
2252
+ } catch {
2253
+ return null;
2254
+ }
2255
+ }
2256
+
2257
+ function hermesAllowlistPath() {
2258
+ return join(hermesHomeDir(), 'shell-hooks-allowlist.json');
2259
+ }
2260
+
2261
+ function ensureHermesHookAllowlist() {
2262
+ const hookCommand = hermesHookCommand();
2263
+ const allowlistPath = hermesAllowlistPath();
2264
+
2265
+ let data = { approvals: [] };
2266
+ if (existsSync(allowlistPath)) {
2267
+ try {
2268
+ const parsed = JSON.parse(readFileSync(allowlistPath, 'utf8'));
2269
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
2270
+ data = parsed;
2271
+ }
2272
+ } catch {}
2273
+ }
2274
+ const approvals = Array.isArray(data.approvals) ? data.approvals : [];
2275
+ const already = approvals.some(
2276
+ (a) => a && typeof a === 'object' && a.event === 'pre_tool_call' && a.command === hookCommand,
2277
+ );
2278
+ if (already) {
2279
+ console.log(` Hook allowlist unchanged: ${allowlistPath}`);
2280
+ return false;
2281
+ }
2282
+
2283
+ const entry = {
2284
+ event: 'pre_tool_call',
2285
+ command: hookCommand,
2286
+ approved_at: new Date().toISOString(),
2287
+ script_mtime_at_approval: hermesHookScriptMtime(),
2288
+ };
2289
+ data.approvals = [...approvals, entry];
2290
+ mkdirSync(dirname(allowlistPath), { recursive: true });
2291
+ writeFileSync(allowlistPath, JSON.stringify(data, null, 2));
2292
+ console.log(` Hook allowlist updated: ${allowlistPath}`);
2293
+ return true;
2294
+ }
2295
+
2296
+ function hermesHookAllowlisted() {
2297
+ const allowlistPath = hermesAllowlistPath();
2298
+ if (!existsSync(allowlistPath)) return false;
2299
+ try {
2300
+ const data = JSON.parse(readFileSync(allowlistPath, 'utf8'));
2301
+ const cmd = hermesHookCommand();
2302
+ return (
2303
+ Array.isArray(data?.approvals) &&
2304
+ data.approvals.some((a) => a && typeof a === 'object' && a.event === 'pre_tool_call' && a.command === cmd)
2305
+ );
2306
+ } catch {
2307
+ return false;
2308
+ }
2309
+ }
2310
+
2311
+ function hermesHookPluginInit() {
2312
+ const script = hermesHookScript();
2313
+ return [
2314
+ 'import importlib.util',
2315
+ '',
2316
+ `SAFETY_SCRIPT = ${JSON.stringify(script)}`,
2317
+ '',
2318
+ '',
2319
+ 'def _load_safety():',
2320
+ ' spec = importlib.util.spec_from_file_location("huaweicloud_safety_hook", SAFETY_SCRIPT)',
2321
+ ' module = importlib.util.module_from_spec(spec)',
2322
+ ' spec.loader.exec_module(module)',
2323
+ ' return module',
2324
+ '',
2325
+ '',
2326
+ '_safety = _load_safety()',
2327
+ '',
2328
+ '',
2329
+ 'def register(ctx):',
2330
+ ' ctx.register_hook("pre_tool_call", _check)',
2331
+ '',
2332
+ '',
2333
+ 'def _check(tool_name=None, args=None, **kwargs):',
2334
+ ' if tool_name != "terminal":',
2335
+ ' return None',
2336
+ ' if not (isinstance(args, dict) and "command" in args):',
2337
+ ' return None',
2338
+ ' reason = _safety.evaluate(tool_name, args)',
2339
+ ' if reason:',
2340
+ ' return {"action": "block", "message": _safety.DENY_PREFIX + reason}',
2341
+ ' return None',
2342
+ '',
2343
+ ].join('\n');
2344
+ }
2345
+
2346
+ function ensureHermesHookPlugin() {
2347
+ const pluginDir = hermesSafetyPluginDir();
2348
+ const manifestPath = join(pluginDir, 'plugin.yaml');
2349
+ const initPath = join(pluginDir, '__init__.py');
2350
+
2351
+ const manifest = [
2352
+ 'name: huaweicloud-safety',
2353
+ `version: "${pkgVersion}"`,
2354
+ 'description: Huawei Cloud safety pre_tool_call hook',
2355
+ 'author: HuaweiCloud Mate',
2356
+ 'provides_hooks:',
2357
+ ' - pre_tool_call',
2358
+ '',
2359
+ ].join('\n');
2360
+ const init = hermesHookPluginInit();
2361
+
2362
+ const manifestCurrent = existsSync(manifestPath) ? readFileSync(manifestPath, 'utf8') : '';
2363
+ const initCurrent = existsSync(initPath) ? readFileSync(initPath, 'utf8') : '';
2364
+ if (manifestCurrent === manifest && initCurrent === init) {
2365
+ console.log(` Hook plugin unchanged: ${pluginDir}`);
2366
+ return false;
2367
+ }
2368
+
2369
+ mkdirSync(pluginDir, { recursive: true });
2370
+ writeFileSync(manifestPath, manifest);
2371
+ writeFileSync(initPath, init);
2372
+ console.log(` Hook plugin installed: ${pluginDir}`);
2373
+ return true;
2374
+ }
2375
+
2376
+ function removeHermesHookPlugin() {
2377
+ const pluginDir = hermesSafetyPluginDir();
2378
+ if (!existsSync(pluginDir)) return false;
2379
+ rmSync(pluginDir, { recursive: true, force: true });
2380
+ console.log(` Removed hook plugin: ${pluginDir}`);
2381
+ const parent = hermesPythonPluginsDir();
2382
+ try {
2383
+ if (existsSync(parent) && readdirSync(parent).length === 0) {
2384
+ rmSync(parent, { recursive: true, force: true });
2385
+ }
2386
+ } catch {}
2387
+ return true;
2388
+ }
2389
+
2390
+ function hermesHookPluginInstalled() {
2391
+ return (
2392
+ existsSync(join(hermesSafetyPluginDir(), 'plugin.yaml')) && existsSync(join(hermesSafetyPluginDir(), '__init__.py'))
2393
+ );
2394
+ }
2395
+
2220
2396
  function removeHermesHooksConfigBlock() {
2221
2397
  const configPath = hermesConfigFile();
2222
2398
  if (!existsSync(configPath)) return;
@@ -2299,6 +2475,8 @@ async function installHermes() {
2299
2475
 
2300
2476
  if (!skipMcp) ensureHermesMcpConfig();
2301
2477
  ensureHermesHooksConfig();
2478
+ ensureHermesHookAllowlist();
2479
+ ensureHermesHookPlugin();
2302
2480
  if (!skipMcp) installRuntimeDeps(pluginDest);
2303
2481
  if (!skipMcp) ensureHermesMcpSdk();
2304
2482
  }
@@ -2321,6 +2499,8 @@ async function updateHermes() {
2321
2499
  console.log(` Safety Hooks updated -> ${join(pluginDest, 'hooks')}`);
2322
2500
  ensureHermesMcpConfig();
2323
2501
  ensureHermesHooksConfig();
2502
+ ensureHermesHookAllowlist();
2503
+ ensureHermesHookPlugin();
2324
2504
  ensureHermesMcpSdk();
2325
2505
  mkdirSync(pluginDest, { recursive: true });
2326
2506
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
@@ -2335,6 +2515,9 @@ function uninstallHermes() {
2335
2515
  removeHermesHooksConfigBlock();
2336
2516
  console.log(' Hooks config removed');
2337
2517
 
2518
+ // 1b. Remove the Python safety hook plugin (v0.9.0+ enforcement path)
2519
+ removeHermesHookPlugin();
2520
+
2338
2521
  // 2. Clean shell-hooks-allowlist.json (remove approved hook references)
2339
2522
  const hermesHome = hermesHomeDir();
2340
2523
  const allowlistPath = join(hermesHome, 'shell-hooks-allowlist.json');
@@ -2342,9 +2525,10 @@ function uninstallHermes() {
2342
2525
  try {
2343
2526
  const allowlist = JSON.parse(readFileSync(allowlistPath, 'utf8'));
2344
2527
  const before = (allowlist.approvals || []).length;
2345
- allowlist.approvals = (allowlist.approvals || []).filter((a) =>
2346
- typeof a === 'string' ? !a.includes('huaweicloud-safety.py') : true,
2347
- );
2528
+ allowlist.approvals = (allowlist.approvals || []).filter((a) => {
2529
+ const cmd = typeof a === 'string' ? a : a?.command;
2530
+ return !(typeof cmd === 'string' && cmd.includes('huaweicloud-safety.py'));
2531
+ });
2348
2532
  if (allowlist.approvals.length < before) {
2349
2533
  writeFileSync(allowlistPath, JSON.stringify(allowlist, null, 2));
2350
2534
  console.log(` Removed ${before - allowlist.approvals.length} hook approvals from allowlist`);
@@ -2394,6 +2578,12 @@ function hermesStatus() {
2394
2578
  console.log(
2395
2579
  ` Safety Hooks: ${existsSync(join(pluginDir, 'hooks', 'huaweicloud-safety.py')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2396
2580
  );
2581
+ console.log(
2582
+ ` Hook allowlist: ${hermesHookAllowlisted() ? '\x1b[32mApproved\x1b[0m' : '\x1b[31mNot allowlisted\x1b[0m'}`,
2583
+ );
2584
+ console.log(
2585
+ ` Hook plugin: ${hermesHookPluginInstalled() ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2586
+ );
2397
2587
  console.log(` MCP Python SDK: ${hermesMcpSdkOk() ? '\x1b[32mReady\x1b[0m' : '\x1b[31mMissing\x1b[0m'}`);
2398
2588
  let skillCount = 0;
2399
2589
  if (existsSync(skillsDir)) {
@@ -2867,17 +3057,19 @@ async function cmdDoctor() {
2867
3057
  ? 'Codex Desktop'
2868
3058
  : existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs'))
2869
3059
  ? 'CodeArts'
2870
- : existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'))
2871
- ? 'WorkBuddy'
2872
- : existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs'))
2873
- ? 'DSH'
2874
- : existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'))
2875
- ? 'OfficeAce'
2876
- : existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
2877
- ? 'Hermes Agent'
2878
- : existsSync(join(atomcodePluginDir, 'src', 'mcp-server.mjs'))
2879
- ? 'AtomCode'
2880
- : '';
3060
+ : existsSync(join(codeartsWorkPluginDir, 'src', 'mcp-server.mjs'))
3061
+ ? 'CodeArts Work'
3062
+ : existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'))
3063
+ ? 'WorkBuddy'
3064
+ : existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs'))
3065
+ ? 'DSH'
3066
+ : existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'))
3067
+ ? 'OfficeAce'
3068
+ : existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
3069
+ ? 'Hermes Agent'
3070
+ : existsSync(join(atomcodePluginDir, 'src', 'mcp-server.mjs'))
3071
+ ? 'AtomCode'
3072
+ : '';
2881
3073
  check('MCP server installed', mcpOk, 'Run: npx huaweicloud-devkit install');
2882
3074
 
2883
3075
  if (mcpOk) {
@@ -2888,6 +3080,7 @@ async function cmdDoctor() {
2888
3080
  opencodePluginDir,
2889
3081
  codexPluginDir,
2890
3082
  codeartsPluginDir,
3083
+ codeartsWorkPluginDir,
2891
3084
  workbuddyPluginDir,
2892
3085
  dshPluginDir,
2893
3086
  officeacePluginDir,
@@ -2941,6 +3134,16 @@ async function cmdDoctor() {
2941
3134
  }
2942
3135
  } catch {}
2943
3136
  }
3137
+ const codeartsWorkCfg = codeartsWorkMcpSettingsFile();
3138
+ if (!mcpConfigured && existsSync(codeartsWorkCfg)) {
3139
+ try {
3140
+ const cfg = JSON.parse(readFileSync(codeartsWorkCfg, 'utf8'));
3141
+ if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
3142
+ mcpConfigured = true;
3143
+ mcpCfgTarget = 'CodeArts Work';
3144
+ }
3145
+ } catch {}
3146
+ }
2944
3147
  const workbuddyCfg = workbuddyMcpConfigFile();
2945
3148
  if (!mcpConfigured && existsSync(workbuddyCfg)) {
2946
3149
  try {