huaweicloud-devkit 1.0.3-next.0 → 1.1.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/README.zh-CN.md +16 -1
- package/bin/setup.cjs +3 -2
- package/package.json +3 -1
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +11 -0
- package/plugins/huaweicloud-core/src/auth/service.mjs +1 -0
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +2 -1
- package/plugins/huaweicloud-core/src/icon-library.mjs +6 -1
- package/plugins/huaweicloud-core/src/mcp-server.mjs +1 -0
- package/plugins/huaweicloud-core/src/safety-policy.mjs +1 -0
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +1 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +10 -5
- package/plugins/huaweicloud-core/src/search-market.mjs +3 -3
- package/plugins/huaweicloud-core/src/setup-cli.mjs +232 -22
- package/plugins/huaweicloud-core/src/tools.mjs +23 -20
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +1 -1
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +1 -0
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +2 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
Help AI coding agents use Huawei Cloud safely and accurately — a single integration that gives agents cloud knowledge, CLI tooling, and safety guardrails.
|
|
10
10
|
|
|
11
|
-
Supports OpenCode, CodeArts Agent, WorkBuddy, DeepSeek Harness (DSH), OfficeAce, and
|
|
11
|
+
Supports OpenCode, CodeArts Agent, WorkBuddy, DeepSeek Harness (DSH), OfficeAce, OpenClaw, and AtomCode.
|
|
12
12
|
|
|
13
13
|
## Prerequisites
|
|
14
14
|
|
|
@@ -118,6 +118,21 @@ npx --yes huaweicloud-devkit uninstall --target openclaw
|
|
|
118
118
|
rm -rf ~/.npm/_npx/ # Linux/macOS only; Windows path TBD
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
### AtomCode
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npx --yes huaweicloud-devkit install --target atomcode
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Restart the AtomCode session** after installation.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx --yes huaweicloud-devkit doctor --target atomcode
|
|
131
|
+
npx --yes huaweicloud-devkit status --target atomcode
|
|
132
|
+
npx --yes huaweicloud-devkit update --target atomcode
|
|
133
|
+
npx --yes huaweicloud-devkit uninstall --target atomcode
|
|
134
|
+
```
|
|
135
|
+
|
|
121
136
|
### Other Agents
|
|
122
137
|
|
|
123
138
|
Any agent that supports MCP can use the standard config:
|
package/README.zh-CN.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
帮助 AI 编码助手安全、准确地使用华为云——一站式集成云知识、CLI 工具和安全护栏。
|
|
10
10
|
|
|
11
|
-
支持 OpenCode、码道(CodeArts Agent)、WorkBuddy、DeepSeek Harness(DSH)、OfficeAce、OpenClaw。
|
|
11
|
+
支持 OpenCode、码道(CodeArts Agent)、WorkBuddy、DeepSeek Harness(DSH)、OfficeAce、OpenClaw、AtomCode。
|
|
12
12
|
|
|
13
13
|
## 前置条件
|
|
14
14
|
|
|
@@ -118,6 +118,21 @@ npx --yes huaweicloud-devkit uninstall --target openclaw
|
|
|
118
118
|
rm -rf ~/.npm/_npx/ # 仅 Linux/macOS;Windows 路径待确认
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
### AtomCode
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npx --yes huaweicloud-devkit install --target atomcode
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
安装后**重启 AtomCode 会话**。
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx --yes huaweicloud-devkit doctor --target atomcode
|
|
131
|
+
npx --yes huaweicloud-devkit status --target atomcode
|
|
132
|
+
npx --yes huaweicloud-devkit update --target atomcode
|
|
133
|
+
npx --yes huaweicloud-devkit uninstall --target atomcode
|
|
134
|
+
```
|
|
135
|
+
|
|
121
136
|
### 其他 Agent
|
|
122
137
|
|
|
123
138
|
任何支持 MCP 协议的 Agent,直接使用标准 MCP 配置:
|
package/bin/setup.cjs
CHANGED
|
@@ -4,7 +4,8 @@ const path = require('node:path');
|
|
|
4
4
|
const fileUrl = require('node:url').pathToFileURL(
|
|
5
5
|
path.join(__dirname, '..', 'plugins', 'huaweicloud-core', 'src', 'setup-cli.mjs'),
|
|
6
6
|
).href;
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
|
|
8
|
+
import(fileUrl).catch((error) => {
|
|
9
|
+
console.error(error.message);
|
|
9
10
|
process.exit(1);
|
|
10
11
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0-next.0",
|
|
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": [
|
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
"@eslint/js": "^10.0.1",
|
|
37
37
|
"eslint": "^10.8.1",
|
|
38
38
|
"eslint-config-prettier": "^10.1.8",
|
|
39
|
+
"eslint-plugin-import-x": "^4.17.1",
|
|
39
40
|
"eslint-plugin-n": "^18.3.0",
|
|
41
|
+
"eslint-plugin-unicorn": "^73.0.0",
|
|
40
42
|
"globals": "^17.11.0",
|
|
41
43
|
"markdownlint-cli2": "^0.23.2",
|
|
42
44
|
"prettier": "^3.9.6"
|
|
@@ -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
|
|
20
|
+
"version": "1.1.0-next.0",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0-next.0",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -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
|
|
20
|
+
"version": "1.1.0-next.0",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0-next.0",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0-next.0",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"id": "huaweicloud-devkit",
|
|
4
4
|
"displayName": "HuaweiCloud DevKit",
|
|
5
|
-
"version": "1.0
|
|
5
|
+
"version": "1.1.0-next.0",
|
|
6
6
|
"family": "bundle-plugin",
|
|
7
7
|
"bundleFormat": "codex",
|
|
8
8
|
"description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
|
|
@@ -14,6 +14,7 @@ export const SUPPORTED_AGENT_TARGETS = [
|
|
|
14
14
|
'officeace',
|
|
15
15
|
'hermes',
|
|
16
16
|
'openclaw',
|
|
17
|
+
'atomcode',
|
|
17
18
|
];
|
|
18
19
|
|
|
19
20
|
function baseHome() {
|
|
@@ -169,6 +170,15 @@ function officeaceRegistered() {
|
|
|
169
170
|
return hasMcp || hasSkills;
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
function atomcodeHome() {
|
|
174
|
+
return process.env.ATOMCODE_HOME || join(baseHome(), '.atomcode');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function atomcodeRegistered() {
|
|
178
|
+
const cfg = readJsonSafe(join(atomcodeHome(), 'mcp.json'));
|
|
179
|
+
return Boolean(cfg?.mcpServers?.['huaweicloud-devkit']);
|
|
180
|
+
}
|
|
181
|
+
|
|
172
182
|
function hermesHome() {
|
|
173
183
|
if (process.env.HERMES_HOME) return process.env.HERMES_HOME;
|
|
174
184
|
// Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
|
|
@@ -202,6 +212,7 @@ export function getAgentRegistrationStatuses(target = 'all') {
|
|
|
202
212
|
if (agent === 'dsh') configured = dshRegistered();
|
|
203
213
|
if (agent === 'officeace') configured = officeaceRegistered();
|
|
204
214
|
if (agent === 'hermes') configured = hermesRegistered();
|
|
215
|
+
if (agent === 'atomcode') configured = atomcodeRegistered();
|
|
205
216
|
result.agents[agent] = { configured };
|
|
206
217
|
}
|
|
207
218
|
return result;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
|
+
|
|
3
4
|
import { getAgentRegistrationStatuses } from './agent-registration.mjs';
|
|
4
5
|
import { globalCredentialsPath, obsConfigPath, readGlobalCredentials, writeObsConfig } from './credentials.mjs';
|
|
5
6
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
2
3
|
import { classifyHcloudArgs, redactSecrets, assertAllowed } from './safety-policy.mjs';
|
|
3
4
|
import { getProxySettings } from './proxy/proxy-config.mjs';
|
|
4
5
|
|
|
@@ -9,7 +10,7 @@ const DEFAULT_MAX_RETRIES = 1;
|
|
|
9
10
|
export function planHcloudCommand(args, options = {}) {
|
|
10
11
|
const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
|
|
11
12
|
const classification = classifyHcloudArgs(normalizedArgs, options);
|
|
12
|
-
const command = ['hcloud', ...normalizedArgs].map(quoteShellArg).join(' ');
|
|
13
|
+
const command = ['hcloud', ...normalizedArgs].map((arg) => quoteShellArg(arg)).join(' ');
|
|
13
14
|
const warnings = planningWarnings(normalizedArgs);
|
|
14
15
|
const paramValidation = validateRequiredParams(normalizedArgs);
|
|
15
16
|
if (paramValidation.missing.length > 0) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
4
5
|
import { getProxyDispatcher } from './proxy/proxy-agent.mjs';
|
|
5
6
|
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -113,7 +114,11 @@ export async function getServiceIcon(service = '', category = '') {
|
|
|
113
114
|
};
|
|
114
115
|
}
|
|
115
116
|
const { manifest, source } = await loadManifest();
|
|
116
|
-
const tokens = query
|
|
117
|
+
const tokens = query
|
|
118
|
+
.replace(/[,;]/g, ' ')
|
|
119
|
+
.split(/\s+/)
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.map((t) => normalizeToken(t));
|
|
117
122
|
|
|
118
123
|
const index = { aliases: {}, tags: {}, categories: {}, descriptions: {} };
|
|
119
124
|
for (const icon of manifest.icons) {
|
|
@@ -3,6 +3,7 @@ import { stdin, stdout } from 'node:process';
|
|
|
3
3
|
import { rmSync, existsSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { resolve, dirname, join } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
6
7
|
import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
|
|
7
8
|
|
|
8
9
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
4
5
|
import { evaluateCommandRisk, mergeRiskDecision } from './risk-rule-engine.mjs';
|
|
5
6
|
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -6,6 +6,7 @@ import { createHash, randomBytes } from 'node:crypto';
|
|
|
6
6
|
import { join, dirname, basename } from 'node:path';
|
|
7
7
|
import { tmpdir } from 'node:os';
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
|
+
|
|
9
10
|
import { createConnection, getCredentials } from './hwlink-api.mjs';
|
|
10
11
|
import { getWebSocketImpl } from '../proxy/proxy-agent.mjs';
|
|
11
12
|
|
|
@@ -16,6 +17,10 @@ export const WS_EXEC_INDEX_URL = pathToFileURL(join(__dirname, '..', 'ws-exec',
|
|
|
16
17
|
|
|
17
18
|
let currentWorkspaceId = process.env.HW_WORKSPACE_ID || null;
|
|
18
19
|
|
|
20
|
+
function getCurrentWorkspaceId() {
|
|
21
|
+
return currentWorkspaceId;
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
function setWorkspaceId(id) {
|
|
20
25
|
currentWorkspaceId = id;
|
|
21
26
|
process.env.HW_WORKSPACE_ID = id;
|
|
@@ -366,7 +371,7 @@ async function uploadViaTunnel(localPort, archivePath, archiveSize, archiveRemot
|
|
|
366
371
|
}
|
|
367
372
|
try {
|
|
368
373
|
resolve(JSON.parse(body));
|
|
369
|
-
} catch (
|
|
374
|
+
} catch (error) {
|
|
370
375
|
reject(new Error(`invalid JSON response: ${body.slice(0, 200)}`));
|
|
371
376
|
}
|
|
372
377
|
});
|
|
@@ -387,7 +392,7 @@ async function uploadViaTunnel(localPort, archivePath, archiveSize, archiveRemot
|
|
|
387
392
|
}
|
|
388
393
|
try {
|
|
389
394
|
resolve(JSON.parse(body));
|
|
390
|
-
} catch (
|
|
395
|
+
} catch (error) {
|
|
391
396
|
reject(new Error(`invalid JSON response: ${body.slice(0, 200)}`));
|
|
392
397
|
}
|
|
393
398
|
});
|
|
@@ -431,8 +436,8 @@ async function waitForServerReady(localPort) {
|
|
|
431
436
|
return;
|
|
432
437
|
}
|
|
433
438
|
uploadLog(`waitForServerReady: health check returned non-200 (retry ${i + 1})`);
|
|
434
|
-
} catch (
|
|
435
|
-
uploadLog(`waitForServerReady: health check failed: ${
|
|
439
|
+
} catch (error) {
|
|
440
|
+
uploadLog(`waitForServerReady: health check failed: ${error.message} (retry ${i + 1})`);
|
|
436
441
|
}
|
|
437
442
|
await new Promise((r) => setTimeout(r, SERVER_HEALTH_INTERVAL_MS));
|
|
438
443
|
}
|
|
@@ -615,4 +620,4 @@ export async function closeAllSessions() {
|
|
|
615
620
|
}
|
|
616
621
|
}
|
|
617
622
|
|
|
618
|
-
export {
|
|
623
|
+
export { getCurrentWorkspaceId, setWorkspaceId, runNodeExec };
|
|
@@ -62,8 +62,8 @@ async function fetchJson(url, label = '') {
|
|
|
62
62
|
return JSON.parse(decoded);
|
|
63
63
|
}
|
|
64
64
|
return data;
|
|
65
|
-
} catch (
|
|
66
|
-
throw new Error(`Failed to fetch ${label}: ${
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new Error(`Failed to fetch ${label}: ${error.message}`, { cause: error });
|
|
67
67
|
} finally {
|
|
68
68
|
clearTimeout(timer);
|
|
69
69
|
}
|
|
@@ -100,7 +100,7 @@ function expandKeywords(rawKeyword, cnEnMap) {
|
|
|
100
100
|
if (en === lower) expanded.push(cn);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
const unique = [...new Set(expanded)].sort();
|
|
103
|
+
const unique = [...new Set(expanded)].sort((a, b) => a.localeCompare(b));
|
|
104
104
|
const specific = unique.filter((kw) => !isGeneric(kw));
|
|
105
105
|
const generic = unique.filter((kw) => isGeneric(kw));
|
|
106
106
|
return [specific, generic];
|
|
@@ -15,6 +15,7 @@ import { createInterface } from 'node:readline';
|
|
|
15
15
|
import { spawnSync } from 'node:child_process';
|
|
16
16
|
import { randomUUID } from 'node:crypto';
|
|
17
17
|
import Database from 'better-sqlite3';
|
|
18
|
+
|
|
18
19
|
import { getAuthStatus, syncAuth } from './auth/service.mjs';
|
|
19
20
|
import { SUPPORTED_AGENT_TARGETS } from './auth/agent-registration.mjs';
|
|
20
21
|
import {
|
|
@@ -112,6 +113,19 @@ function workbuddyPluginsDir() {
|
|
|
112
113
|
return join(homedir(), '.workbuddy', 'huaweicloud-plugins');
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
function atomcodeHome() {
|
|
117
|
+
return process.env.ATOMCODE_HOME || join(homedir(), '.atomcode');
|
|
118
|
+
}
|
|
119
|
+
function atomcodeSkillsDir() {
|
|
120
|
+
return join(atomcodeHome(), 'skills');
|
|
121
|
+
}
|
|
122
|
+
function atomcodeMcpConfigFile() {
|
|
123
|
+
return join(atomcodeHome(), 'mcp.json');
|
|
124
|
+
}
|
|
125
|
+
function atomcodePluginsDir() {
|
|
126
|
+
return join(atomcodeHome(), 'huaweicloud-plugins');
|
|
127
|
+
}
|
|
128
|
+
|
|
115
129
|
function dshRoot() {
|
|
116
130
|
return process.env.DSH_HOME || join(homedir(), '.dsh');
|
|
117
131
|
}
|
|
@@ -248,13 +262,13 @@ function ensureOfficeaceMcpInSqlite() {
|
|
|
248
262
|
}
|
|
249
263
|
db.close();
|
|
250
264
|
return true;
|
|
251
|
-
} catch (
|
|
265
|
+
} catch (error) {
|
|
252
266
|
if (db) {
|
|
253
267
|
try {
|
|
254
268
|
db.close();
|
|
255
269
|
} catch {}
|
|
256
270
|
}
|
|
257
|
-
console.log(` \x1b[31mFailed to write MCP config: ${
|
|
271
|
+
console.log(` \x1b[31mFailed to write MCP config: ${error.message}\x1b[0m`);
|
|
258
272
|
return false;
|
|
259
273
|
}
|
|
260
274
|
}
|
|
@@ -273,13 +287,13 @@ function removeOfficeaceMcpFromSqlite() {
|
|
|
273
287
|
console.log(` MCP config removed: ${dbPath}`);
|
|
274
288
|
}
|
|
275
289
|
db.close();
|
|
276
|
-
} catch (
|
|
290
|
+
} catch (error) {
|
|
277
291
|
if (db) {
|
|
278
292
|
try {
|
|
279
293
|
db.close();
|
|
280
294
|
} catch {}
|
|
281
295
|
}
|
|
282
|
-
console.log(` \x1b[31mFailed to remove MCP config: ${
|
|
296
|
+
console.log(` \x1b[31mFailed to remove MCP config: ${error.message}\x1b[0m`);
|
|
283
297
|
}
|
|
284
298
|
}
|
|
285
299
|
|
|
@@ -476,8 +490,8 @@ function removeIfExists(p) {
|
|
|
476
490
|
try {
|
|
477
491
|
rmSync(p, { recursive: true, force: true });
|
|
478
492
|
return true;
|
|
479
|
-
} catch (
|
|
480
|
-
console.log(` \x1b[33m[WARN]\x1b[0m Could not remove ${p}: ${
|
|
493
|
+
} catch (error) {
|
|
494
|
+
console.log(` \x1b[33m[WARN]\x1b[0m Could not remove ${p}: ${error.message}`);
|
|
481
495
|
return false;
|
|
482
496
|
}
|
|
483
497
|
}
|
|
@@ -1202,6 +1216,148 @@ function workbuddyStatus() {
|
|
|
1202
1216
|
}
|
|
1203
1217
|
}
|
|
1204
1218
|
|
|
1219
|
+
function ensureAtomcodeMcpConfig() {
|
|
1220
|
+
const configPath = atomcodeMcpConfigFile();
|
|
1221
|
+
const mcpPath = join(atomcodePluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
1222
|
+
const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
|
|
1223
|
+
const hcloudBin = findHcloudBin();
|
|
1224
|
+
if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
|
|
1225
|
+
let config = {};
|
|
1226
|
+
if (existsSync(configPath)) {
|
|
1227
|
+
try {
|
|
1228
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1229
|
+
} catch {
|
|
1230
|
+
console.log(
|
|
1231
|
+
` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
|
|
1232
|
+
);
|
|
1233
|
+
return false;
|
|
1234
|
+
}
|
|
1235
|
+
const existing = config.mcpServers?.['huaweicloud-devkit'];
|
|
1236
|
+
if (
|
|
1237
|
+
existing &&
|
|
1238
|
+
existing.command === 'node' &&
|
|
1239
|
+
Array.isArray(existing.args) &&
|
|
1240
|
+
existing.args[0] === mcpPath &&
|
|
1241
|
+
existing.timeout === 300000
|
|
1242
|
+
) {
|
|
1243
|
+
console.log(` MCP config unchanged: ${configPath}`);
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
config.mcpServers = config.mcpServers || {};
|
|
1248
|
+
config.mcpServers['huaweicloud-devkit'] = {
|
|
1249
|
+
command: 'node',
|
|
1250
|
+
args: [mcpPath],
|
|
1251
|
+
env,
|
|
1252
|
+
timeout: 300000,
|
|
1253
|
+
};
|
|
1254
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
1255
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1256
|
+
console.log(` MCP config updated: ${configPath}`);
|
|
1257
|
+
return true;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
async function installAtomCode() {
|
|
1261
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1262
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1263
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1264
|
+
const pluginDest = atomcodePluginsDir();
|
|
1265
|
+
|
|
1266
|
+
copyDir(skillsSrc, atomcodeSkillsDir());
|
|
1267
|
+
console.log(` Skills -> ${atomcodeSkillsDir()}`);
|
|
1268
|
+
|
|
1269
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1270
|
+
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1271
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1272
|
+
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1273
|
+
|
|
1274
|
+
ensureAtomcodeMcpConfig();
|
|
1275
|
+
installRuntimeDeps(pluginDest);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
1279
|
+
async function updateAtomCode() {
|
|
1280
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1281
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1282
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1283
|
+
const pluginDest = atomcodePluginsDir();
|
|
1284
|
+
|
|
1285
|
+
copyDir(skillsSrc, atomcodeSkillsDir());
|
|
1286
|
+
const stale = pruneStale(atomcodeSkillsDir(), skillsSrc);
|
|
1287
|
+
console.log(` Skills updated -> ${atomcodeSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
|
|
1288
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1289
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1290
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1291
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1292
|
+
ensureAtomcodeMcpConfig();
|
|
1293
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
1294
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1295
|
+
installRuntimeDeps(pluginDest);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function uninstallAtomCode() {
|
|
1299
|
+
const skillsDir = atomcodeSkillsDir();
|
|
1300
|
+
let removed = 0;
|
|
1301
|
+
if (existsSync(skillsDir)) {
|
|
1302
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
1303
|
+
if (entry.name.startsWith('huawei')) {
|
|
1304
|
+
removeIfExists(join(skillsDir, entry.name));
|
|
1305
|
+
removed++;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
if (removed > 0) console.log(` Removed ${removed} skills`);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (removeIfExists(atomcodePluginsDir())) {
|
|
1312
|
+
console.log(' Removed MCP server and safety policy');
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
const configPath = atomcodeMcpConfigFile();
|
|
1316
|
+
if (existsSync(configPath)) {
|
|
1317
|
+
let config = {};
|
|
1318
|
+
try {
|
|
1319
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1320
|
+
} catch {}
|
|
1321
|
+
if (config.mcpServers?.['huaweicloud-devkit']) {
|
|
1322
|
+
delete config.mcpServers['huaweicloud-devkit'];
|
|
1323
|
+
if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
|
|
1324
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1325
|
+
console.log(` MCP config cleaned: ${configPath}`);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
function atomcodeStatus() {
|
|
1331
|
+
const pluginDir = atomcodePluginsDir();
|
|
1332
|
+
const skillsDir = atomcodeSkillsDir();
|
|
1333
|
+
console.log(
|
|
1334
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1335
|
+
);
|
|
1336
|
+
console.log(
|
|
1337
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1338
|
+
);
|
|
1339
|
+
let skillCount = 0;
|
|
1340
|
+
if (existsSync(skillsDir)) {
|
|
1341
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1342
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1343
|
+
).length;
|
|
1344
|
+
}
|
|
1345
|
+
console.log(
|
|
1346
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1347
|
+
);
|
|
1348
|
+
const configPath = atomcodeMcpConfigFile();
|
|
1349
|
+
if (existsSync(configPath)) {
|
|
1350
|
+
try {
|
|
1351
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1352
|
+
console.log(
|
|
1353
|
+
` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
|
|
1354
|
+
);
|
|
1355
|
+
} catch {
|
|
1356
|
+
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1205
1361
|
function dshMcpServerPath() {
|
|
1206
1362
|
return join(dshPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
1207
1363
|
}
|
|
@@ -1694,9 +1850,6 @@ function removeHermesMcpConfigBlock() {
|
|
|
1694
1850
|
}
|
|
1695
1851
|
// If we just skipped the only server under mcp_servers, remove the key too
|
|
1696
1852
|
if (line.trim() === 'mcp_servers:') {
|
|
1697
|
-
// Peek ahead to check if this mcp_servers block only has huaweicloud-devkit
|
|
1698
|
-
const remaining = out.join('\n');
|
|
1699
|
-
// Remove trailing mcp_servers: if it's the last meaningful line
|
|
1700
1853
|
continue; // skip the mcp_servers key for now, re-add if other servers exist
|
|
1701
1854
|
}
|
|
1702
1855
|
out.push(line);
|
|
@@ -1848,6 +2001,7 @@ function autoDetectTarget() {
|
|
|
1848
2001
|
['officeace', () => existsSync(officeaceCapabilitiesDir())],
|
|
1849
2002
|
['hermes', () => existsSync(hermesHomeDir())],
|
|
1850
2003
|
['openclaw', () => existsSync(join(homedir(), '.openclaw'))],
|
|
2004
|
+
['atomcode', () => existsSync(atomcodeHome())],
|
|
1851
2005
|
];
|
|
1852
2006
|
const detected = checks.filter(([, check]) => check()).map(([name]) => name);
|
|
1853
2007
|
if (detected.length === 0) {
|
|
@@ -1908,6 +2062,10 @@ async function cmdInstall() {
|
|
|
1908
2062
|
console.log('\n[OpenClaw]');
|
|
1909
2063
|
await installCodexDesktop();
|
|
1910
2064
|
}
|
|
2065
|
+
if (target === 'atomcode' || target === 'all') {
|
|
2066
|
+
console.log('\n[AtomCode]');
|
|
2067
|
+
await installAtomCode();
|
|
2068
|
+
}
|
|
1911
2069
|
if (target === 'codex' || target === 'all') {
|
|
1912
2070
|
console.log('\n[Codex]');
|
|
1913
2071
|
if (!hasCodexCLI()) {
|
|
@@ -1949,7 +2107,9 @@ async function cmdInstall() {
|
|
|
1949
2107
|
? 'Hermes Agent'
|
|
1950
2108
|
: target === 'openclaw'
|
|
1951
2109
|
? 'OpenClaw'
|
|
1952
|
-
:
|
|
2110
|
+
: target === 'atomcode'
|
|
2111
|
+
? 'AtomCode'
|
|
2112
|
+
: '当前 agent';
|
|
1953
2113
|
const pad = ' '.repeat(24 - appName.length);
|
|
1954
2114
|
if (target === 'officeace') {
|
|
1955
2115
|
console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════════╗`);
|
|
@@ -2002,9 +2162,11 @@ async function cmdInstall() {
|
|
|
2002
2162
|
? officeacePluginsDir()
|
|
2003
2163
|
: target === 'openclaw'
|
|
2004
2164
|
? codexDesktopPluginsDir()
|
|
2005
|
-
: target === '
|
|
2006
|
-
?
|
|
2007
|
-
:
|
|
2165
|
+
: target === 'atomcode'
|
|
2166
|
+
? atomcodePluginsDir()
|
|
2167
|
+
: target === 'codex-desktop'
|
|
2168
|
+
? codexDesktopPluginsDir()
|
|
2169
|
+
: opencodePluginsDir();
|
|
2008
2170
|
mkdirSync(markerDir, { recursive: true });
|
|
2009
2171
|
writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
|
|
2010
2172
|
if (target === 'opencode' || target === 'all') {
|
|
@@ -2028,6 +2190,9 @@ async function cmdInstall() {
|
|
|
2028
2190
|
if (target === 'openclaw' || target === 'all') {
|
|
2029
2191
|
console.log('Or describe your Huawei Cloud task in OpenClaw');
|
|
2030
2192
|
}
|
|
2193
|
+
if (target === 'atomcode' || target === 'all') {
|
|
2194
|
+
console.log('Or describe your Huawei Cloud task in AtomCode');
|
|
2195
|
+
}
|
|
2031
2196
|
}
|
|
2032
2197
|
|
|
2033
2198
|
async function cmdUninstall() {
|
|
@@ -2063,6 +2228,10 @@ async function cmdUninstall() {
|
|
|
2063
2228
|
console.log('\n[OpenClaw]');
|
|
2064
2229
|
uninstallCodexDesktop();
|
|
2065
2230
|
}
|
|
2231
|
+
if (target === 'atomcode' || target === 'all') {
|
|
2232
|
+
console.log('\n[AtomCode]');
|
|
2233
|
+
uninstallAtomCode();
|
|
2234
|
+
}
|
|
2066
2235
|
if (target === 'codex-desktop' || target === 'codex' || target === 'all') {
|
|
2067
2236
|
console.log('\n[Codex]');
|
|
2068
2237
|
uninstallCodexDesktop();
|
|
@@ -2139,6 +2308,10 @@ async function cmdStatus() {
|
|
|
2139
2308
|
` Skills: ${cdSkillCount > 0 ? `\x1b[32m${cdSkillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
2140
2309
|
);
|
|
2141
2310
|
}
|
|
2311
|
+
if (target === 'atomcode' || target === 'all') {
|
|
2312
|
+
console.log('\n[AtomCode]');
|
|
2313
|
+
atomcodeStatus();
|
|
2314
|
+
}
|
|
2142
2315
|
if (target === 'codex' || target === 'all') {
|
|
2143
2316
|
console.log('\n[Codex]');
|
|
2144
2317
|
if (!hasCodexCLI()) {
|
|
@@ -2181,6 +2354,7 @@ async function cmdDoctor() {
|
|
|
2181
2354
|
const dshPluginDir = dshPluginsDir();
|
|
2182
2355
|
const officeacePluginDir = officeacePluginsDir();
|
|
2183
2356
|
const hermesPluginDir = hermesPluginsDir();
|
|
2357
|
+
const atomcodePluginDir = atomcodePluginsDir();
|
|
2184
2358
|
const mcpOk =
|
|
2185
2359
|
existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2186
2360
|
existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
@@ -2188,7 +2362,8 @@ async function cmdDoctor() {
|
|
|
2188
2362
|
existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2189
2363
|
existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2190
2364
|
existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2191
|
-
existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
|
|
2365
|
+
existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2366
|
+
existsSync(join(atomcodePluginDir, 'src', 'mcp-server.mjs'));
|
|
2192
2367
|
const mcpTarget = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs'))
|
|
2193
2368
|
? 'OpenCode'
|
|
2194
2369
|
: existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs'))
|
|
@@ -2203,7 +2378,9 @@ async function cmdDoctor() {
|
|
|
2203
2378
|
? 'OfficeAce'
|
|
2204
2379
|
: existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
|
|
2205
2380
|
? 'Hermes Agent'
|
|
2206
|
-
: ''
|
|
2381
|
+
: existsSync(join(atomcodePluginDir, 'src', 'mcp-server.mjs'))
|
|
2382
|
+
? 'AtomCode'
|
|
2383
|
+
: '';
|
|
2207
2384
|
check('MCP server installed', mcpOk, 'Run: npx huaweicloud-devkit install');
|
|
2208
2385
|
|
|
2209
2386
|
if (mcpOk) {
|
|
@@ -2229,7 +2406,8 @@ async function cmdDoctor() {
|
|
|
2229
2406
|
existsSync(join(workbuddyPluginDir, 'safety', 'policy.json')) ||
|
|
2230
2407
|
existsSync(join(dshPluginDir, 'safety', 'policy.json')) ||
|
|
2231
2408
|
existsSync(join(officeacePluginDir, 'safety', 'policy.json')) ||
|
|
2232
|
-
existsSync(join(hermesPluginDir, 'safety', 'policy.json'))
|
|
2409
|
+
existsSync(join(hermesPluginDir, 'safety', 'policy.json')) ||
|
|
2410
|
+
existsSync(join(atomcodePluginDir, 'safety', 'policy.json'));
|
|
2233
2411
|
check('Safety policy installed', safetyOk, 'Run: npx huaweicloud-devkit install');
|
|
2234
2412
|
|
|
2235
2413
|
// MCP config — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH
|
|
@@ -2306,6 +2484,18 @@ async function cmdDoctor() {
|
|
|
2306
2484
|
} catch {}
|
|
2307
2485
|
}
|
|
2308
2486
|
}
|
|
2487
|
+
if (!mcpConfigured) {
|
|
2488
|
+
const atomcodeCfg = atomcodeMcpConfigFile();
|
|
2489
|
+
if (existsSync(atomcodeCfg)) {
|
|
2490
|
+
try {
|
|
2491
|
+
const cfg = JSON.parse(readFileSync(atomcodeCfg, 'utf8'));
|
|
2492
|
+
if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
|
|
2493
|
+
mcpConfigured = true;
|
|
2494
|
+
mcpCfgTarget = 'AtomCode';
|
|
2495
|
+
}
|
|
2496
|
+
} catch {}
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2309
2499
|
check(
|
|
2310
2500
|
'MCP configured',
|
|
2311
2501
|
mcpConfigured,
|
|
@@ -2357,6 +2547,7 @@ async function cmdDoctor() {
|
|
|
2357
2547
|
dshSkillsDir(),
|
|
2358
2548
|
officeaceSkillsDir(),
|
|
2359
2549
|
hermesSkillsDir(),
|
|
2550
|
+
atomcodeSkillsDir(),
|
|
2360
2551
|
];
|
|
2361
2552
|
let skillCount = 0;
|
|
2362
2553
|
const missingSkills = [];
|
|
@@ -2413,6 +2604,7 @@ async function cmdDoctor() {
|
|
|
2413
2604
|
{ path: join(codeartsPluginsDir(), '.installed'), name: 'CodeArts' },
|
|
2414
2605
|
{ path: join(dshPluginsDir(), '.installed'), name: 'DSH' },
|
|
2415
2606
|
{ path: join(officeacePluginsDir(), '.installed'), name: 'OfficeAce' },
|
|
2607
|
+
{ path: join(atomcodePluginsDir(), '.installed'), name: 'AtomCode' },
|
|
2416
2608
|
];
|
|
2417
2609
|
for (const marker of installedMarkers) {
|
|
2418
2610
|
if (existsSync(marker.path)) {
|
|
@@ -2542,6 +2734,18 @@ async function cmdUpdate() {
|
|
|
2542
2734
|
return;
|
|
2543
2735
|
}
|
|
2544
2736
|
|
|
2737
|
+
if (target === 'atomcode') {
|
|
2738
|
+
if (!existsSync(join(atomcodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2739
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
console.log('[AtomCode]');
|
|
2743
|
+
await updateAtomCode();
|
|
2744
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
2745
|
+
console.log(`\x1b[33mRestart the AtomCode session for changes to take effect.\x1b[0m`);
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2545
2749
|
if (target === 'all') {
|
|
2546
2750
|
let updatedAny = false;
|
|
2547
2751
|
if (existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
@@ -2584,6 +2788,11 @@ async function cmdUpdate() {
|
|
|
2584
2788
|
await updateCodexDesktop();
|
|
2585
2789
|
updatedAny = true;
|
|
2586
2790
|
}
|
|
2791
|
+
if (existsSync(join(atomcodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2792
|
+
console.log('\n[AtomCode]');
|
|
2793
|
+
await updateAtomCode();
|
|
2794
|
+
updatedAny = true;
|
|
2795
|
+
}
|
|
2587
2796
|
if (codexStatus()) {
|
|
2588
2797
|
console.log('\n[Codex]');
|
|
2589
2798
|
installCodex();
|
|
@@ -2721,8 +2930,8 @@ async function cmdInstallHcloud() {
|
|
|
2721
2930
|
}
|
|
2722
2931
|
|
|
2723
2932
|
console.log(' Or restart terminal and: hcloud version');
|
|
2724
|
-
} catch (
|
|
2725
|
-
console.log(`\n\x1b[33mAuto-install failed: ${
|
|
2933
|
+
} catch (error) {
|
|
2934
|
+
console.log(`\n\x1b[33mAuto-install failed: ${error.message}\x1b[0m`);
|
|
2726
2935
|
console.log(` Manual: download ${url}, unzip to ${installDir}, add to PATH`);
|
|
2727
2936
|
console.log(` Guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_01.html`);
|
|
2728
2937
|
if (detectCodeartsSandbox() === 'sandbox') {
|
|
@@ -3079,7 +3288,7 @@ async function main() {
|
|
|
3079
3288
|
default:
|
|
3080
3289
|
console.log(BANNER);
|
|
3081
3290
|
console.log(
|
|
3082
|
-
'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|hermes|openclaw|all>]\n',
|
|
3291
|
+
'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|hermes|openclaw|atomcode|all>]\n',
|
|
3083
3292
|
);
|
|
3084
3293
|
console.log('Commands:');
|
|
3085
3294
|
console.log(' install Install skills, MCP server, safety policy');
|
|
@@ -3094,7 +3303,7 @@ async function main() {
|
|
|
3094
3303
|
console.log(' help Show this help');
|
|
3095
3304
|
console.log('\nOptions:');
|
|
3096
3305
|
console.log(
|
|
3097
|
-
' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, officeace, hermes, openclaw, all',
|
|
3306
|
+
' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, officeace, hermes, openclaw, atomcode, all',
|
|
3098
3307
|
);
|
|
3099
3308
|
console.log('\nExamples:');
|
|
3100
3309
|
console.log(' npx huaweicloud-devkit install');
|
|
@@ -3104,6 +3313,7 @@ async function main() {
|
|
|
3104
3313
|
console.log(' npx huaweicloud-devkit install --target dsh');
|
|
3105
3314
|
console.log(' npx huaweicloud-devkit install --target officeace');
|
|
3106
3315
|
console.log(' npx huaweicloud-devkit install --target hermes');
|
|
3316
|
+
console.log(' npx huaweicloud-devkit install --target atomcode');
|
|
3107
3317
|
console.log(' npx huaweicloud-devkit install --target all');
|
|
3108
3318
|
console.log(' npx huaweicloud-devkit auth init');
|
|
3109
3319
|
console.log(' npx huaweicloud-devkit auth sync --target all');
|
|
@@ -3114,7 +3324,7 @@ async function main() {
|
|
|
3114
3324
|
}
|
|
3115
3325
|
}
|
|
3116
3326
|
|
|
3117
|
-
main().catch((
|
|
3118
|
-
console.error(`\x1b[31mError: ${
|
|
3327
|
+
main().catch((error) => {
|
|
3328
|
+
console.error(`\x1b[31mError: ${error.message}\x1b[0m`);
|
|
3119
3329
|
process.exit(1);
|
|
3120
3330
|
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { planHcloudCommand, runHcloud } from './hcloud-cli.mjs';
|
|
2
|
-
import { classifyTextCommand, redactSecrets } from './safety-policy.mjs';
|
|
3
|
-
import { evaluateArtifacts, evaluateCommandRisk, evaluateDeployPlan } from './risk-rule-engine.mjs';
|
|
4
1
|
import { readFileSync, readdirSync, existsSync, writeFileSync } from 'node:fs';
|
|
5
2
|
import { join, dirname } from 'node:path';
|
|
6
3
|
import { fileURLToPath } from 'node:url';
|
|
7
4
|
import { homedir } from 'node:os';
|
|
8
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
import { evaluateArtifacts, evaluateCommandRisk, evaluateDeployPlan } from './risk-rule-engine.mjs';
|
|
8
|
+
import { classifyTextCommand, redactSecrets } from './safety-policy.mjs';
|
|
9
|
+
import { planHcloudCommand, runHcloud } from './hcloud-cli.mjs';
|
|
9
10
|
import { searchMarketplace } from './search-market.mjs';
|
|
10
11
|
import { getServiceIcon } from './icon-library.mjs';
|
|
11
12
|
import {
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
closeSession,
|
|
15
16
|
uploadFileWithSession,
|
|
16
17
|
uploadProjectWithSession,
|
|
17
|
-
|
|
18
|
+
getCurrentWorkspaceId,
|
|
18
19
|
setWorkspaceId,
|
|
19
20
|
} from './sandbox/session-manager.mjs';
|
|
20
21
|
import { hdkitCheckUser, hdkitSignAgreement, hdkitConnect, hdkitCredentials } from './sandbox/hdkitservice-api.mjs';
|
|
@@ -425,7 +426,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
425
426
|
target: {
|
|
426
427
|
type: 'string',
|
|
427
428
|
description:
|
|
428
|
-
'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, openclaw, or all (default).',
|
|
429
|
+
'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, openclaw, atomcode, or all (default).',
|
|
429
430
|
},
|
|
430
431
|
},
|
|
431
432
|
},
|
|
@@ -685,7 +686,7 @@ export async function callTool(name, args = {}) {
|
|
|
685
686
|
setRuntimeCredentials(args.ak, args.sk, undefined, args.region);
|
|
686
687
|
return { status: 'ok', message: 'Runtime credentials set for this MCP session.' };
|
|
687
688
|
case 'huaweicloud_sandbox_exec_with_session': {
|
|
688
|
-
const sandboxWsId2 = args.workspace_id ||
|
|
689
|
+
const sandboxWsId2 = args.workspace_id || getCurrentWorkspaceId();
|
|
689
690
|
if (!sandboxWsId2) {
|
|
690
691
|
throw new Error(
|
|
691
692
|
'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
|
|
@@ -698,7 +699,7 @@ export async function callTool(name, args = {}) {
|
|
|
698
699
|
return { stdout: sandboxResult2.stdout, exitCode: sandboxResult2.exitCode };
|
|
699
700
|
}
|
|
700
701
|
case 'huaweicloud_sandbox_exec_one_shot': {
|
|
701
|
-
const sandboxWsId3 = args.workspace_id ||
|
|
702
|
+
const sandboxWsId3 = args.workspace_id || getCurrentWorkspaceId();
|
|
702
703
|
if (!sandboxWsId3) {
|
|
703
704
|
throw new Error(
|
|
704
705
|
'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
|
|
@@ -711,7 +712,7 @@ export async function callTool(name, args = {}) {
|
|
|
711
712
|
return { stdout: sandboxResult3.stdout, exitCode: sandboxResult3.exitCode };
|
|
712
713
|
}
|
|
713
714
|
case 'huaweicloud_sandbox_close_session': {
|
|
714
|
-
const sandboxWsId4 = args.workspace_id ||
|
|
715
|
+
const sandboxWsId4 = args.workspace_id || getCurrentWorkspaceId();
|
|
715
716
|
if (!sandboxWsId4) {
|
|
716
717
|
throw new Error(
|
|
717
718
|
'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
|
|
@@ -726,7 +727,7 @@ export async function callTool(name, args = {}) {
|
|
|
726
727
|
if (!args.local_path || !args.remote_path) {
|
|
727
728
|
throw new Error('local_path and remote_path are required.');
|
|
728
729
|
}
|
|
729
|
-
const sandboxWsId5 = args.workspace_id ||
|
|
730
|
+
const sandboxWsId5 = args.workspace_id || getCurrentWorkspaceId();
|
|
730
731
|
if (!sandboxWsId5) {
|
|
731
732
|
throw new Error(
|
|
732
733
|
'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
|
|
@@ -747,7 +748,7 @@ export async function callTool(name, args = {}) {
|
|
|
747
748
|
if (!args.local_dir) {
|
|
748
749
|
throw new Error('local_dir is required.');
|
|
749
750
|
}
|
|
750
|
-
const sandboxWsId6 = args.workspace_id ||
|
|
751
|
+
const sandboxWsId6 = args.workspace_id || getCurrentWorkspaceId();
|
|
751
752
|
if (!sandboxWsId6) {
|
|
752
753
|
throw new Error(
|
|
753
754
|
'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
|
|
@@ -942,11 +943,11 @@ async function setupObsConfigFromHcloud(profile) {
|
|
|
942
943
|
|
|
943
944
|
try {
|
|
944
945
|
writeFileSync(obsConfigPath, configContent, { encoding: 'utf8', mode: 0o600 });
|
|
945
|
-
} catch (
|
|
946
|
+
} catch (error) {
|
|
946
947
|
return {
|
|
947
948
|
ok: false,
|
|
948
949
|
error: 'Failed to write OBS config file.',
|
|
949
|
-
detail:
|
|
950
|
+
detail: error.message,
|
|
950
951
|
path: obsConfigPath,
|
|
951
952
|
};
|
|
952
953
|
}
|
|
@@ -1132,10 +1133,10 @@ function serviceCatalog(intent = '') {
|
|
|
1132
1133
|
},
|
|
1133
1134
|
];
|
|
1134
1135
|
const matched = [];
|
|
1135
|
-
const tokens = it.split(/[\s,./-]+/).filter((t) => t.length > 0);
|
|
1136
|
+
const tokens = new Set(it.split(/[\s,./-]+/).filter((t) => t.length > 0));
|
|
1136
1137
|
const cjk = /[\u4e00-\u9fff]/;
|
|
1137
1138
|
for (const route of routeMap) {
|
|
1138
|
-
if (route.keywords.some((kw) => (kw.includes(' ') || cjk.test(kw) ? it.includes(kw) : tokens.
|
|
1139
|
+
if (route.keywords.some((kw) => (kw.includes(' ') || cjk.test(kw) ? it.includes(kw) : tokens.has(kw)))) {
|
|
1139
1140
|
matched.push(route);
|
|
1140
1141
|
}
|
|
1141
1142
|
}
|
|
@@ -1328,8 +1329,8 @@ async function searchDocs(query, topic = 'all') {
|
|
|
1328
1329
|
}
|
|
1329
1330
|
}
|
|
1330
1331
|
}
|
|
1331
|
-
} catch (
|
|
1332
|
-
return { ok: false, error:
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
return { ok: false, error: error.message, results: [] };
|
|
1333
1334
|
}
|
|
1334
1335
|
results.sort((a, b) => b.relevance - a.relevance);
|
|
1335
1336
|
return { ok: true, query: q, topic, count: results.length, results: results.slice(0, 10) };
|
|
@@ -1366,10 +1367,12 @@ async function retrieveSkill(name) {
|
|
|
1366
1367
|
}
|
|
1367
1368
|
|
|
1368
1369
|
async function listRegions() {
|
|
1369
|
-
const result = await runHcloud(['IAM', 'KeystoneListRegions'], { timeoutMs: 30000, maxRetries: 0 }).catch(
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1370
|
+
const result = await runHcloud(['IAM', 'KeystoneListRegions'], { timeoutMs: 30000, maxRetries: 0 }).catch(
|
|
1371
|
+
(error) => ({
|
|
1372
|
+
ok: false,
|
|
1373
|
+
error: error.message,
|
|
1374
|
+
}),
|
|
1375
|
+
);
|
|
1373
1376
|
if (!result.ok) {
|
|
1374
1377
|
return {
|
|
1375
1378
|
ok: false,
|
|
@@ -93,7 +93,7 @@ class HwlinkWebSocketMultiplexer {
|
|
|
93
93
|
const { WebSocketImpl = globalThis.WebSocket, protocol = 'devenv', onFrame, trace = false } = options;
|
|
94
94
|
|
|
95
95
|
if (typeof WebSocketImpl !== 'function') {
|
|
96
|
-
throw new
|
|
96
|
+
throw new TypeError('global WebSocket is unavailable; use Node.js 22+ or pass WebSocketImpl');
|
|
97
97
|
}
|
|
98
98
|
if (!Number.isInteger(source) || source < -0x80000000 || source > 0xffffffff) {
|
|
99
99
|
throw new Error('hwlink source must be an int32 or uint32 number');
|
|
@@ -287,10 +287,12 @@ class WebSocketShellSession {
|
|
|
287
287
|
const markerIndex = pending.buffer.lastIndexOf(doneMarker);
|
|
288
288
|
if (markerIndex === -1) return;
|
|
289
289
|
|
|
290
|
+
// eslint-disable-next-line unicorn/no-unsafe-buffer-conversion -- pending.buffer is a String, not a Buffer
|
|
290
291
|
const afterMarker = pending.buffer.slice(markerIndex + doneMarker.length);
|
|
291
292
|
const exitMatch = afterMarker.match(/^(\d+)/);
|
|
292
293
|
if (!exitMatch) return;
|
|
293
294
|
|
|
295
|
+
// eslint-disable-next-line unicorn/no-unsafe-buffer-conversion -- pending.buffer is a String, not a Buffer
|
|
294
296
|
const rawOutput = pending.buffer.slice(0, markerIndex);
|
|
295
297
|
const stdout = cleanCommandOutput(rawOutput, {
|
|
296
298
|
inputEchoed: this.inputEchoed,
|