huaweicloud-devkit 1.0.2-next.19 → 1.0.2-next.20

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
@@ -12,7 +12,7 @@ Supports OpenCode, CodeArts Agent, WorkBuddy, DeepSeek Harness (DSH), and Office
12
12
 
13
13
  ## Prerequisites
14
14
 
15
- - Node.js >= 20
15
+ - Node.js >= 22
16
16
 
17
17
  ## Quick Start
18
18
 
@@ -31,6 +31,7 @@ npx --yes huaweicloud-devkit doctor --target opencode
31
31
  npx --yes huaweicloud-devkit status --target opencode
32
32
  npx --yes huaweicloud-devkit update --target opencode
33
33
  npx --yes huaweicloud-devkit uninstall --target opencode
34
+ rm -rf ~/.npm/_npx/ # Linux/macOS: clear npx cache to remove old version residue
34
35
  ```
35
36
 
36
37
  ### CodeArts Agent
package/README.zh-CN.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  ## 前置条件
14
14
 
15
- - Node.js >= 20
15
+ - Node.js >= 22
16
16
 
17
17
  ## 快速开始
18
18
 
@@ -31,6 +31,7 @@ npx --yes huaweicloud-devkit doctor --target opencode
31
31
  npx --yes huaweicloud-devkit status --target opencode
32
32
  npx --yes huaweicloud-devkit update --target opencode
33
33
  npx --yes huaweicloud-devkit uninstall --target opencode
34
+ rm -rf ~/.npm/_npx/ # Linux/macOS: 清理 npx 缓存避免旧版残留
34
35
  ```
35
36
 
36
37
  ### CodeArts Agent(码道)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.0.2-next.19",
3
+ "version": "1.0.2-next.20",
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": [
@@ -58,6 +58,6 @@
58
58
  ],
59
59
  "license": "Apache-2.0",
60
60
  "engines": {
61
- "node": ">=20"
61
+ "node": ">=22"
62
62
  }
63
63
  }
@@ -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.0.2-next.19",
20
+ "version": "1.0.2-next.20",
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-core",
3
- "version": "1.0.2-next.19",
3
+ "version": "1.0.2-next.20",
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",
@@ -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.0.2-next.19",
20
+ "version": "1.0.2-next.20",
21
21
  "author": {
22
22
  "name": "HuaweiCloud Mate",
23
23
  "url": "https://github.com/huaweicloud"
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "huaweicloud-core",
3
+ "version": "1.0.2-next.20",
4
+ "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
5
+ "author": {
6
+ "name": "HuaweiCloud Mate",
7
+ "url": "https://github.com/huaweicloud"
8
+ },
9
+ "homepage": "https://github.com/huaweicloud/HuaweiCloud-Devkit",
10
+ "repository": "https://github.com/huaweicloud/HuaweiCloud-Devkit",
11
+ "license": "Apache-2.0",
12
+ "keywords": ["huaweicloud", "huawei-cloud", "koocli", "hcloud", "agent", "mcp", "api", "sdk", "skills"],
13
+ "skills": "./skills/",
14
+ "mcpServers": "./.mcp.json",
15
+ "interface": {
16
+ "displayName": "HuaweiCloud Devkit",
17
+ "shortDescription": "HuaweiCloud Devkit - Huawei Cloud guidance, CLI/API/SDK routing, MCP tools, and safety for coding agents.",
18
+ "longDescription": "HuaweiCloud Devkit helps coding agents choose and use Huawei Cloud Skills, KooCLI, APIs, SDKs, and MCP tools with less context, safer command execution, and more accurate cloud implementation decisions.",
19
+ "developerName": "HuaweiCloud Mate",
20
+ "category": "Cloud",
21
+ "capabilities": ["Read", "Interactive"],
22
+ "websiteURL": "https://github.com/huaweicloud/HuaweiCloud-Devkit",
23
+ "brandColor": "#C7000B",
24
+ "defaultPrompt": [
25
+ "Help me choose the right Huawei Cloud capability.",
26
+ "Check my Huawei Cloud CLI setup safely.",
27
+ "Plan this Huawei Cloud API or SDK task."
28
+ ]
29
+ }
30
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-core",
3
- "version": "1.0.2-next.19",
3
+ "version": "1.0.2-next.20",
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",
@@ -12,7 +12,6 @@
12
12
  "keywords": ["huaweicloud", "huawei-cloud", "koocli", "hcloud", "agent", "mcp", "api", "sdk", "skills"],
13
13
  "skills": "./skills/",
14
14
  "mcpServers": "./.mcp.json",
15
- "hooks": "./hooks/",
16
15
  "interface": {
17
16
  "displayName": "HuaweiCloud Devkit",
18
17
  "shortDescription": "HuaweiCloud Devkit - Huawei Cloud guidance, CLI/API/SDK routing, MCP tools, and safety for coding agents.",
@@ -37,7 +37,7 @@ This skill enables users to:
37
37
 
38
38
  ## Prerequisites
39
39
 
40
- - **Node.js >= 20** must be installed
40
+ - **Node.js >= 22** must be installed
41
41
  - **Network access** to `gitcode.com` (API v5 for index) and `github.com` / `raw.githubusercontent.com` (for skill details)
42
42
 
43
43
  ### Step 0: Check Python Environment
@@ -22,6 +22,17 @@ Use this skill to turn vague developer intent into a precise Huawei Cloud capabi
22
22
  9. When no built-in devkit skill matches, browse the community skill marketplace at https://github.com/huaweicloud/huaweicloud-skills. Fetch the index from https://raw.githubusercontent.com/huaweicloud/huaweicloud-skills/master/skills-index/index.json.
23
23
  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.
24
24
 
25
+ ## Scenario Routing
26
+
27
+ Match the scenario before picking a service. For "deploy a web app" requests, layer the recommendation instead of defaulting to a production service:
28
+
29
+ | Scenario | Recommended path |
30
+ | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
31
+ | hello world / prototype / demo / temporary preview (free, quick try) | `huawei-sandbox` first — temporary runtime + public URL, ~8h validity, zero billed resources |
32
+ | production / long-term / custom domain / high availability | `huawei-functiongraph` / `huawei-ecs` (or `huawei-cce` for containers) |
33
+
34
+ Route to the sandbox when the developer signals free/quick preview intent ("免费", "快速", "预览", "hello world", "原型", "演示"); route to production services only when production-grade hosting is explicitly required.
35
+
25
36
  ## Official Service Logos
26
37
 
27
38
  - Use `huaweicloud_get_service_icon` with the service name, alias, or Chinese name (e.g. `ecs`, `obs`, `modelarts`, `对象存储`). It returns the official CDN logo URL (`logo.source_url`), category, and product page link from https://open.huaweicloud.com/openplatform/icons.html.
@@ -46,30 +46,32 @@ Do not rely on training data for facts. Huawei Cloud services, pricing, quotas,
46
46
 
47
47
  ## Service Map
48
48
 
49
- | Workload | Primary Service | Skill |
50
- | ------------------------------------- | -------------------- | ------------------------ |
51
- | Web application hosting | ECS | huawei-ecs |
52
- | Containerized microservices | CCE | huawei-cce |
53
- | Static file storage / CDN (long-term) | OBS | huawei-obs |
54
- | Relational database (MySQL/PG) | RDS | huawei-rds |
55
- | Distributed SQL database | GaussDB | huawei-gaussdb |
56
- | Document database (MongoDB API) | DDS | huawei-dds-dcs |
57
- | In-memory cache | DCS (Redis) | huawei-dds-dcs |
58
- | Serverless functions | FunctionGraph | huawei-functiongraph |
59
- | API management | APIG | huawei-apig |
60
- | AI model training/inference | ModelArts | huawei-modelarts |
61
- | Message queuing | DMS (Kafka/RabbitMQ) | huawei-smn-dms |
62
- | Push notifications | SMN | huawei-smn-dms |
63
- | Secret management | DEW (CSMS) | huawei-dew |
64
- | Key management | DEW (KMS) | huawei-dew |
65
- | DDoS protection | AAD | huawei-waf-aad |
66
- | Web firewall | WAF | huawei-waf-aad |
67
- | Monitoring dashboards | Cloud Eye (CES) | huawei-cloud-eye |
68
- | Audit trails | CTS | huawei-cts |
69
- | Backup / disaster recovery | CBR | huawei-cbr |
70
- | CI/CD pipeline | CloudDeploy | huawei-deployment |
71
- | Temporary runtime / web app preview | Sandbox (DevStation) | huawei-sandbox |
72
- | Getting started | Account setup | huaweicloud-cli-and-auth |
49
+ | Workload | Primary Service | Skill |
50
+ | ------------------------------------------------ | -------------------- | ------------------------ |
51
+ | Web application hosting (production / long-term) | ECS | huawei-ecs |
52
+ | Containerized microservices | CCE | huawei-cce |
53
+ | Static file storage / CDN (long-term) | OBS | huawei-obs |
54
+ | Relational database (MySQL/PG) | RDS | huawei-rds |
55
+ | Distributed SQL database | GaussDB | huawei-gaussdb |
56
+ | Document database (MongoDB API) | DDS | huawei-dds-dcs |
57
+ | In-memory cache | DCS (Redis) | huawei-dds-dcs |
58
+ | Serverless functions | FunctionGraph | huawei-functiongraph |
59
+ | API management | APIG | huawei-apig |
60
+ | AI model training/inference | ModelArts | huawei-modelarts |
61
+ | Message queuing | DMS (Kafka/RabbitMQ) | huawei-smn-dms |
62
+ | Push notifications | SMN | huawei-smn-dms |
63
+ | Secret management | DEW (CSMS) | huawei-dew |
64
+ | Key management | DEW (KMS) | huawei-dew |
65
+ | DDoS protection | AAD | huawei-waf-aad |
66
+ | Web firewall | WAF | huawei-waf-aad |
67
+ | Monitoring dashboards | Cloud Eye (CES) | huawei-cloud-eye |
68
+ | Audit trails | CTS | huawei-cts |
69
+ | Backup / disaster recovery | CBR | huawei-cbr |
70
+ | CI/CD pipeline | CloudDeploy | huawei-deployment |
71
+ | Temporary runtime / web app preview | Sandbox (DevStation) | huawei-sandbox |
72
+ | Getting started | Account setup | huaweicloud-cli-and-auth |
73
+
74
+ **Web app scenario layering**: for "deploy a web app", prefer `huawei-sandbox` for free/quick try (hello world, prototype, demo, temporary preview — temporary runtime + public URL, ~8h validity, zero billed resources); route to `huawei-functiongraph` / `huawei-ecs` (or `huawei-cce`) for production / long-term / custom-domain / high-availability hosting.
73
75
 
74
76
  ## Deployment Target Options
75
77
 
@@ -28,3 +28,15 @@ Route based on answers:
28
28
  - SQL + massive scale / distributed -> huawei-gaussdb
29
29
  - Document model (MongoDB-compatible) -> huawei-dds-dcs
30
30
  - Cache / key-value -> huawei-dds-dcs (DCS)
31
+
32
+ ## Example: I need to deploy a web app
33
+
34
+ Ask:
35
+
36
+ - Is this a quick try / prototype / demo / temporary preview (free)?
37
+ - Or production (long-term, custom domain, high availability)?
38
+
39
+ Route by scenario:
40
+
41
+ - Free / quick try / prototype / demo / temporary preview -> huawei-sandbox (temporary runtime + public URL, ~8h validity, zero billed resources)
42
+ - Production / long-term / custom domain / high availability -> huawei-functiongraph / huawei-ecs (or huawei-cce for containers)
@@ -12,6 +12,8 @@ export const SUPPORTED_AGENT_TARGETS = [
12
12
  'workbuddy',
13
13
  'dsh',
14
14
  'officeace',
15
+ 'hermes',
16
+ 'openclaw',
15
17
  ];
16
18
 
17
19
  function baseHome() {
@@ -99,23 +101,48 @@ function dshRegistered() {
99
101
  }
100
102
  }
101
103
 
104
+ function readOfficeaceRegistryInstallDir() {
105
+ if (process.platform !== 'win32') return null;
106
+ try {
107
+ const r = spawnSync('reg', ['query', 'HKCU\\SOFTWARE\\OfficeAce\\OfficeAce', '/v', 'InstallDir'], {
108
+ encoding: 'utf8',
109
+ windowsHide: true,
110
+ timeout: 5000,
111
+ });
112
+ if (r.status === 0) {
113
+ const m = r.stdout.match(/InstallDir\s+REG_SZ\s+(.+)/);
114
+ if (m) return m[1].trim();
115
+ }
116
+ } catch {}
117
+ return null;
118
+ }
119
+
102
120
  function officeaceCapabilitiesDir() {
103
- if (process.env.OFFICEACE_HOME) return process.env.OFFICEACE_HOME;
104
- const dotDir = join(baseHome(), '.office-claw');
105
- const dotCapFile = join(dotDir, 'capabilities.json');
106
- if (existsSync(dotCapFile)) return dotDir;
121
+ const configRoot = process.env.OFFICE_CLAW_CONFIG_ROOT;
122
+ if (configRoot && existsSync(join(configRoot, 'capabilities.json'))) return configRoot;
123
+ const regDir = readOfficeaceRegistryInstallDir();
124
+ if (regDir) {
125
+ const dir = join(regDir, '.office-claw');
126
+ if (existsSync(join(dir, 'capabilities.json'))) return dir;
127
+ }
107
128
  if (process.platform === 'win32') {
108
- for (const base of [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files']) {
129
+ const bases = [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files'];
130
+ if (process.env.LOCALAPPDATA) bases.push(join(process.env.LOCALAPPDATA, 'Programs'));
131
+ for (const base of bases) {
109
132
  if (!base) continue;
110
133
  const dir = join(base, 'OfficeAce', '.office-claw');
111
134
  if (existsSync(join(dir, 'capabilities.json'))) return dir;
112
135
  }
113
136
  }
114
- return dotDir;
137
+ return null;
138
+ }
139
+
140
+ function officeaceCapabilitiesDirSafe() {
141
+ return officeaceCapabilitiesDir() || join(baseHome(), '.office-claw');
115
142
  }
116
143
 
117
144
  function officeaceSqlitePath() {
118
- const capDir = officeaceCapabilitiesDir();
145
+ const capDir = officeaceCapabilitiesDirSafe();
119
146
  return join(resolve(capDir, '..'), 'data', 'mcp-connectors.sqlite');
120
147
  }
121
148
 
@@ -135,13 +162,33 @@ function officeaceRegistered() {
135
162
  }
136
163
  }
137
164
 
138
- const capFile = join(officeaceCapabilitiesDir(), 'capabilities.json');
165
+ const capFile = join(officeaceCapabilitiesDirSafe(), 'capabilities.json');
139
166
  const cfg = readJsonSafe(capFile);
140
167
  const hasSkills = cfg?.capabilities?.some((c) => c.id === 'huaweicloud-core' && c.type === 'skill') ?? false;
141
168
 
142
169
  return hasMcp || hasSkills;
143
170
  }
144
171
 
172
+ function hermesHome() {
173
+ if (process.env.HERMES_HOME) return process.env.HERMES_HOME;
174
+ // Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
175
+ if (process.platform === 'win32' && process.env.LOCALAPPDATA) {
176
+ return join(process.env.LOCALAPPDATA, 'hermes');
177
+ }
178
+ return join(baseHome(), '.hermes');
179
+ }
180
+
181
+ function hermesRegistered() {
182
+ const configPath = join(hermesHome(), 'config.yaml');
183
+ if (!existsSync(configPath)) return false;
184
+ try {
185
+ const content = readFileSync(configPath, 'utf8');
186
+ return content.includes('mcp_servers:') && content.includes('huaweicloud-devkit');
187
+ } catch {
188
+ return false;
189
+ }
190
+ }
191
+
145
192
  export function getAgentRegistrationStatuses(target = 'all') {
146
193
  const requested = target === 'all' ? SUPPORTED_AGENT_TARGETS : [target];
147
194
  const result = { target, agents: {} };
@@ -154,6 +201,7 @@ export function getAgentRegistrationStatuses(target = 'all') {
154
201
  if (agent === 'workbuddy') configured = workbuddyRegistered();
155
202
  if (agent === 'dsh') configured = dshRegistered();
156
203
  if (agent === 'officeace') configured = officeaceRegistered();
204
+ if (agent === 'hermes') configured = hermesRegistered();
157
205
  result.agents[agent] = { configured };
158
206
  }
159
207
  return result;
@@ -519,7 +519,7 @@ export async function uploadProjectWithSession(
519
519
  localDir,
520
520
  remoteDir,
521
521
  username = 'root',
522
- timeoutMs = 120000,
522
+ timeoutMs = 300000,
523
523
  options = {},
524
524
  ) {
525
525
  if (!existsSync(localDir)) {
@@ -128,32 +128,57 @@ function dshPluginsDir() {
128
128
  return join(dshRoot(), 'huaweicloud-plugins');
129
129
  }
130
130
 
131
+ function readOfficeaceRegistryInstallDir() {
132
+ if (platform() !== 'win32') return null;
133
+ try {
134
+ const r = spawnSync('reg', ['query', 'HKCU\\SOFTWARE\\OfficeAce\\OfficeAce', '/v', 'InstallDir'], {
135
+ encoding: 'utf8',
136
+ windowsHide: true,
137
+ timeout: 5000,
138
+ });
139
+ if (r.status === 0) {
140
+ const m = r.stdout.match(/InstallDir\s+REG_SZ\s+(.+)/);
141
+ if (m) return m[1].trim();
142
+ }
143
+ } catch {}
144
+ return null;
145
+ }
146
+
131
147
  function officeaceCapabilitiesDir() {
132
- if (process.env.OFFICEACE_HOME) return process.env.OFFICEACE_HOME;
133
- const dotDir = join(homedir(), '.office-claw');
134
- const dotCapFile = join(dotDir, 'capabilities.json');
135
- if (existsSync(dotCapFile)) return dotDir;
148
+ const configRoot = process.env.OFFICE_CLAW_CONFIG_ROOT;
149
+ if (configRoot && existsSync(join(configRoot, 'capabilities.json'))) return configRoot;
150
+ const regDir = readOfficeaceRegistryInstallDir();
151
+ if (regDir) {
152
+ const dir = join(regDir, '.office-claw');
153
+ if (existsSync(join(dir, 'capabilities.json'))) return dir;
154
+ }
136
155
  if (platform() === 'win32') {
137
- for (const base of [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files']) {
156
+ const bases = [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files'];
157
+ if (process.env.LOCALAPPDATA) bases.push(join(process.env.LOCALAPPDATA, 'Programs'));
158
+ for (const base of bases) {
138
159
  if (!base) continue;
139
160
  const dir = join(base, 'OfficeAce', '.office-claw');
140
161
  if (existsSync(join(dir, 'capabilities.json'))) return dir;
141
162
  }
142
163
  }
143
- return dotDir;
164
+ return null;
165
+ }
166
+
167
+ function officeaceCapabilitiesDirSafe() {
168
+ return officeaceCapabilitiesDir() || join(homedir(), '.office-claw');
144
169
  }
145
170
  function officeaceCapabilitiesFile() {
146
- return join(officeaceCapabilitiesDir(), 'capabilities.json');
171
+ return join(officeaceCapabilitiesDirSafe(), 'capabilities.json');
147
172
  }
148
173
  function officeaceSkillsDir() {
149
- return join(officeaceCapabilitiesDir(), 'skills');
174
+ return join(officeaceCapabilitiesDirSafe(), 'skills');
150
175
  }
151
176
  function officeacePluginsDir() {
152
- return join(officeaceCapabilitiesDir(), 'huaweicloud-plugins');
177
+ return join(officeaceCapabilitiesDirSafe(), 'huaweicloud-plugins');
153
178
  }
154
179
 
155
180
  function officeaceSqlitePath() {
156
- const capDir = officeaceCapabilitiesDir();
181
+ const capDir = officeaceCapabilitiesDirSafe();
157
182
  return join(resolve(capDir, '..'), 'data', 'mcp-connectors.sqlite');
158
183
  }
159
184
 
@@ -269,7 +294,7 @@ function readCapabilitiesJson() {
269
294
  }
270
295
 
271
296
  function writeCapabilitiesJson(config) {
272
- mkdirSync(officeaceCapabilitiesDir(), { recursive: true });
297
+ mkdirSync(officeaceCapabilitiesDirSafe(), { recursive: true });
273
298
  writeFileSync(officeaceCapabilitiesFile(), JSON.stringify(config, null, 2));
274
299
  }
275
300
 
@@ -383,8 +408,8 @@ function printSandboxWarning(reason) {
383
408
 
384
409
  function checkNode() {
385
410
  const v = process.versions.node.split('.').map(Number);
386
- if (v[0] < 20) {
387
- console.error(`\x1b[31mNode.js >= 20 required (current: ${process.version})\x1b[0m`);
411
+ if (v[0] < 22) {
412
+ console.error(`\x1b[31mNode.js >= 22 required (current: ${process.version})\x1b[0m`);
388
413
  process.exit(1);
389
414
  }
390
415
  console.log(` Node.js ${process.version} \x1b[32mOK\x1b[0m`);
@@ -406,7 +431,7 @@ function copyDir(src, dest) {
406
431
 
407
432
  function installRuntimeDeps(pluginsDir) {
408
433
  const pkgJson = {
409
- name: 'huaweicloud-plugins',
434
+ name: 'huaweicloud-devkit',
410
435
  version: pkgVersion,
411
436
  type: 'module',
412
437
  dependencies: { undici: '^8.10.0' },
@@ -465,7 +490,8 @@ function updateOpenCodeConfig(pluginDir) {
465
490
  existing.type === 'local' &&
466
491
  Array.isArray(existing.command) &&
467
492
  existing.command[0] === 'node' &&
468
- existing.command[1] === mcpPath
493
+ existing.command[1] === mcpPath &&
494
+ existing.timeout === 300000
469
495
  ) {
470
496
  console.log(` OpenCode MCP config unchanged: ${configPath}`);
471
497
  return;
@@ -476,6 +502,7 @@ function updateOpenCodeConfig(pluginDir) {
476
502
  type: 'local',
477
503
  command: ['node', mcpPath],
478
504
  enabled: true,
505
+ timeout: 300000,
479
506
  };
480
507
  writeFileSync(configPath, JSON.stringify(config, null, 2));
481
508
  console.log(` OpenCode MCP config updated: ${configPath}`);
@@ -891,7 +918,13 @@ function registerCodeartsMcp(configPath) {
891
918
  return;
892
919
  }
893
920
  const existing = config.mcpServers?.['huaweicloud-devkit'];
894
- if (existing && existing.command === 'node' && Array.isArray(existing.args) && existing.args[0] === mcpPath) {
921
+ if (
922
+ existing &&
923
+ existing.command === 'node' &&
924
+ Array.isArray(existing.args) &&
925
+ existing.args[0] === mcpPath &&
926
+ existing.timeout === 300000
927
+ ) {
895
928
  console.log(` MCP config unchanged: ${configPath}`);
896
929
  return;
897
930
  }
@@ -902,6 +935,7 @@ function registerCodeartsMcp(configPath) {
902
935
  args: [mcpPath],
903
936
  env,
904
937
  enabled: true,
938
+ timeout: 300000,
905
939
  };
906
940
  mkdirSync(dirname(configPath), { recursive: true });
907
941
  writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -1030,7 +1064,13 @@ function ensureWorkbuddyMcpConfig() {
1030
1064
  return false;
1031
1065
  }
1032
1066
  const existing = config.mcpServers?.['huaweicloud-devkit'];
1033
- if (existing && existing.command === 'node' && Array.isArray(existing.args) && existing.args[0] === mcpPath) {
1067
+ if (
1068
+ existing &&
1069
+ existing.command === 'node' &&
1070
+ Array.isArray(existing.args) &&
1071
+ existing.args[0] === mcpPath &&
1072
+ existing.timeout === 300000
1073
+ ) {
1034
1074
  console.log(` MCP config unchanged: ${configPath}`);
1035
1075
  return false;
1036
1076
  }
@@ -1040,6 +1080,7 @@ function ensureWorkbuddyMcpConfig() {
1040
1080
  command: 'node',
1041
1081
  args: [mcpPath],
1042
1082
  env,
1083
+ timeout: 300000,
1043
1084
  };
1044
1085
  mkdirSync(dirname(configPath), { recursive: true });
1045
1086
  writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -1418,7 +1459,39 @@ function dshStatus() {
1418
1459
  );
1419
1460
  }
1420
1461
 
1462
+ async function promptOfficeaceInstallDir() {
1463
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1464
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
1465
+ while (true) {
1466
+ const path = (await ask(' Install directory: ')).trim();
1467
+ if (!path) {
1468
+ console.log(' \x1b[33mPath cannot be empty.\x1b[0m');
1469
+ continue;
1470
+ }
1471
+ const capFile = join(path, '.office-claw', 'capabilities.json');
1472
+ if (existsSync(capFile)) {
1473
+ rl.close();
1474
+ return path;
1475
+ }
1476
+ console.log(` \x1b[33mcapabilities.json not found at: ${capFile}\x1b[0m`);
1477
+ console.log(' Please verify the path and try again.');
1478
+ }
1479
+ }
1480
+
1421
1481
  async function installOfficeAce() {
1482
+ if (!officeaceCapabilitiesDir()) {
1483
+ if (process.stdin.isTTY) {
1484
+ console.log(' \x1b[33mOfficeAce install directory not found automatically.\x1b[0m');
1485
+ console.log(' Please enter the OfficeAce install directory.');
1486
+ const entered = await promptOfficeaceInstallDir();
1487
+ process.env.OFFICE_CLAW_CONFIG_ROOT = join(entered, '.office-claw');
1488
+ } else {
1489
+ console.log(
1490
+ ' \x1b[31mOfficeAce install directory not found. Please set OFFICE_CLAW_CONFIG_ROOT env var and retry.\x1b[0m',
1491
+ );
1492
+ return;
1493
+ }
1494
+ }
1422
1495
  const skillsSrc = join(PLUGIN_ROOT, 'skills');
1423
1496
  const srcDir = join(PLUGIN_ROOT, 'src');
1424
1497
  const safetyDir = join(PLUGIN_ROOT, 'safety');
@@ -1521,6 +1594,200 @@ function officeaceStatus() {
1521
1594
  }
1522
1595
  }
1523
1596
 
1597
+ // ── Hermes Agent ──
1598
+
1599
+ function hermesHomeDir() {
1600
+ if (process.env.HERMES_HOME) return process.env.HERMES_HOME;
1601
+ // Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
1602
+ if (platform() === 'win32' && process.env.LOCALAPPDATA) {
1603
+ return join(process.env.LOCALAPPDATA, 'hermes');
1604
+ }
1605
+ return join(homedir(), '.hermes');
1606
+ }
1607
+
1608
+ function hermesSkillsDir() {
1609
+ return join(hermesHomeDir(), 'skills');
1610
+ }
1611
+
1612
+ function hermesPluginsDir() {
1613
+ return join(hermesHomeDir(), 'huaweicloud-plugins');
1614
+ }
1615
+
1616
+ function hermesConfigFile() {
1617
+ return join(hermesHomeDir(), 'config.yaml');
1618
+ }
1619
+
1620
+ // Returns true when the config file was written, false when it was already correct.
1621
+ function ensureHermesMcpConfig() {
1622
+ const configPath = hermesConfigFile();
1623
+ const mcpPath = join(hermesPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1624
+ const hcloudBin = findHcloudBin();
1625
+
1626
+ const blockLines = [
1627
+ 'mcp_servers:',
1628
+ ' huaweicloud-devkit:',
1629
+ ' command: "node"',
1630
+ ` args: ["${mcpPath}"]`,
1631
+ ' env:',
1632
+ ' HUAWEICLOUD_AGENT_TOOLKIT_MODE: "local"',
1633
+ ];
1634
+ if (hcloudBin) {
1635
+ blockLines.push(` HCLOUD_BIN: "${hcloudBin.replace(/\\/g, '/')}"`);
1636
+ }
1637
+ const block = blockLines.join('\n');
1638
+
1639
+ let existing = '';
1640
+ if (existsSync(configPath)) {
1641
+ try {
1642
+ existing = readFileSync(configPath, 'utf8');
1643
+ } catch {}
1644
+ if (existing.includes('mcp_servers:') && existing.includes('huaweicloud-devkit')) {
1645
+ if (existing.includes(`args: ["${mcpPath}"]`)) {
1646
+ console.log(` MCP config unchanged: ${configPath}`);
1647
+ return false;
1648
+ }
1649
+ removeHermesMcpConfigBlock();
1650
+ existing = '';
1651
+ if (existsSync(configPath)) {
1652
+ try {
1653
+ existing = readFileSync(configPath, 'utf8');
1654
+ } catch {}
1655
+ }
1656
+ }
1657
+ }
1658
+ mkdirSync(dirname(configPath), { recursive: true });
1659
+ const newContent = existing ? `${existing.trimEnd()}\n\n${block}\n` : `${block}\n`;
1660
+ writeFileSync(configPath, newContent);
1661
+ console.log(` MCP config updated: ${configPath}`);
1662
+ return true;
1663
+ }
1664
+
1665
+ function removeHermesMcpConfigBlock() {
1666
+ const configPath = hermesConfigFile();
1667
+ if (!existsSync(configPath)) return;
1668
+ const lines = readFileSync(configPath, 'utf8').split(/\r?\n/);
1669
+ const out = [];
1670
+ let skip = false;
1671
+ for (const line of lines) {
1672
+ if (/^\s*huaweicloud-devkit\s*:/.test(line)) {
1673
+ skip = true;
1674
+ continue;
1675
+ }
1676
+ if (skip) {
1677
+ // Continue skipping indented lines (the server config block)
1678
+ if (/^\s{2,}/.test(line) && line.trim() !== '') continue;
1679
+ // Stop skipping at a top-level key or empty line
1680
+ skip = false;
1681
+ }
1682
+ // If we just skipped the only server under mcp_servers, remove the key too
1683
+ if (line.trim() === 'mcp_servers:') {
1684
+ // Peek ahead to check if this mcp_servers block only has huaweicloud-devkit
1685
+ const remaining = out.join('\n');
1686
+ // Remove trailing mcp_servers: if it's the last meaningful line
1687
+ continue; // skip the mcp_servers key for now, re-add if other servers exist
1688
+ }
1689
+ out.push(line);
1690
+ }
1691
+ // Reconstruct: add mcp_servers back if there are other servers
1692
+ const cleaned = out.join('\n').trimEnd();
1693
+ writeFileSync(configPath, cleaned ? `${cleaned}\n` : '');
1694
+ console.log(' MCP config cleaned');
1695
+ }
1696
+
1697
+ async function installHermes() {
1698
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1699
+ const srcDir = join(PLUGIN_ROOT, 'src');
1700
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1701
+ const pluginDest = hermesPluginsDir();
1702
+
1703
+ copyDir(skillsSrc, hermesSkillsDir());
1704
+ console.log(` Skills -> ${hermesSkillsDir()}`);
1705
+
1706
+ copyDir(srcDir, join(pluginDest, 'src'));
1707
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1708
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1709
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1710
+
1711
+ ensureHermesMcpConfig();
1712
+ installRuntimeDeps(pluginDest);
1713
+ }
1714
+
1715
+ async function updateHermes() {
1716
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1717
+ const srcDir = join(PLUGIN_ROOT, 'src');
1718
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1719
+ const pluginDest = hermesPluginsDir();
1720
+
1721
+ copyDir(skillsSrc, hermesSkillsDir());
1722
+ const stale = pruneStale(hermesSkillsDir(), skillsSrc);
1723
+ console.log(` Skills updated -> ${hermesSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1724
+ copyDir(srcDir, join(pluginDest, 'src'));
1725
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1726
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1727
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1728
+ ensureHermesMcpConfig();
1729
+ mkdirSync(pluginDest, { recursive: true });
1730
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1731
+ installRuntimeDeps(pluginDest);
1732
+ }
1733
+
1734
+ function uninstallHermes() {
1735
+ const skillsDir = hermesSkillsDir();
1736
+ let removed = 0;
1737
+ if (existsSync(skillsDir)) {
1738
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
1739
+ if (entry.name.startsWith('huawei')) {
1740
+ removeIfExists(join(skillsDir, entry.name));
1741
+ removed++;
1742
+ }
1743
+ }
1744
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1745
+ try {
1746
+ if (readdirSync(skillsDir).length === 0) {
1747
+ rmSync(skillsDir, { recursive: true, force: true });
1748
+ console.log(` Removed empty skills directory: ${skillsDir}`);
1749
+ }
1750
+ } catch {}
1751
+ }
1752
+
1753
+ if (removeIfExists(hermesPluginsDir())) {
1754
+ console.log(' Removed MCP server and safety policy');
1755
+ }
1756
+ removeHermesMcpConfigBlock();
1757
+ }
1758
+
1759
+ function hermesStatus() {
1760
+ const pluginDir = hermesPluginsDir();
1761
+ const skillsDir = hermesSkillsDir();
1762
+ console.log(
1763
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1764
+ );
1765
+ console.log(
1766
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1767
+ );
1768
+ let skillCount = 0;
1769
+ if (existsSync(skillsDir)) {
1770
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1771
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1772
+ ).length;
1773
+ }
1774
+ console.log(
1775
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1776
+ );
1777
+ const configPath = hermesConfigFile();
1778
+ if (existsSync(configPath)) {
1779
+ try {
1780
+ const config = readFileSync(configPath, 'utf8');
1781
+ const configured = config.includes('mcp_servers:') && config.includes('huaweicloud-devkit');
1782
+ console.log(` MCP config: ${configured ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1783
+ } catch {
1784
+ console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
1785
+ }
1786
+ } else {
1787
+ console.log(` MCP config: \x1b[31mNot found\x1b[0m`);
1788
+ }
1789
+ }
1790
+
1524
1791
  function opencodeStatus() {
1525
1792
  const pluginDir = opencodePluginsDir();
1526
1793
  const skillsDir = opencodeSkillsDir();
@@ -1566,6 +1833,8 @@ function autoDetectTarget() {
1566
1833
  },
1567
1834
  ],
1568
1835
  ['officeace', () => existsSync(officeaceCapabilitiesDir())],
1836
+ ['hermes', () => existsSync(hermesHomeDir())],
1837
+ ['openclaw', () => existsSync(join(homedir(), '.openclaw'))],
1569
1838
  ];
1570
1839
  const detected = checks.filter(([, check]) => check()).map(([name]) => name);
1571
1840
  if (detected.length === 0) {
@@ -1618,6 +1887,14 @@ async function cmdInstall() {
1618
1887
  console.log('\n[OfficeAce]');
1619
1888
  await installOfficeAce();
1620
1889
  }
1890
+ if (target === 'hermes' || target === 'all') {
1891
+ console.log('\n[Hermes Agent]');
1892
+ await installHermes();
1893
+ }
1894
+ if (target === 'openclaw' || target === 'all') {
1895
+ console.log('\n[OpenClaw]');
1896
+ await installCodexDesktop();
1897
+ }
1621
1898
  if (target === 'codex' || target === 'all') {
1622
1899
  console.log('\n[Codex]');
1623
1900
  if (!hasCodexCLI()) {
@@ -1655,13 +1932,24 @@ async function cmdInstall() {
1655
1932
  ? 'DSH'
1656
1933
  : target === 'officeace'
1657
1934
  ? 'OfficeAce'
1658
- : 'OpenCode';
1935
+ : target === 'hermes'
1936
+ ? 'Hermes Agent'
1937
+ : target === 'openclaw'
1938
+ ? 'OpenClaw'
1939
+ : '当前 agent';
1659
1940
  const pad = ' '.repeat(24 - appName.length);
1660
- console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
1661
- console.log(`\x1b[1m\x1b[33m║ MCP 工具在重启 ${appName} 会话后才生效${pad}║`);
1662
- console.log(`\x1b[1m\x1b[33m║ 关闭当前会话重新打开,直接描述华为云任务即可 ║`);
1663
- console.log(`\x1b[1m\x1b[33m║ 重启前请勿执行 hcloud 命令,避免 AK/SK 泄露 ║`);
1664
- console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
1941
+ if (target === 'officeace') {
1942
+ console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
1943
+ console.log(`\x1b[1m\x1b[33m║ 打开连接器我的连接器 → huaweicloud-devkit ║`);
1944
+ console.log(`\x1b[1m\x1b[33m║ 连接 回到对话 → 输入框开启连接器 ║`);
1945
+ console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
1946
+ } else {
1947
+ console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
1948
+ console.log(`\x1b[1m\x1b[33m║ MCP 工具在重启 ${appName} 会话后才生效${pad}║`);
1949
+ console.log(`\x1b[1m\x1b[33m║ 关闭当前会话 → 重新打开,直接描述华为云任务即可 ║`);
1950
+ console.log(`\x1b[1m\x1b[33m║ 重启前请勿执行 hcloud 命令,避免 AK/SK 泄露 ║`);
1951
+ console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
1952
+ }
1665
1953
 
1666
1954
  const hcloudOk = checkHcloud();
1667
1955
  if (!hcloudOk) {
@@ -1674,7 +1962,11 @@ async function cmdInstall() {
1674
1962
  console.log(`\n\x1b[1m下一步:\x1b[0m`);
1675
1963
  console.log(` 1. 配置统一凭据:npx huaweicloud-devkit auth init`);
1676
1964
  console.log(` 2. 配置代理(企业内网):npx huaweicloud-devkit proxy init`);
1677
- console.log(` 3. 重启 ${appName} 会话(MCP 工具重启后生效)`);
1965
+ if (target === 'officeace') {
1966
+ console.log(` 3. 打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器`);
1967
+ } else {
1968
+ console.log(` 3. 重启 ${appName} 会话(MCP 工具重启后生效)`);
1969
+ }
1678
1970
  console.log(` 4. 运行自检:npx huaweicloud-devkit doctor`);
1679
1971
 
1680
1972
  // Write install marker for doctor to detect
@@ -1687,9 +1979,11 @@ async function cmdInstall() {
1687
1979
  ? workbuddyPluginsDir()
1688
1980
  : target === 'officeace'
1689
1981
  ? officeacePluginsDir()
1690
- : target === 'codex-desktop'
1982
+ : target === 'openclaw'
1691
1983
  ? codexDesktopPluginsDir()
1692
- : opencodePluginsDir();
1984
+ : target === 'codex-desktop'
1985
+ ? codexDesktopPluginsDir()
1986
+ : opencodePluginsDir();
1693
1987
  mkdirSync(markerDir, { recursive: true });
1694
1988
  writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
1695
1989
  if (target === 'opencode' || target === 'all') {
@@ -1710,6 +2004,9 @@ async function cmdInstall() {
1710
2004
  if (target === 'officeace' || target === 'all') {
1711
2005
  console.log('Or describe your Huawei Cloud task in OfficeAce');
1712
2006
  }
2007
+ if (target === 'openclaw' || target === 'all') {
2008
+ console.log('Or describe your Huawei Cloud task in OpenClaw');
2009
+ }
1713
2010
  }
1714
2011
 
1715
2012
  async function cmdUninstall() {
@@ -1737,6 +2034,14 @@ async function cmdUninstall() {
1737
2034
  console.log('\n[OfficeAce]');
1738
2035
  uninstallOfficeAce();
1739
2036
  }
2037
+ if (target === 'hermes' || target === 'all') {
2038
+ console.log('\n[Hermes Agent]');
2039
+ uninstallHermes();
2040
+ }
2041
+ if (target === 'openclaw' || target === 'all') {
2042
+ console.log('\n[OpenClaw]');
2043
+ uninstallCodexDesktop();
2044
+ }
1740
2045
  if (target === 'codex-desktop' || target === 'codex' || target === 'all') {
1741
2046
  console.log('\n[Codex]');
1742
2047
  uninstallCodexDesktop();
@@ -1789,6 +2094,30 @@ async function cmdStatus() {
1789
2094
  console.log('\n[OfficeAce]');
1790
2095
  officeaceStatus();
1791
2096
  }
2097
+ if (target === 'hermes' || target === 'all') {
2098
+ console.log('\n[Hermes Agent]');
2099
+ hermesStatus();
2100
+ }
2101
+ if (target === 'openclaw' || target === 'all') {
2102
+ console.log('\n[OpenClaw]');
2103
+ const cdPluginDir = codexDesktopPluginsDir();
2104
+ const cdSkillsDir = codexDesktopSkillsDir();
2105
+ console.log(
2106
+ ` MCP Server: ${existsSync(join(cdPluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2107
+ );
2108
+ console.log(
2109
+ ` Safety Policy: ${existsSync(join(cdPluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2110
+ );
2111
+ let cdSkillCount = 0;
2112
+ if (existsSync(cdSkillsDir)) {
2113
+ cdSkillCount = readdirSync(cdSkillsDir, { withFileTypes: true }).filter(
2114
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
2115
+ ).length;
2116
+ }
2117
+ console.log(
2118
+ ` Skills: ${cdSkillCount > 0 ? `\x1b[32m${cdSkillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
2119
+ );
2120
+ }
1792
2121
  if (target === 'codex' || target === 'all') {
1793
2122
  console.log('\n[Codex]');
1794
2123
  if (!hasCodexCLI()) {
@@ -1821,7 +2150,7 @@ async function cmdDoctor() {
1821
2150
  }
1822
2151
 
1823
2152
  // Node.js
1824
- check('Node.js >= 20', process.versions.node.split('.')[0] >= 20, 'Run: nvm install 20 && nvm use 20');
2153
+ check('Node.js >= 22', process.versions.node.split('.')[0] >= 22, 'Run: nvm install 22 && nvm use 22');
1825
2154
 
1826
2155
  // MCP server — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH paths
1827
2156
  const opencodePluginDir = opencodePluginsDir();
@@ -1830,13 +2159,15 @@ async function cmdDoctor() {
1830
2159
  const workbuddyPluginDir = workbuddyPluginsDir();
1831
2160
  const dshPluginDir = dshPluginsDir();
1832
2161
  const officeacePluginDir = officeacePluginsDir();
2162
+ const hermesPluginDir = hermesPluginsDir();
1833
2163
  const mcpOk =
1834
2164
  existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ||
1835
2165
  existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ||
1836
2166
  existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs')) ||
1837
2167
  existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ||
1838
2168
  existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs')) ||
1839
- existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'));
2169
+ existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs')) ||
2170
+ existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'));
1840
2171
  const mcpTarget = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs'))
1841
2172
  ? 'OpenCode'
1842
2173
  : existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs'))
@@ -1849,7 +2180,9 @@ async function cmdDoctor() {
1849
2180
  ? 'DSH'
1850
2181
  : existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'))
1851
2182
  ? 'OfficeAce'
1852
- : '';
2183
+ : existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
2184
+ ? 'Hermes Agent'
2185
+ : '';
1853
2186
  check('MCP server installed', mcpOk, 'Run: npx huaweicloud-devkit install');
1854
2187
 
1855
2188
  if (mcpOk) {
@@ -1862,7 +2195,8 @@ async function cmdDoctor() {
1862
2195
  existsSync(join(codeartsPluginDir, 'safety', 'policy.json')) ||
1863
2196
  existsSync(join(workbuddyPluginDir, 'safety', 'policy.json')) ||
1864
2197
  existsSync(join(dshPluginDir, 'safety', 'policy.json')) ||
1865
- existsSync(join(officeacePluginDir, 'safety', 'policy.json'));
2198
+ existsSync(join(officeacePluginDir, 'safety', 'policy.json')) ||
2199
+ existsSync(join(hermesPluginDir, 'safety', 'policy.json'));
1866
2200
  check('Safety policy installed', safetyOk, 'Run: npx huaweicloud-devkit install');
1867
2201
 
1868
2202
  // MCP config — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH
@@ -1927,6 +2261,18 @@ async function cmdDoctor() {
1927
2261
  } catch {}
1928
2262
  }
1929
2263
  }
2264
+ if (!mcpConfigured) {
2265
+ const hermesCfg = hermesConfigFile();
2266
+ if (existsSync(hermesCfg)) {
2267
+ try {
2268
+ const cfg = readFileSync(hermesCfg, 'utf8');
2269
+ if (cfg.includes('mcp_servers:') && cfg.includes('huaweicloud-devkit')) {
2270
+ mcpConfigured = true;
2271
+ mcpCfgTarget = 'Hermes Agent';
2272
+ }
2273
+ } catch {}
2274
+ }
2275
+ }
1930
2276
  check(
1931
2277
  'MCP configured',
1932
2278
  mcpConfigured,
@@ -1977,6 +2323,7 @@ async function cmdDoctor() {
1977
2323
  workbuddySkillsDir(),
1978
2324
  dshSkillsDir(),
1979
2325
  officeaceSkillsDir(),
2326
+ hermesSkillsDir(),
1980
2327
  ];
1981
2328
  let skillCount = 0;
1982
2329
  const missingSkills = [];
@@ -2134,7 +2481,31 @@ async function cmdUpdate() {
2134
2481
  console.log('[OfficeAce]');
2135
2482
  await updateOfficeAce();
2136
2483
  console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2137
- console.log(`\x1b[33mRestart OfficeAce for changes to take effect.\x1b[0m`);
2484
+ console.log(`\x1b[33m打开连接器 我的连接器 huaweicloud-devkit 连接 → 回到对话 → 输入框开启连接器\x1b[0m`);
2485
+ return;
2486
+ }
2487
+
2488
+ if (target === 'hermes') {
2489
+ if (!existsSync(join(hermesPluginsDir(), 'src', 'mcp-server.mjs'))) {
2490
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2491
+ return;
2492
+ }
2493
+ console.log('[Hermes Agent]');
2494
+ await updateHermes();
2495
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2496
+ console.log(`\x1b[33mRestart Hermes Agent for changes to take effect.\x1b[0m`);
2497
+ return;
2498
+ }
2499
+
2500
+ if (target === 'openclaw') {
2501
+ if (!existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
2502
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2503
+ return;
2504
+ }
2505
+ console.log('[OpenClaw]');
2506
+ await updateCodexDesktop();
2507
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2508
+ console.log(`\x1b[33mRestart OpenClaw for changes to take effect.\x1b[0m`);
2138
2509
  return;
2139
2510
  }
2140
2511
 
@@ -2170,6 +2541,16 @@ async function cmdUpdate() {
2170
2541
  await updateOfficeAce();
2171
2542
  updatedAny = true;
2172
2543
  }
2544
+ if (existsSync(join(hermesPluginsDir(), 'src', 'mcp-server.mjs'))) {
2545
+ console.log('\n[Hermes Agent]');
2546
+ await updateHermes();
2547
+ updatedAny = true;
2548
+ }
2549
+ if (existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
2550
+ console.log('\n[OpenClaw]');
2551
+ await updateCodexDesktop();
2552
+ updatedAny = true;
2553
+ }
2173
2554
  if (codexStatus()) {
2174
2555
  console.log('\n[Codex]');
2175
2556
  installCodex();
@@ -2665,7 +3046,7 @@ async function main() {
2665
3046
  default:
2666
3047
  console.log(BANNER);
2667
3048
  console.log(
2668
- 'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|all>]\n',
3049
+ 'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|hermes|all>]\n',
2669
3050
  );
2670
3051
  console.log('Commands:');
2671
3052
  console.log(' install Install skills, MCP server, safety policy');
@@ -2679,7 +3060,9 @@ async function main() {
2679
3060
  console.log(' proxy Manage proxy config: init | show | clear');
2680
3061
  console.log(' help Show this help');
2681
3062
  console.log('\nOptions:');
2682
- console.log(' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, all');
3063
+ console.log(
3064
+ ' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, officeace, hermes, all',
3065
+ );
2683
3066
  console.log('\nExamples:');
2684
3067
  console.log(' npx huaweicloud-devkit install');
2685
3068
  console.log(' npx huaweicloud-devkit install --target codex');
@@ -2687,6 +3070,7 @@ async function main() {
2687
3070
  console.log(' npx huaweicloud-devkit install --target workbuddy');
2688
3071
  console.log(' npx huaweicloud-devkit install --target dsh');
2689
3072
  console.log(' npx huaweicloud-devkit install --target officeace');
3073
+ console.log(' npx huaweicloud-devkit install --target hermes');
2690
3074
  console.log(' npx huaweicloud-devkit install --target all');
2691
3075
  console.log(' npx huaweicloud-devkit auth init');
2692
3076
  console.log(' npx huaweicloud-devkit auth sync --target all');
@@ -5,6 +5,7 @@ import { readFileSync, readdirSync, existsSync, writeFileSync } from 'node:fs';
5
5
  import { join, dirname } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { homedir } from 'node:os';
8
+ import { spawnSync } from 'node:child_process';
8
9
  import { searchMarketplace } from './search-market.mjs';
9
10
  import { getServiceIcon } from './icon-library.mjs';
10
11
  import {
@@ -42,20 +43,52 @@ function dshSkillsDir() {
42
43
  const home = process.env.DSH_HOME || join(homedir(), '.dsh');
43
44
  return join(home, 'skills');
44
45
  }
46
+ function readOfficeaceRegistryInstallDir() {
47
+ if (process.platform !== 'win32') return null;
48
+ try {
49
+ const r = spawnSync('reg', ['query', 'HKCU\\SOFTWARE\\OfficeAce\\OfficeAce', '/v', 'InstallDir'], {
50
+ encoding: 'utf8',
51
+ windowsHide: true,
52
+ timeout: 5000,
53
+ });
54
+ if (r.status === 0) {
55
+ const m = r.stdout.match(/InstallDir\s+REG_SZ\s+(.+)/);
56
+ if (m) return m[1].trim();
57
+ }
58
+ } catch {}
59
+ return null;
60
+ }
61
+
45
62
  function officeaceSkillsRoot() {
46
- if (process.env.OFFICEACE_HOME) return join(process.env.OFFICEACE_HOME, 'skills');
47
- const dotDir = join(homedir(), '.office-claw');
48
- const dotCapFile = join(dotDir, 'capabilities.json');
49
- if (existsSync(dotCapFile)) return join(dotDir, 'skills');
63
+ const configRoot = process.env.OFFICE_CLAW_CONFIG_ROOT;
64
+ if (configRoot && existsSync(join(configRoot, 'capabilities.json'))) return join(configRoot, 'skills');
65
+ const regDir = readOfficeaceRegistryInstallDir();
66
+ if (regDir) {
67
+ const dir = join(regDir, '.office-claw', 'skills');
68
+ if (existsSync(dir)) return dir;
69
+ }
50
70
  if (process.platform === 'win32') {
51
- for (const base of [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files']) {
71
+ const bases = [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files'];
72
+ if (process.env.LOCALAPPDATA) bases.push(join(process.env.LOCALAPPDATA, 'Programs'));
73
+ for (const base of bases) {
52
74
  if (!base) continue;
53
- const dir = join(base, 'OfficeAce', '.office-claw');
54
- if (existsSync(join(dir, 'capabilities.json'))) return join(dir, 'skills');
75
+ const dir = join(base, 'OfficeAce', '.office-claw', 'skills');
76
+ if (existsSync(dir)) return dir;
55
77
  }
56
78
  }
57
- return join(dotDir, 'skills');
79
+ return null;
58
80
  }
81
+
82
+ function hermesSkillsDir() {
83
+ if (process.env.HERMES_HOME) return join(process.env.HERMES_HOME, 'skills');
84
+ // Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
85
+ if (process.platform === 'win32' && process.env.LOCALAPPDATA) {
86
+ return join(process.env.LOCALAPPDATA, 'hermes', 'skills');
87
+ }
88
+ const home = homedir();
89
+ return join(home, '.hermes', 'skills');
90
+ }
91
+
59
92
  export function listSkillDirs(root) {
60
93
  if (!existsSync(root)) return [];
61
94
  try {
@@ -83,6 +116,7 @@ function resolveSkillsRoot() {
83
116
  opencodeSkillsDir(),
84
117
  workbuddySkillsDir(),
85
118
  officeaceSkillsRoot(),
119
+ hermesSkillsDir(),
86
120
  ]) || SKILLS_ROOT_DEV
87
121
  );
88
122
  }
@@ -390,7 +424,7 @@ export const TOOL_DEFINITIONS = [
390
424
  target: {
391
425
  type: 'string',
392
426
  description:
393
- 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, or all (default).',
427
+ 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, or all (default).',
394
428
  },
395
429
  },
396
430
  },
@@ -405,7 +439,7 @@ export const TOOL_DEFINITIONS = [
405
439
  target: {
406
440
  type: 'string',
407
441
  description:
408
- 'Agent target to report after sync: opencode, codex, codex-desktop, codearts, workbuddy, dsh, or all (default).',
442
+ 'Agent target to report after sync: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, or all (default).',
409
443
  },
410
444
  },
411
445
  },
@@ -506,7 +540,7 @@ export const TOOL_DEFINITIONS = [
506
540
  type: 'boolean',
507
541
  description: 'Extract tar.gz on sandbox after upload (default: true)',
508
542
  },
509
- timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 120000)' },
543
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 300000)' },
510
544
  },
511
545
  },
512
546
  },
@@ -1,3 +1,12 @@
1
+ /*
2
+ * This file contains code derived from hwlink.
3
+ *
4
+ * Source:
5
+ * https://gitcode.com/huawei-developers/hwlink
6
+ *
7
+ * Licensed under the ISC License.
8
+ */
9
+
1
10
  import {
2
11
  DEFAULT_TIMEOUT_MS,
3
12
  WebSocketExecError,
@@ -1,3 +1,12 @@
1
+ /*
2
+ * This file contains code derived from hwlink.
3
+ *
4
+ * Source:
5
+ * https://gitcode.com/huawei-developers/hwlink
6
+ *
7
+ * Licensed under the ISC License.
8
+ */
9
+
1
10
  const DEFAULT_HIGH_WATERMARK = 8 * 1024 * 1024;
2
11
  const DEFAULT_LOW_WATERMARK = 2 * 1024 * 1024;
3
12
 
@@ -1,3 +1,12 @@
1
+ /*
2
+ * This file contains code derived from hwlink.
3
+ *
4
+ * Source:
5
+ * https://gitcode.com/huawei-developers/hwlink
6
+ *
7
+ * Licensed under the ISC License.
8
+ */
9
+
1
10
  import { Buffer } from 'node:buffer';
2
11
 
3
12
  import { FairQueue } from './hwlink-fair-queue.js';
@@ -1,3 +1,12 @@
1
+ /*
2
+ * This file contains code derived from hwlink.
3
+ *
4
+ * Source:
5
+ * https://gitcode.com/huawei-developers/hwlink
6
+ *
7
+ * Licensed under the ISC License.
8
+ */
9
+
1
10
  const FIXED_HEADER_LEN = 20;
2
11
  const MAX_SEND_CHUNK_SIZE = 8 * 1024;
3
12
 
@@ -1,3 +1,12 @@
1
+ /*
2
+ * This file contains code derived from hwlink.
3
+ *
4
+ * Source:
5
+ * https://gitcode.com/huawei-developers/hwlink
6
+ *
7
+ * Licensed under the ISC License.
8
+ */
9
+
1
10
  import {
2
11
  OpCode,
3
12
  ReserveSource,
@@ -1,3 +1,12 @@
1
+ /*
2
+ * This file contains code derived from hwlink.
3
+ *
4
+ * Source:
5
+ * https://gitcode.com/huawei-developers/hwlink
6
+ *
7
+ * Licensed under the ISC License.
8
+ */
9
+
1
10
  import { createServer } from 'node:net';
2
11
  import {
3
12
  OpCode,