huaweicloud-devkit 1.1.2-next.6 → 1.1.2-next.8

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/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.2-next.6",
4
+ "version": "1.1.2-next.8",
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",
@@ -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.2-next.6",
20
+ "version": "1.1.2-next.8",
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.2-next.6",
23
+ "version": "1.1.2-next.8",
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.2-next.6",
20
+ "version": "1.1.2-next.8",
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.2-next.6",
3
+ "version": "1.1.2-next.8",
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.2-next.6",
3
+ "version": "1.1.2-next.8",
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",
@@ -2,7 +2,7 @@
2
2
  "name": "huaweicloud-devkit",
3
3
  "id": "huaweicloud-devkit",
4
4
  "displayName": "HuaweiCloud DevKit",
5
- "version": "1.1.2-next.6",
5
+ "version": "1.1.2-next.8",
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.",
@@ -16,24 +16,24 @@ Domain expertise for billing queries (BSS). Covers cost tracking, bill details,
16
16
 
17
17
  ## Critical Warnings
18
18
 
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
- | Global service needs domain-id | BSS is a global service: AK/SK mode requires `--cli-domain-id`. If unknown, run `hcloud STS GetCallerIdentity --cli-region=<region>` and use `account_id` (see `huaweicloud-cli-and-auth`). |
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
+ | `缺少必填参数 cli-domain-id` / APIGW.0301 | Invalid credentials — KooCLI can't resolve the account-id | Re-run `npx huaweicloud-devkit auth init` |
26
26
 
27
27
  ## Common Workflows
28
28
 
29
- | Task | Operation |
30
- | -------------------- | --------------------------------------------------------------------------------- |
31
- | List costs | `ListCosts --cli-region=cn-north-1 --project_id=<p>` |
32
- | List customer bills | `ListCustomerBillsFeeRecords --cli-region=cn-north-1 --project_id=<p>` |
33
- | List resource usage | `ListResourceUsage --cli-region=cn-north-1 --project_id=<p>` |
34
- | List sub-customers | `ListConsumeSubCustomers --cli-region=cn-north-1 --project_id=<p>` |
35
- | Show account balance | `ShowCustomerAccountBalances --cli-region=cn-north-1 --cli-domain-id=<domain_id>` |
36
- | List conversions | `ListConversions --cli-region=cn-north-1 --project_id=<p>` |
29
+ | Task | Operation |
30
+ | -------------------- | ---------------------------------------------------------------------- |
31
+ | List costs | `ListCosts --cli-region=cn-north-1 --project_id=<p>` |
32
+ | List customer bills | `ListCustomerBillsFeeRecords --cli-region=cn-north-1 --project_id=<p>` |
33
+ | List resource usage | `ListResourceUsage --cli-region=cn-north-1 --project_id=<p>` |
34
+ | List sub-customers | `ListConsumeSubCustomers --cli-region=cn-north-1 --project_id=<p>` |
35
+ | Show account balance | `ShowCustomerAccountBalances --cli-region=cn-north-1` |
36
+ | List conversions | `ListConversions --cli-region=cn-north-1 --project_id=<p>` |
37
37
 
38
38
  Discover exact parameters with `--help` before executing any command. All BSS operations are read-only.
39
39
 
@@ -69,6 +69,6 @@ hcloud IAM AssociateAgencyWithDomainPermission --agency_id=<id> --domain_id=<id>
69
69
  hcloud FunctionGraph CreateFunction --func_vpc.vpc_id=<vpc> --func_vpc.subnet_id=<subnet> --app_xrole=<name> ...
70
70
  ```
71
71
 
72
- > IAM is a global service in KooCLI. If `CreateAgency` / `AssociateAgencyWithDomainPermission` fail with `缺少必填参数 cli-domain-id`, prefer a concrete `--cli-region` (e.g. `cn-north-4`), or discover the domain-id via `hcloud STS GetCallerIdentity --cli-region=<region>` (see `huaweicloud-cli-and-auth`).
72
+ > The `缺少必填参数 cli-domain-id` error means invalid credentials (APIGW.0301) — re-run `auth init`. `CreateAgency` / `AssociateAgencyWithDomainPermission` need the account-id for their `--agency.domain_id` / `--domain_id` params; get it via `hcloud STS GetCallerIdentity --cli-region=<region>`.
73
73
 
74
74
  | QuotaExceeded | Max 10 functions per project per region |
@@ -86,7 +86,7 @@ Execution: show the to-be-deleted list for final confirmation → delete in **re
86
86
  - `huaweicloud_run_readonly_command` → discovery (flavors, images, zones, prices)
87
87
  - `huaweicloud_list_regions` / `huaweicloud_get_regional_availability` → region intent
88
88
  - `huaweicloud_hook_check_deploy_plan` → risk-check the plan before approval
89
- - Balance and pricing: `huawei-billing` skill. Balance operation (BSS is pinned to cn-north-1 in KooCLI): `hcloud BSS ShowCustomerAccountBalances --cli-region=cn-north-1 --cli-domain-id=<domain_id>`. Global services (BSS/IAM) require `--cli-domain-id` per call or in the profile. If unknown, discover it with `hcloud STS GetCallerIdentity --cli-region=<region>` (`account_id` = domain-id); use `tenant_id` from a resource response only as a fallback
89
+ - Balance and pricing: `huawei-billing` skill. Balance operation (BSS is pinned to cn-north-1 in KooCLI): `hcloud BSS ShowCustomerAccountBalances --cli-region=cn-north-1` (domain-id auto-resolves from the AK/SK). If an IAM business param genuinely needs the account-id, run `hcloud STS GetCallerIdentity --cli-region=<region>`
90
90
 
91
91
  ## Cross-Skill References
92
92
 
@@ -100,8 +100,8 @@ hcloud BSS ListOnDemandResourceRatings --project_id=<pid> \
100
100
  - **Balance query may be permission-blocked**: `CBC.0151` = IAM user lacks `billing:balance:view` → degrade to "user self-checks balance" and continue (do not block; `Ecs.7000` at order time is recoverable). domain_id 可从报错信息 `iam::<domain_id>:user:<name>` 提取
101
101
 
102
102
  - Account balance: `hcloud BSS ShowCustomerAccountBalances --cli-region=cn-north-1 --cli-domain-id=<domain_id>`
103
- - Global services (BSS/IAM/CDN) demand `--cli-domain-id` per call (or in the profile) under AK/SK auth
104
- - If the domain_id is unknown: discover it via `hcloud STS GetCallerIdentity --cli-region=<region>` (`account_id` = domain-id); fall back to `tenant_id` from a resource response or the console
103
+ - domain-id auto-resolves from the AK/SK; `缺少必填参数 cli-domain-id` signals invalid credentials (APIGW.0301), not a missing domain-id
104
+ - If an IAM business param needs the account-id (`--domain_id` etc.), run `hcloud STS GetCallerIdentity --cli-region=<region>` (`account_id` = domain-id); `tenant_id` from a resource response is a fallback
105
105
  - Response shape: `account_balances[].amount` (cash account type=1, voucher account type=5), `debt_amount`, `currency`
106
106
  - Cost gate rule: no deployment without a per-resource cost estimate AND a balance check. Verified failure mode when skipped: ECS creation dies with `Ecs.7000 Insufficient account balance` at order submission
107
107
  - **Quota pre-check (EIP and friends)**: quota errors surface only at RUN stage (`EIP.7905 Quota exceeded` AFTER plan+approval), wasting an approval cycle. Before provisioning, pre-check counts with `hcloud EIP ListPublicips` (⚠️ beware pagination truncation: `--limit=50` on a 59-EIP account showed "50" - the first "50/50 full" verdict was wrong; count precisely, e.g. loop pages or use the console quota page). If quota is exhausted, surface it at the cost gate instead of after approval. Note: after releasing an EIP, quota counting may lag several minutes - `EIP.7905` can persist briefly even under the limit
@@ -111,4 +111,4 @@ hcloud BSS ListOnDemandResourceRatings --project_id=<pid> \
111
111
  - Symptom of a broken profile: every hcloud API returns `APIGW.0301 Incorrect IAM authentication information` while OBS/obsutil keeps working - caused by a stale `securityToken` in the profile
112
112
  - Fix: `hcloud configure delete --cli-profile=default`, then re-run `npx huaweicloud-devkit auth init` with permanent AK/SK only (no token). `configure set` refuses empty values, so the token cannot be cleared in place
113
113
  - project_id is auto-discovered once signing works - no need to configure it manually
114
- - VPC/OBS create responses expose `tenant_id` (= domain_id) - a fallback when `STS GetCallerIdentity` discovery is unavailable
114
+ - VPC/OBS create responses expose `tenant_id` (= domain_id) - a fallback source for the account-id
@@ -16,14 +16,14 @@ Domain expertise for Huawei Cloud Identity and Access Management (IAM). Covers u
16
16
 
17
17
  ## Critical Warnings
18
18
 
19
- | Trap | Why |
20
- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
- | NEVER create IAM users for humans | Use OneAccess (IAM Identity Center) or federated SSO |
22
- | NEVER create long-term AK/SK | Use temporary STS tokens via agencies |
23
- | Wildcard policies dangerous | Effect:Allow + Resource:* = full access. Always scope resources |
24
- | Agency trust is powerful | Agencies let services assume roles. Always add conditions |
25
- | Root account must have MFA | Root AK/SK is all-powerful. Enable MFA immediately |
26
- | IAM is a global service in KooCLI | `--cli-region=cn-north-1` routes to the global endpoint and then AK/SK mode needs `--cli-domain-id`. Prefer a concrete region (e.g. `cn-north-4`); if you hit `缺少必填参数 cli-domain-id`, discover it via `hcloud STS GetCallerIdentity --cli-region=<region>` |
19
+ | Trap | Why |
20
+ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
21
+ | NEVER create IAM users for humans | Use OneAccess (IAM Identity Center) or federated SSO |
22
+ | NEVER create long-term AK/SK | Use temporary STS tokens via agencies |
23
+ | Wildcard policies dangerous | Effect:Allow + Resource:* = full access. Always scope resources |
24
+ | Agency trust is powerful | Agencies let services assume roles. Always add conditions |
25
+ | Root account must have MFA | Root AK/SK is all-powerful. Enable MFA immediately |
26
+ | `缺少必填参数 cli-domain-id` = invalid creds | KooCLI's account-id lookup failed (APIGW.0301). Re-run `auth init`. Domain-level params (`--domain_id`, `--agency.domain_id`) are separate — get the account-id via `hcloud STS GetCallerIdentity --cli-region=<region>` when needed |
27
27
 
28
28
  ## Policy Structure
29
29
 
@@ -112,7 +112,13 @@ Setup is a **plugin-side preflight** — the developer should be asked a questio
112
112
  sudo nginx -s reload 2>/dev/null || true
113
113
  ```
114
114
 
115
- 7. **Inject credentials** (optional): `huaweicloud_sandbox_credentials` — enables cloud API access from sandbox. After injection, credentials are also written to `/tmp/hw_creds.sh` for shell access:
115
+ 7. **Inject credentials** (optional): `huaweicloud_sandbox_credentials` — enables cloud API access from sandbox. Pass `session_id` **or** `dev_stage_id` (at least one; both come from `sandbox_connect`, and `dev_stage_id` may be omitted to reuse the most recent connect). The tool validates the AK/SK against IAM first — an invalid SK is rejected here instead of failing later with `APIGW.0301` during exec. Example:
116
+
117
+ ```json
118
+ { "dev_stage_id": "<dev_stage_id from connect>" }
119
+ ```
120
+
121
+ After injection, credentials are also written to `/tmp/hw_creds.sh` for shell access (includes `HW_PROJECT_ID` when resolvable):
116
122
 
117
123
  ```bash
118
124
  # Source credentials in any sandbox shell before using hcloud/devbridge:
@@ -139,19 +139,18 @@ When switching accounts within the same Agent session, use `huaweicloud_auth_ini
139
139
 
140
140
  ## Global Services & domain-id
141
141
 
142
- Global services (BSS, and IAM routed via the `cn-north-1` global endpoint) require `--cli-domain-id` under AK/SK auth. When it is missing, KooCLI fails with `[USE_ERROR]...缺少必填参数cli-domain-id`.
142
+ KooCLI resolves the account/domain-id automatically from a valid AK/SK, so global services (BSS, IAM, CDN) normally do **not** need an explicit `--cli-domain-id`.
143
143
 
144
- Do **not** ask the user to run `hcloud configure set --cli-domain-id=...`. Discover the domain-id yourself:
144
+ If a command fails with `[USE_ERROR]...缺少必填参数 cli-domain-id`, the real cause is almost always **invalid or expired credentials** (`APIGW.0301 Incorrect IAM authentication information`): KooCLI's internal account-id lookup failed and it misreports that as a missing domain-id. Fix the credentials instead (`npx huaweicloud-devkit auth init`).
145
+
146
+ Some IAM operations take a genuine business parameter for the account-id (e.g. `--domain_id`, `--agency.domain_id`, `--agency.trust_domain_id`, `--agency_urn`). Discover those with `--help`; get the account-id value with:
145
147
 
146
148
  ```bash
147
- hcloud STS GetCallerIdentity --cli-region=<region>
148
- # → account_id IS the domain-id; retry the original call with --cli-domain-id=<account_id>
149
+ hcloud STS GetCallerIdentity --cli-region=<region> # account_id = the account/domain-id
149
150
  ```
150
151
 
151
- - `<region>` is the profile's current region. **STS is not deployed in `cn-north-1`** — if the profile region is `cn-north-1`, use another region (e.g. `cn-north-4`).
152
+ - `<region>` is the profile's current region. STS is not deployed in `cn-north-1` — use another region if needed.
152
153
  - Temporary credentials (AK/SK + security token) work too: add `--cli-security-token=<token>`.
153
- - Prefer `STS GetCallerIdentity` over `IAM KeystoneListAuthDomains` — the latter only works with permanent credentials and outside `cn-north-1`; it fails under temporary credentials.
154
- - `cli-domain-id` is a KooCLI CLI requirement, not a Huawei Cloud API requirement: the raw `GET /v3/auth/domains` can be signed with just AK/SK.
155
154
 
156
155
  ## Preferred Toolkit Tools
157
156
 
@@ -12,7 +12,7 @@ Huawei Cloud comprises 200+ services. This skill is the entry point for ANY acti
12
12
 
13
13
  ## 会话启动
14
14
 
15
- 首次操作前必须先调用 `huaweicloud_check_update` 检查插件版本:
15
+ 首次操作前先调用 `huaweicloud_check_update` 检查插件版本;若未先行检查,插件会在使用中收到服务端升级提示。
16
16
 
17
17
  1. 如果 `updateAvailable == true` 且 `dismissed == false`,先询问用户是否升级
18
18
  2. 用户同意 → 调用 `huaweicloud_upgrade`,完成后提示重启会话
@@ -50,19 +50,19 @@ Use evidence before fixes. Do not guess service behavior when request IDs, regio
50
50
 
51
51
  ## Common Error Codes
52
52
 
53
- | Error | Likely Cause | Fix |
54
- | --------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
55
- | AuthFailure / 401 | AK/SK invalid or expired | Regenerate AK/SK, re-run `npx huaweicloud-devkit auth init` |
56
- | AccessDenied / 403 | IAM permission missing | Check `huawei-iam` skill, add required policy action |
57
- | NoSuchKey / 404 | Resource not found | Verify resource ID, region, and project_id |
58
- | QuotaExceeded | Account limit reached | Request quota increase in console |
59
- | [USE_ERROR] 不正确的参数 | Wrong param name | Run `--help`, check `--param=value` format and nested prefix |
60
- | `[USE_ERROR]缺少必填参数 cli-domain-id` | Global service (BSS / IAM global endpoint) under AK/SK | Run `hcloud STS GetCallerIdentity --cli-region=<region>` → `account_id`, retry with `--cli-domain-id=<account_id>` (see `huaweicloud-cli-and-auth`) |
61
- | Ecs.0005 | Flavor-image mismatch | Check image `__support_*` against flavor virtualization type |
62
- | FSS.0400 | FunctionGraph latest version error | Strip `:latest` from function URN |
63
- | FSS.1417 | DEDICATEDGATEWAY missing params | Add instance_id, group_id, protocol, env_name, env_id |
64
- | APIC.7201 | Missing security_group_id | Add `--security_group_id` param for APIG CreateInstanceV2 |
65
- | [NETWORK_ERROR] | Transient network failure | Retry with `maxRetries` param or wait and retry |
53
+ | Error | Likely Cause | Fix |
54
+ | --------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
55
+ | AuthFailure / 401 | AK/SK invalid or expired | Regenerate AK/SK, re-run `npx huaweicloud-devkit auth init` |
56
+ | AccessDenied / 403 | IAM permission missing | Check `huawei-iam` skill, add required policy action |
57
+ | NoSuchKey / 404 | Resource not found | Verify resource ID, region, and project_id |
58
+ | QuotaExceeded | Account limit reached | Request quota increase in console |
59
+ | [USE_ERROR] 不正确的参数 | Wrong param name | Run `--help`, check `--param=value` format and nested prefix |
60
+ | `[USE_ERROR]缺少必填参数 cli-domain-id` | Invalid/expired credentials — KooCLI's account-id lookup failed (APIGW.0301) | Re-run `npx huaweicloud-devkit auth init`; KooCLI auto-resolves domain-id once creds are valid |
61
+ | Ecs.0005 | Flavor-image mismatch | Check image `__support_*` against flavor virtualization type |
62
+ | FSS.0400 | FunctionGraph latest version error | Strip `:latest` from function URN |
63
+ | FSS.1417 | DEDICATEDGATEWAY missing params | Add instance_id, group_id, protocol, env_name, env_id |
64
+ | APIC.7201 | Missing security_group_id | Add `--security_group_id` param for APIG CreateInstanceV2 |
65
+ | [NETWORK_ERROR] | Transient network failure | Retry with `maxRetries` param or wait and retry |
66
66
 
67
67
  ## KooCLI Error Types
68
68
 
@@ -1,6 +1,7 @@
1
1
  import { getCredentials } from './hwlink-api.mjs';
2
2
  import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
3
3
  import { cacheUserHash } from '../telemetry/telemetry.mjs';
4
+ import { readInstalledVersion } from '../update-check.mjs';
4
5
 
5
6
  function getHdkitBaseUrl() {
6
7
  return process.env.HDKITSERVICE_ENDPOINT || 'https://devkit.huaweicloud.com/rest/developer/server/hdkitservice/';
@@ -19,6 +20,7 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
19
20
  'Content-Type': 'application/json',
20
21
  'X-HW-AK': ak,
21
22
  'X-HW-SK': sk,
23
+ 'X-HW-Client-Version': readInstalledVersion() || '0.0.0',
22
24
  };
23
25
  if (securitytoken) {
24
26
  headers['X-HW-Security-Token'] = securitytoken;
@@ -10,7 +10,7 @@ import {
10
10
  } from 'node:fs';
11
11
  import { join, dirname, resolve } from 'node:path';
12
12
  import { fileURLToPath } from 'node:url';
13
- import { homedir, platform } from 'node:os';
13
+ import { homedir, platform, tmpdir } from 'node:os';
14
14
  import { createInterface } from 'node:readline';
15
15
  import { spawnSync } from 'node:child_process';
16
16
  import { randomUUID } from 'node:crypto';
@@ -3404,72 +3404,89 @@ async function promptGlobalCleanup() {
3404
3404
  }
3405
3405
  }
3406
3406
 
3407
+ function openclawStatus() {
3408
+ const cdPluginDir = openclawPluginsDir();
3409
+ const cdSkillsDir = openclawSkillsDir();
3410
+ console.log(
3411
+ ` MCP Server: ${existsSync(join(cdPluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
3412
+ );
3413
+ console.log(
3414
+ ` Safety Policy: ${existsSync(join(cdPluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
3415
+ );
3416
+ let cdSkillCount = 0;
3417
+ if (existsSync(cdSkillsDir)) {
3418
+ cdSkillCount = readdirSync(cdSkillsDir, { withFileTypes: true }).filter(
3419
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
3420
+ ).length;
3421
+ }
3422
+ console.log(
3423
+ ` Skills: ${cdSkillCount > 0 ? `\x1b[32m${cdSkillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
3424
+ );
3425
+ }
3426
+
3427
+ function codexSectionStatus() {
3428
+ if (!hasCodexCLI()) {
3429
+ console.log(' \x1b[33mCodex CLI not found.\x1b[0m');
3430
+ } else {
3431
+ console.log(` Plugin: ${codexStatus() ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
3432
+ }
3433
+ }
3434
+
3407
3435
  async function cmdStatus() {
3408
- const target = parseTarget();
3436
+ const scope = parseTarget();
3409
3437
  console.log(BANNER);
3410
3438
  console.log(`HuaweiCloud DevKit Status\n`);
3411
3439
 
3412
- if (target === 'opencode' || target === 'all') {
3413
- console.log('[OpenCode]');
3414
- opencodeStatus();
3415
- }
3416
- if (target === 'codearts' || target === 'all') {
3417
- console.log('\n[CodeArts]');
3418
- codeartsStatus();
3419
- }
3420
- if (target === 'codearts-work' || target === 'all') {
3421
- console.log('\n[CodeArts Work]');
3422
- codeartsWorkStatus();
3423
- }
3424
- if (target === 'workbuddy' || target === 'all') {
3425
- console.log('\n[WorkBuddy]');
3426
- workbuddyStatus();
3427
- }
3428
- if (target === 'dsh' || target === 'all') {
3429
- console.log('\n[DSH]');
3430
- dshStatus();
3431
- }
3432
- if (target === 'officeace' || target === 'all') {
3433
- console.log('\n[OfficeAce]');
3434
- officeaceStatus();
3435
- }
3436
- if (target === 'hermes' || target === 'all') {
3437
- console.log('\n[Hermes Agent]');
3438
- hermesStatus();
3439
- }
3440
- if (target === 'openclaw' || target === 'all') {
3441
- console.log('\n[OpenClaw]');
3442
- const cdPluginDir = openclawPluginsDir();
3443
- const cdSkillsDir = openclawSkillsDir();
3444
- console.log(
3445
- ` MCP Server: ${existsSync(join(cdPluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
3446
- );
3447
- console.log(
3448
- ` Safety Policy: ${existsSync(join(cdPluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
3449
- );
3450
- let cdSkillCount = 0;
3451
- if (existsSync(cdSkillsDir)) {
3452
- cdSkillCount = readdirSync(cdSkillsDir, { withFileTypes: true }).filter(
3453
- (d) => d.isDirectory() && d.name.startsWith('huawei'),
3454
- ).length;
3455
- }
3456
- console.log(
3457
- ` Skills: ${cdSkillCount > 0 ? `\x1b[32m${cdSkillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
3458
- );
3459
- }
3460
- if (target === 'atomcode' || target === 'all') {
3461
- console.log('\n[AtomCode]');
3462
- atomcodeStatus();
3463
- }
3464
- if (target === 'codex' || target === 'all') {
3465
- console.log('\n[Codex]');
3466
- if (!hasCodexCLI()) {
3467
- console.log(' \x1b[33mCodex CLI not found.\x1b[0m');
3468
- } else {
3469
- console.log(` Plugin: ${codexStatus() ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
3440
+ const sections = [
3441
+ { name: 'OpenCode', run: opencodeStatus, on: (target) => target === 'opencode' || target === 'all' },
3442
+ { name: 'CodeArts', run: codeartsStatus, on: (target) => target === 'codearts' || target === 'all' },
3443
+ { name: 'CodeArts Work', run: codeartsWorkStatus, on: (target) => target === 'codearts-work' || target === 'all' },
3444
+ { name: 'WorkBuddy', run: workbuddyStatus, on: (target) => target === 'workbuddy' || target === 'all' },
3445
+ { name: 'DSH', run: dshStatus, on: (target) => target === 'dsh' || target === 'all' },
3446
+ { name: 'OfficeAce', run: officeaceStatus, on: (target) => target === 'officeace' || target === 'all' },
3447
+ { name: 'Hermes Agent', run: hermesStatus, on: (target) => target === 'hermes' || target === 'all' },
3448
+ { name: 'OpenClaw', run: openclawStatus, on: (target) => target === 'openclaw' || target === 'all' },
3449
+ { name: 'AtomCode', run: atomcodeStatus, on: (target) => target === 'atomcode' || target === 'all' },
3450
+ { name: 'Codex', run: codexSectionStatus, on: (target) => target === 'codex' || target === 'all' },
3451
+ ];
3452
+
3453
+ // Capture each section's output so results can be grouped by install state
3454
+ // (installed first) instead of the fixed agent order.
3455
+ const originalLog = console.log;
3456
+ const results = [];
3457
+ for (const section of sections) {
3458
+ if (!section.on(scope)) continue;
3459
+ const lines = [];
3460
+ console.log = (...args) => lines.push(args.map((a) => String(a)).join(' '));
3461
+ try {
3462
+ section.run();
3463
+ } finally {
3464
+ console.log = originalLog;
3470
3465
  }
3466
+ const plain = lines.join('\n');
3467
+ const hasInstalled = /Installed/.test(plain);
3468
+ const hasMissing = /Not installed|not found/i.test(plain);
3469
+ const state = hasInstalled && hasMissing ? 'partial' : hasInstalled ? 'installed' : hasMissing ? 'not' : 'unknown';
3470
+ results.push({ name: section.name, lines, state });
3471
+ }
3472
+
3473
+ const stateOrder = { installed: 0, partial: 1, unknown: 2, not: 3 };
3474
+ results.sort((a, b) => stateOrder[a.state] - stateOrder[b.state]);
3475
+
3476
+ const namesIn = (state) => results.filter((r) => r.state === state).map((r) => r.name);
3477
+ const summaryParts = [];
3478
+ if (namesIn('installed').length) summaryParts.push(`已安装: ${namesIn('installed').join(', ')}`);
3479
+ if (namesIn('partial').length) summaryParts.push(`部分安装: ${namesIn('partial').join(', ')}`);
3480
+ if (namesIn('not').length) summaryParts.push(`未安装: ${namesIn('not').join(', ')}`);
3481
+ if (summaryParts.length) console.log(` ${summaryParts.join(' | ')}\n`);
3482
+
3483
+ for (const result of results) {
3484
+ console.log(`[${result.name}]`);
3485
+ for (const line of result.lines) console.log(line);
3486
+ console.log('');
3471
3487
  }
3472
- console.log('\nEnvironment:');
3488
+
3489
+ console.log('Environment:');
3473
3490
  console.log(` Node.js: ${process.version}`);
3474
3491
  console.log(` Platform: ${platform()}`);
3475
3492
  }
@@ -3812,6 +3829,7 @@ async function cmdDoctor() {
3812
3829
  { path: join(codeartsPluginsDir(), '.installed'), name: 'CodeArts' },
3813
3830
  { path: join(codeartsWorkPluginsDir(), '.installed'), name: 'CodeArts Work' },
3814
3831
  { path: join(dshPluginsDir(), '.installed'), name: 'DSH' },
3832
+ { path: join(hermesPluginsDir(), '.installed'), name: 'Hermes Agent' },
3815
3833
  { path: join(officeacePluginsDir(), '.installed'), name: 'OfficeAce' },
3816
3834
  { path: join(atomcodePluginsDir(), '.installed'), name: 'AtomCode' },
3817
3835
  ];
@@ -4033,10 +4051,6 @@ async function cmdUpdate() {
4033
4051
  console.log(`\x1b[33mMCP 工具在重启各 agent 会话后才生效。\x1b[0m`);
4034
4052
  return;
4035
4053
  }
4036
-
4037
- await cmdUninstall();
4038
- console.log('');
4039
- await cmdInstall();
4040
4054
  }
4041
4055
 
4042
4056
  async function cmdReinstall() {
@@ -4167,32 +4181,102 @@ async function cmdInstallHcloud() {
4167
4181
  printSandboxWarning('沙箱模式拦截了 KooCLI 自动安装(无法创建/写入安装目录)。');
4168
4182
  }
4169
4183
  }
4170
- } else if (os === 'linux') {
4171
- console.log('[Linux] Auto-install (pinned to KooCLI <version>):'.replace('<version>', kooCliVersion || ''));
4172
- const pkg = arch === 'arm64' ? 'linux-arm64' : 'linux-amd64';
4173
- console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
4174
- console.log(` tar -zxvf huaweicloud-cli-${pkg}.tar.gz`);
4175
- console.log(` mv hcloud ~/.local/bin/`);
4176
- console.log(` hcloud version`);
4177
- console.log(`\n Or one-liner (installs LATEST, may differ from pinned ${kooCliVersion || ''}):`);
4178
- console.log(` curl -sSL ${latestUrl}/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y`);
4179
- console.log(
4180
- ` \x1b[33m⚠️ The one-liner installs the latest KooCLI. If \`hcloud version\` != ${kooCliVersion}, doctor/check_cli will warn — use the pinned download above instead.\x1b[0m`,
4181
- );
4182
- console.log(`\nFull guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_02.html`);
4183
- } else if (os === 'darwin') {
4184
- console.log('[macOS] Auto-install (pinned to KooCLI <version>):'.replace('<version>', kooCliVersion || ''));
4185
- const pkg = arch === 'arm64' ? 'mac-arm64' : 'mac-amd64';
4186
- console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
4187
- console.log(` tar -zxvf huaweicloud-cli-${pkg}.tar.gz`);
4188
- console.log(` mv hcloud /usr/local/bin/`);
4189
- console.log(` hcloud version`);
4190
- console.log(`\n Or one-liner (installs LATEST, may differ from pinned ${kooCliVersion || ''}):`);
4191
- console.log(` curl -sSL ${latestUrl}/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y`);
4192
- console.log(
4193
- ` \x1b[33m⚠️ The one-liner installs the latest KooCLI. If \`hcloud version\` != ${kooCliVersion}, doctor/check_cli will warn — use the pinned download above instead.\x1b[0m`,
4194
- );
4195
- console.log(`\nFull guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_03.html`);
4184
+ } else if (os === 'linux' || os === 'darwin') {
4185
+ const label = os === 'linux' ? 'Linux' : 'macOS';
4186
+ const pkg =
4187
+ os === 'linux'
4188
+ ? arch === 'arm64'
4189
+ ? 'linux-arm64'
4190
+ : 'linux-amd64'
4191
+ : arch === 'arm64'
4192
+ ? 'mac-arm64'
4193
+ : 'mac-amd64';
4194
+ const url = `${baseUrl}/huaweicloud-cli-${pkg}.tar.gz`;
4195
+ const binDir = os === 'linux' ? join(homedir(), '.local', 'bin') : '/usr/local/bin';
4196
+ const binPath = join(binDir, 'hcloud');
4197
+
4198
+ const printManual = () => {
4199
+ console.log(`\n[${label}] Manual install (pinned to KooCLI ${kooCliVersion || ''}):`);
4200
+ console.log(` curl -LO "${url}"`);
4201
+ console.log(` tar -zxvf huaweicloud-cli-${pkg}.tar.gz`);
4202
+ console.log(` mv hcloud ${binDir}/`);
4203
+ console.log(` hcloud version`);
4204
+ console.log(`\n Or one-liner (installs LATEST, may differ from pinned ${kooCliVersion || ''}):`);
4205
+ console.log(` curl -sSL ${latestUrl}/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y`);
4206
+ console.log(
4207
+ ` \x1b[33m⚠️ The one-liner installs the latest KooCLI. If \`hcloud version\` != ${kooCliVersion}, doctor/check_cli will warn — use the pinned download above instead.\x1b[0m`,
4208
+ );
4209
+ console.log(
4210
+ `\nFull guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_0${os === 'linux' ? '2' : '3'}.html`,
4211
+ );
4212
+ };
4213
+
4214
+ if (detectCodeartsSandbox() === 'sandbox') {
4215
+ printSandboxWarning('沙箱模式拦截了 KooCLI 自动安装(无法创建/写入安装目录)。');
4216
+ printManual();
4217
+ } else {
4218
+ console.log(`[${label}] Auto-installing to ${binDir}...`);
4219
+ try {
4220
+ const tmpTar = join(tmpdir(), `huaweicloud-cli-${pkg}.tar.gz`);
4221
+ console.log(` Downloading ${url}...`);
4222
+ const dl = spawnSync('curl', ['-fL', url, '-o', tmpTar], { stdio: 'inherit', timeout: 180000 });
4223
+ if (dl.status !== 0) throw new Error(`下载失败 (curl exit ${dl.status})`);
4224
+
4225
+ console.log(' Extracting...');
4226
+ const ex = spawnSync('tar', ['-xzf', tmpTar, '-C', tmpdir()], { stdio: 'inherit', timeout: 60000 });
4227
+ if (ex.status !== 0) throw new Error(`解压失败 (tar exit ${ex.status})`);
4228
+ rmSync(tmpTar, { force: true });
4229
+ const extracted = join(tmpdir(), 'hcloud');
4230
+ if (!existsSync(extracted)) throw new Error('hcloud 二进制未生成,可能已被安全软件拦截');
4231
+
4232
+ try {
4233
+ mkdirSync(binDir, { recursive: true });
4234
+ } catch {}
4235
+ console.log(' Installing...');
4236
+ let mv = spawnSync('mv', [extracted, binPath], { stdio: 'inherit', timeout: 30000 });
4237
+ if (mv.status !== 0) {
4238
+ console.log(' 目标目录需要权限,尝试 sudo mv...');
4239
+ mv = spawnSync('sudo', ['mv', extracted, binPath], { stdio: 'inherit', timeout: 30000 });
4240
+ if (mv.status !== 0) throw new Error(`安装失败 (mv exit ${mv.status})`);
4241
+ }
4242
+
4243
+ console.log(`\n\x1b[32mInstall complete.\x1b[0m`);
4244
+ console.log(` Verify: ${binPath} version`);
4245
+
4246
+ // Ask user before accepting the privacy agreement — never auto-accept.
4247
+ if (process.stdin.isTTY && process.stdout.isTTY) {
4248
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
4249
+ const agree = await new Promise((resolve) => {
4250
+ rl.question('\n KooCLI requires accepting its privacy agreement. Do you accept? (y/N) ', (answer) => {
4251
+ rl.close();
4252
+ resolve(/^\s*y\s*$/i.test(answer));
4253
+ });
4254
+ });
4255
+ if (agree) {
4256
+ const r = spawnSync(binPath, ['version'], {
4257
+ input: 'y\n',
4258
+ encoding: 'utf8',
4259
+ timeout: 15000,
4260
+ });
4261
+ console.log(
4262
+ r.status === 0
4263
+ ? ' \x1b[32mPrivacy agreement accepted. KooCLI ready.\x1b[0m'
4264
+ : ' \x1b[33m无法写入配置目录。请在终端运行: hcloud version 并按提示操作\x1b[0m',
4265
+ );
4266
+ } else {
4267
+ console.log(' \x1b[33m请手动接受隐私协议:在终端运行 hcloud version 并按提示操作\x1b[0m');
4268
+ }
4269
+ } else {
4270
+ console.log(' \x1b[33m非交互会话:请稍后在终端运行 hcloud version 并按提示接受隐私协议\x1b[0m');
4271
+ }
4272
+ } catch (error) {
4273
+ console.log(`\n\x1b[33mAuto-install failed: ${error.message}\x1b[0m`);
4274
+ printManual();
4275
+ if (detectCodeartsSandbox() === 'sandbox') {
4276
+ printSandboxWarning('沙箱模式拦截了 KooCLI 自动安装(无法创建/写入安装目录)。');
4277
+ }
4278
+ }
4279
+ }
4196
4280
  }
4197
4281
 
4198
4282
  console.log('\nAfter install, set HCLOUD_BIN if hcloud is not on PATH.');
@@ -68,7 +68,7 @@ export const AGENTS = [
68
68
  id: 'officeace',
69
69
  pathPatterns: ['/.office-claw/', '/.officeace/'],
70
70
  envVars: ['OFFICEACE_SESSION_ID', 'OFFICE_CLAW_CONFIG_ROOT'],
71
- clientNames: ['office-claw-mcp-connector-probe'],
71
+ clientNames: ['office-claw-mcp-connector-probe', 'officeace-agent'],
72
72
  version: { type: 'officeace' },
73
73
  },
74
74
  {
@@ -94,6 +94,7 @@ export const AGENTS = [
94
94
  id: 'cursor',
95
95
  pathPatterns: ['/.cursor/', '/cursor/'],
96
96
  envVars: ['CURSOR_SESSION_ID', 'CURSOR_GIT_WORKDIR'],
97
+ clientNames: ['cursor-vscode'],
97
98
  version: null,
98
99
  },
99
100
  {