huaweicloud-devkit 1.1.2-next.1 → 1.1.2-next.3
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 +24 -6
- package/README.zh-CN.md +24 -6
- package/integrations/hermes/skills-snapshot.json +175 -0
- 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/skills/huawei-apig/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +6 -7
- package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +7 -6
- package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/evs.md +3 -1
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +10 -10
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +2 -2
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/deploy-workflow.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +15 -9
- package/plugins/huaweicloud-core/skills/huawei-iac/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-iac/references/resource-catalog.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +3 -3
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +15 -15
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +1 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +18 -16
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +11 -0
- package/plugins/huaweicloud-core/src/koocli-version.mjs +47 -0
- package/plugins/huaweicloud-core/src/mcp-protocol.mjs +88 -0
- package/plugins/huaweicloud-core/src/mcp-server-remote.mjs +107 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +125 -183
- package/plugins/huaweicloud-core/src/setup-cli.mjs +42 -15
- package/plugins/huaweicloud-core/src/tools.mjs +17 -2
|
@@ -54,7 +54,7 @@ hcloud EIP AssociatePublicips --publicip_id=<id> \
|
|
|
54
54
|
--publicip.associate_instance_id=<port-id> --publicip.associate_instance_type=PORT
|
|
55
55
|
|
|
56
56
|
# Unbind
|
|
57
|
-
hcloud EIP
|
|
57
|
+
hcloud EIP DisassociatePublicips --publicip_id=<id>
|
|
58
58
|
|
|
59
59
|
# List
|
|
60
60
|
hcloud EIP ListPublicips
|
|
@@ -13,56 +13,58 @@ Use KooCLI `hcloud` for local inspection and reviewed operations. Never ask the
|
|
|
13
13
|
|
|
14
14
|
Official guide: `https://support.huaweicloud.com/qs-hcli/hcli_02_003.html`.
|
|
15
15
|
|
|
16
|
+
> **Version pairing**: this plugin is paired with **KooCLI v7.2.12** (`kooCliVersion` in `package.json`). Prefer the pinned download below. The one-liner script `hcloud_install.sh` always installs the `latest` version and cannot be pinned — if you use it, `check_cli`/`doctor` will warn when the installed version does not match v7.2.12.
|
|
17
|
+
|
|
16
18
|
### Windows
|
|
17
19
|
|
|
18
|
-
1. Download and unzip: `https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/
|
|
20
|
+
1. Download and unzip: `https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.2.12/huaweicloud-cli-windows-amd64.zip`
|
|
19
21
|
2. Extract to `%USERPROFILE%\hcloud`, add to user `PATH`
|
|
20
22
|
3. Verify: `hcloud version`
|
|
21
23
|
|
|
22
24
|
### Linux (amd64 / arm64)
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
curl -sSL https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Or manual download:
|
|
26
|
+
Fixed download (recommended):
|
|
31
27
|
|
|
32
28
|
```bash
|
|
33
29
|
# amd64
|
|
34
|
-
curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/
|
|
30
|
+
curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.2.12/huaweicloud-cli-linux-amd64.tar.gz"
|
|
35
31
|
tar -zxvf huaweicloud-cli-linux-amd64.tar.gz
|
|
36
32
|
# arm64
|
|
37
|
-
curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/
|
|
33
|
+
curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.2.12/huaweicloud-cli-linux-arm64.tar.gz"
|
|
38
34
|
tar -zxvf huaweicloud-cli-linux-arm64.tar.gz
|
|
39
35
|
```
|
|
40
36
|
|
|
41
37
|
Move to PATH: `mv $(pwd)/hcloud ~/.local/bin/`
|
|
42
38
|
Verify: `hcloud version`
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
One-liner (recommended):
|
|
40
|
+
One-liner (installs `latest`, may drift from v7.2.12):
|
|
47
41
|
|
|
48
42
|
```bash
|
|
49
43
|
curl -sSL https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y
|
|
50
44
|
```
|
|
51
45
|
|
|
52
|
-
|
|
46
|
+
### macOS (amd64 / arm64)
|
|
47
|
+
|
|
48
|
+
Fixed download (recommended):
|
|
53
49
|
|
|
54
50
|
```bash
|
|
55
51
|
# amd64
|
|
56
|
-
curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/
|
|
52
|
+
curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.2.12/huaweicloud-cli-mac-amd64.tar.gz"
|
|
57
53
|
tar -zxvf huaweicloud-cli-mac-amd64.tar.gz
|
|
58
54
|
# arm64 (Apple Silicon)
|
|
59
|
-
curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/
|
|
55
|
+
curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.2.12/huaweicloud-cli-mac-arm64.tar.gz"
|
|
60
56
|
tar -zxvf huaweicloud-cli-mac-arm64.tar.gz
|
|
61
57
|
```
|
|
62
58
|
|
|
63
59
|
Move to PATH: `mv $(pwd)/hcloud /usr/local/bin/`
|
|
64
60
|
Verify: `hcloud version`
|
|
65
61
|
|
|
62
|
+
One-liner (installs `latest`, may drift from v7.2.12):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
curl -sSL https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y
|
|
66
|
+
```
|
|
67
|
+
|
|
66
68
|
Agent processes find executables through `PATH`. If OpenCode/Codex cannot find `hcloud`, restart after updating `PATH`, or set `HCLOUD_BIN`.
|
|
67
69
|
|
|
68
70
|
## Configure Credentials Outside Chat
|
|
@@ -131,11 +131,22 @@ function applyPreflightFindings(classification, sgFindings) {
|
|
|
131
131
|
return classification;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
const OBS_WRITE_SUBCOMMANDS = new Set(['mb', 'cp', 'mv', 'rm', 'chattri', 'restore']);
|
|
135
|
+
|
|
136
|
+
function obsWriteHint(args) {
|
|
137
|
+
if (!Array.isArray(args) || args.length < 2) return null;
|
|
138
|
+
if (String(args[0]).toUpperCase() !== 'OBS') return null;
|
|
139
|
+
if (!OBS_WRITE_SUBCOMMANDS.has(String(args[1]).toLowerCase())) return null;
|
|
140
|
+
return 'OBS write operations are obsutil-style and always write-class. Before executing, present the full resource manifest (bucket/object list) to the user for ONE batch approval, then run each command through plan → approve (see huawei-iac skill, Provisioning Rules).';
|
|
141
|
+
}
|
|
142
|
+
|
|
134
143
|
export function planHcloudCommand(args, options = {}) {
|
|
135
144
|
const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
|
|
136
145
|
const classification = classifyHcloudArgs(normalizedArgs, options);
|
|
137
146
|
const command = ['hcloud', ...normalizedArgs].map((arg) => quoteShellArg(arg)).join(' ');
|
|
138
147
|
const warnings = planningWarnings(normalizedArgs);
|
|
148
|
+
const obsHint = obsWriteHint(normalizedArgs);
|
|
149
|
+
if (obsHint) warnings.push(obsHint);
|
|
139
150
|
const sgFindings = preflightSecurityGroupCheck(normalizedArgs);
|
|
140
151
|
if (sgFindings.length > 0) {
|
|
141
152
|
for (const f of sgFindings) warnings.push(f);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const PACKAGE_JSON = resolve(__dirname, '..', '..', '..', 'package.json');
|
|
7
|
+
|
|
8
|
+
export const KOO_CLI_BASE = 'https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli';
|
|
9
|
+
|
|
10
|
+
const VERSION_RE = /\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/;
|
|
11
|
+
|
|
12
|
+
export function getKooCliVersion() {
|
|
13
|
+
try {
|
|
14
|
+
const pkg = JSON.parse(readFileSync(PACKAGE_JSON, 'utf8'));
|
|
15
|
+
return pkg.kooCliVersion || null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function parseHcloudVersion(out) {
|
|
22
|
+
if (!out) return null;
|
|
23
|
+
const match = String(out).match(VERSION_RE);
|
|
24
|
+
return match ? match[0] : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function compareVersion(a, b) {
|
|
28
|
+
const pa = String(a || '')
|
|
29
|
+
.split(/[-+]/)[0]
|
|
30
|
+
.split('.')
|
|
31
|
+
.map((n) => Number.parseInt(n, 10) || 0);
|
|
32
|
+
const pb = String(b || '')
|
|
33
|
+
.split(/[-+]/)[0]
|
|
34
|
+
.split('.')
|
|
35
|
+
.map((n) => Number.parseInt(n, 10) || 0);
|
|
36
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
|
|
37
|
+
const va = pa[i] || 0;
|
|
38
|
+
const vb = pb[i] || 0;
|
|
39
|
+
if (va > vb) return 1;
|
|
40
|
+
if (va < vb) return -1;
|
|
41
|
+
}
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function kooCliDownloadBase() {
|
|
46
|
+
return `${KOO_CLI_BASE}/${getKooCliVersion() || 'latest'}`;
|
|
47
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve, dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
|
|
6
|
+
import { peekCachedUpdateInfo, applyUpdateHint } from './update-check.mjs';
|
|
7
|
+
import { initTelemetry } from './telemetry/telemetry.mjs';
|
|
8
|
+
import { detectAgent } from './telemetry/agent-detect.mjs';
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const pluginRoot = resolve(__dirname, '..');
|
|
12
|
+
const packageRoot = resolve(pluginRoot, '..', '..');
|
|
13
|
+
let pkgVersion = '0.0.0';
|
|
14
|
+
for (const base of [pluginRoot, packageRoot]) {
|
|
15
|
+
try {
|
|
16
|
+
const version = JSON.parse(readFileSync(join(base, 'package.json'), 'utf8')).version;
|
|
17
|
+
if (version) {
|
|
18
|
+
pkgVersion = version;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 会话内首个非 check/upgrade 工具调用附加 _updateInfo,只消费一次。
|
|
25
|
+
let hintConsumed = false;
|
|
26
|
+
function decorateResult(name, result) {
|
|
27
|
+
if (hintConsumed) return result;
|
|
28
|
+
try {
|
|
29
|
+
const hint = peekCachedUpdateInfo();
|
|
30
|
+
if (!hint) return result;
|
|
31
|
+
const decorated = applyUpdateHint(result, name, hint);
|
|
32
|
+
if (decorated !== result) hintConsumed = true;
|
|
33
|
+
return decorated;
|
|
34
|
+
} catch {
|
|
35
|
+
return result; // 兜底装饰失败绝不影响工具调用
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function dispatch(method, params) {
|
|
40
|
+
if (method === 'initialize') {
|
|
41
|
+
const ci = params.clientInfo || {};
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const { hdkitGenerateUserHash } = await import('./sandbox/hdkitservice-api.mjs');
|
|
45
|
+
await Promise.race([
|
|
46
|
+
hdkitGenerateUserHash(),
|
|
47
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 3000)),
|
|
48
|
+
]);
|
|
49
|
+
} catch {}
|
|
50
|
+
|
|
51
|
+
const agent = detectAgent(ci);
|
|
52
|
+
initTelemetry({ harness: agent.harness, version: agent.version });
|
|
53
|
+
return {
|
|
54
|
+
protocolVersion: params.protocolVersion || '2024-11-05',
|
|
55
|
+
capabilities: {
|
|
56
|
+
tools: {},
|
|
57
|
+
},
|
|
58
|
+
serverInfo: {
|
|
59
|
+
name: 'huaweicloud-devkit',
|
|
60
|
+
version: pkgVersion,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (method === 'tools/list') {
|
|
66
|
+
return { tools: TOOL_DEFINITIONS };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (method === 'tools/call') {
|
|
70
|
+
const result = await callTool(params.name, params.arguments || {});
|
|
71
|
+
const decorated = decorateResult(params.name, result);
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: 'text',
|
|
76
|
+
text: JSON.stringify(decorated, null, 2),
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
isError: false,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (method === 'resources/list') {
|
|
84
|
+
return { resources: [] };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw new Error(`Unsupported method: ${method}`);
|
|
88
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { format } from 'node:util';
|
|
5
|
+
|
|
6
|
+
import { dispatch } from './mcp-protocol.mjs';
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_PORT = 9528;
|
|
9
|
+
export const DEFAULT_HOST = '127.0.0.1';
|
|
10
|
+
|
|
11
|
+
export async function startRemoteServer({ port = DEFAULT_PORT, host = DEFAULT_HOST } = {}) {
|
|
12
|
+
const server = createServer(async (req, res) => {
|
|
13
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
14
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
15
|
+
res.setHeader(
|
|
16
|
+
'Access-Control-Allow-Headers',
|
|
17
|
+
'Content-Type, MCP-Protocol-Version, Mcp-Session-Id, Accept, Authorization',
|
|
18
|
+
);
|
|
19
|
+
res.setHeader('Access-Control-Expose-Headers', 'MCP-Protocol-Version, Mcp-Session-Id');
|
|
20
|
+
|
|
21
|
+
if (req.method === 'OPTIONS') {
|
|
22
|
+
res.writeHead(204);
|
|
23
|
+
res.end();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (req.method !== 'POST') {
|
|
28
|
+
res.writeHead(405, { Allow: 'POST, OPTIONS' });
|
|
29
|
+
res.end();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let message;
|
|
34
|
+
try {
|
|
35
|
+
const chunks = [];
|
|
36
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
37
|
+
message = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
38
|
+
} catch {
|
|
39
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
40
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } }));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!Object.hasOwn(message, 'id')) {
|
|
45
|
+
// 通知类消息(含 notifications/initialized)无需响应体,HTTP 层直接 202。
|
|
46
|
+
res.writeHead(202);
|
|
47
|
+
res.end();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let response;
|
|
52
|
+
try {
|
|
53
|
+
const result = await dispatch(message.method, message.params || {});
|
|
54
|
+
response = { jsonrpc: '2.0', id: message.id, result };
|
|
55
|
+
if (message.method === 'initialize') {
|
|
56
|
+
res.setHeader('MCP-Protocol-Version', result.protocolVersion || '2024-11-05');
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
response = {
|
|
60
|
+
jsonrpc: '2.0',
|
|
61
|
+
id: message.id,
|
|
62
|
+
error: { code: -32603, message: error.message },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
writeMCPResponse(res, response, req.headers.accept || '');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
70
|
+
server.once('error', rejectPromise);
|
|
71
|
+
server.listen(port, host, () => resolvePromise());
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const address = server.address();
|
|
75
|
+
process.stdout.write(format('huaweicloud-devkit MCP server (remote) listening on %s:%s\n', host, address.port));
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
server,
|
|
79
|
+
port: address.port,
|
|
80
|
+
close: () => new Promise((resolvePromise) => server.close(resolvePromise)),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function writeMCPResponse(res, response, accept) {
|
|
85
|
+
const json = JSON.stringify(response);
|
|
86
|
+
if (accept.includes('application/json')) {
|
|
87
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
88
|
+
res.end(json);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// 客户端只接受 SSE 时的兜底:单帧 event 后关闭流。
|
|
92
|
+
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' });
|
|
93
|
+
res.write(`event: message\ndata: ${json}\n\n`);
|
|
94
|
+
res.end();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
|
|
98
|
+
const portIdx = process.argv.indexOf('--port');
|
|
99
|
+
const port = portIdx > -1 ? Number(process.argv[portIdx + 1]) : DEFAULT_PORT;
|
|
100
|
+
const hostIdx = process.argv.indexOf('--host');
|
|
101
|
+
const host = hostIdx > -1 && process.argv[hostIdx + 1] ? process.argv[hostIdx + 1] : DEFAULT_HOST;
|
|
102
|
+
startRemoteServer({ port, host }).catch((error) => {
|
|
103
|
+
process.stderr.write(`Failed to start MCP remote server: ${error.message}\n`);
|
|
104
|
+
// eslint-disable-next-line n/no-process-exit -- fatal startup error in standalone CLI mode
|
|
105
|
+
process.exit(1);
|
|
106
|
+
});
|
|
107
|
+
}
|