huaweicloud-devkit 1.1.2-next.1 → 1.1.2-next.2

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
@@ -11,12 +11,6 @@ Help AI coding agents use Huawei Cloud safely and accurately — a single integr
11
11
 
12
12
  Supports OpenCode, Codex, CodeArts Agent, WorkBuddy, DeepSeek Harness (DSH), OfficeAce, Hermes, OpenClaw, and AtomCode.
13
13
 
14
- ## Cursor Directory Plugin
15
-
16
- This repository also acts as an [Open Plugins](https://open-plugins.com) package for the [Cursor Directory](https://cursor.directory) marketplace. The root-level `plugin.json`, `mcp.json`, `skills/`, and `rules/` files are used **only** for Cursor Directory repository-scan discovery and are **not** shipped in the npm tarball (the `package.json` `files` whitelist excludes them). The npm package's manifests live under `plugins/huaweicloud-core/`.
17
-
18
- The `mcp.json` MCP server launches via `npx -y -p huaweicloud-devkit huaweicloud-devkit-mcp`, which tracks the npm `latest` tag. During pre-release this may differ from the manifest `version`; it can be pinned after a stable release.
19
-
20
14
  ## Prerequisites
21
15
 
22
16
  - Node.js >= 22
@@ -226,6 +220,30 @@ No installation required — `npx` handles everything.
226
220
 
227
221
  > Set `HW_ACCESS_KEY`/`HW_SECRET_KEY` in the MCP config `env` field for project-level credentials.
228
222
 
223
+ #### Connecting over Remote (HTTP)
224
+
225
+ If your agent supports `type: "remote"` (Streamable HTTP) instead of stdio, start the devkit remote MCP server locally first:
226
+
227
+ ```bash
228
+ npx --yes huaweicloud-devkit-mcp --transport remote
229
+ ```
230
+
231
+ It listens on `127.0.0.1:9528` by default (no conflict with the IACMCPServer port 9527). Then connect with a remote config (opencode example):
232
+
233
+ ```jsonc
234
+ {
235
+ "mcp": {
236
+ "huaweicloud-devkit": {
237
+ "type": "remote",
238
+ "url": "http://localhost:9528",
239
+ "enabled": true,
240
+ },
241
+ },
242
+ }
243
+ ```
244
+
245
+ > Use `--port <port>` if 9528 is taken and update `url` accordingly; add `--host 0.0.0.0` for LAN access. The remote server has no built-in auth — do not expose it anonymously to the public internet.
246
+
229
247
  ### Install KooCLI
230
248
 
231
249
  ```bash
package/README.zh-CN.md CHANGED
@@ -11,12 +11,6 @@
11
11
 
12
12
  支持 OpenCode、Codex、码道(CodeArts Agent)、WorkBuddy、DeepSeek Harness(DSH)、OfficeAce、Hermes、OpenClaw、AtomCode。
13
13
 
14
- ## Cursor Directory 插件
15
-
16
- 本仓库同时作为 [Open Plugins](https://open-plugins.com) 包挂载到 [Cursor Directory](https://cursor.directory) 市场。根目录的 `plugin.json`、`mcp.json`、`skills/`、`rules/` 仅用于 Cursor Directory 的仓库扫描发现,**不会**打入 npm 包(`package.json` 的 `files` 白名单未包含它们)。npm 包的 manifests 位于 `plugins/huaweicloud-core/`。
17
-
18
- `mcp.json` 中的 MCP 服务通过 `npx -y -p huaweicloud-devkit huaweicloud-devkit-mcp` 启动,跟踪的是 npm 的 `latest` tag。预发布阶段可能与 manifest 中的 `version` 不一致,稳定发版后可以固定版本。
19
-
20
14
  ## 前置条件
21
15
 
22
16
  - Node.js >= 22
@@ -226,6 +220,30 @@ npx --yes huaweicloud-devkit uninstall --target atomcode
226
220
 
227
221
  > 项目级 AK/SK 可通过 MCP 配置的 `env` 字段设置 `HW_ACCESS_KEY`/`HW_SECRET_KEY`。
228
222
 
223
+ #### 通过 Remote(HTTP)连接
224
+
225
+ 若 Agent 支持 `type: "remote"`(Streamable HTTP)而非 stdio,可在本地先启动 devkit 的远程 MCP 服务器:
226
+
227
+ ```bash
228
+ npx --yes huaweicloud-devkit-mcp --transport remote
229
+ ```
230
+
231
+ 默认监听 `127.0.0.1:9528`(与预置的 IACMCPServer 端口 9527 不冲突)。随后以远程方式连接(以 opencode 为例):
232
+
233
+ ```jsonc
234
+ {
235
+ "mcp": {
236
+ "huaweicloud-devkit": {
237
+ "type": "remote",
238
+ "url": "http://localhost:9528",
239
+ "enabled": true,
240
+ },
241
+ },
242
+ }
243
+ ```
244
+
245
+ > 端口被占用时用 `--port <端口>` 换端口,`url` 同步修改;需要局域网访问时加 `--host 0.0.0.0`。remote 服务器不内置鉴权,请勿匿名暴露到公网。
246
+
229
247
  ### 安装 KooCLI
230
248
 
231
249
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.2-next.1",
3
+ "version": "1.1.2-next.2",
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": [
@@ -17,7 +17,7 @@
17
17
  "mcpServers": "./.mcp.json",
18
18
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
19
19
  "skills": "./skills/",
20
- "version": "1.1.2-next.1",
20
+ "version": "1.1.2-next.2",
21
21
  "author": {
22
22
  "name": "HuaweiCloud Mate",
23
23
  "url": "https://github.com/huaweicloud"
@@ -20,7 +20,7 @@
20
20
  "mcpServers": "./.mcp.json",
21
21
  "description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
22
22
  "skills": "./skills/",
23
- "version": "1.1.2-next.1",
23
+ "version": "1.1.2-next.2",
24
24
  "author": {
25
25
  "name": "HuaweiCloud Mate",
26
26
  "url": "https://github.com/huaweicloud"
@@ -17,7 +17,7 @@
17
17
  "mcpServers": "./.mcp.json",
18
18
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
19
19
  "skills": "./skills/",
20
- "version": "1.1.2-next.1",
20
+ "version": "1.1.2-next.2",
21
21
  "author": {
22
22
  "name": "HuaweiCloud Mate",
23
23
  "url": "https://github.com/huaweicloud"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.2-next.1",
3
+ "version": "1.1.2-next.2",
4
4
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
5
5
  "author": {
6
6
  "name": "HuaweiCloud Mate",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.2-next.1",
3
+ "version": "1.1.2-next.2",
4
4
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
5
5
  "author": {
6
6
  "name": "HuaweiCloud Mate",
@@ -2,7 +2,7 @@
2
2
  "name": "huaweicloud-devkit",
3
3
  "id": "huaweicloud-devkit",
4
4
  "displayName": "HuaweiCloud DevKit",
5
- "version": "1.1.2-next.1",
5
+ "version": "1.1.2-next.2",
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.",
@@ -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
+ }
@@ -1,15 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import { stdin, stdout } from 'node:process';
3
- import { rmSync, existsSync, readFileSync } from 'node:fs';
4
- import { resolve, dirname, join } from 'node:path';
3
+ import { rmSync, existsSync } from 'node:fs';
4
+ import { resolve, dirname } from 'node:path';
5
5
  import { platform } from 'node:os';
6
6
  import { fileURLToPath } from 'node:url';
7
7
 
8
- import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
9
- import { getCachedUpdateInfo, readInstalledVersion, peekCachedUpdateInfo, applyUpdateHint } from './update-check.mjs';
10
- import { initTelemetry } from './telemetry/telemetry.mjs';
8
+ import { dispatch } from './mcp-protocol.mjs';
9
+ import { DEFAULT_PORT, DEFAULT_HOST } from './mcp-server-remote.mjs';
10
+ import { getCachedUpdateInfo, readInstalledVersion } from './update-check.mjs';
11
11
  import { detectAgent } from './telemetry/agent-detect.mjs';
12
12
 
13
+ const transportIdx = process.argv.indexOf('--transport');
14
+ const transport = transportIdx > -1 && process.argv[transportIdx + 1] ? process.argv[transportIdx + 1] : 'stdio';
15
+ const portIdx = process.argv.indexOf('--port');
16
+ const remotePort = portIdx > -1 ? Number(process.argv[portIdx + 1]) : DEFAULT_PORT;
17
+ const hostIdx = process.argv.indexOf('--host');
18
+ const remoteHost = hostIdx > -1 && process.argv[hostIdx + 1] ? process.argv[hostIdx + 1] : DEFAULT_HOST;
19
+
13
20
  const projectDirIdx = process.argv.indexOf('--codearts-project-dir');
14
21
  if (projectDirIdx > -1 && process.argv[projectDirIdx + 1]) {
15
22
  process.env.CODEARTS_PROJECT_DIR = process.argv[projectDirIdx + 1];
@@ -46,197 +53,132 @@ try {
46
53
  if (existsSync(marker)) rmSync(marker, { force: true });
47
54
  } catch {}
48
55
 
49
- const __dirname = dirname(fileURLToPath(import.meta.url));
50
- const pluginRoot = resolve(__dirname, '..');
51
- const packageRoot = resolve(pluginRoot, '..', '..');
52
- let pkgVersion = '0.0.0';
53
- for (const base of [pluginRoot, packageRoot]) {
54
- try {
55
- const version = JSON.parse(readFileSync(join(base, 'package.json'), 'utf8')).version;
56
- if (version) {
57
- pkgVersion = version;
58
- break;
59
- }
60
- } catch {}
61
- }
62
-
63
- let buffer = Buffer.alloc(0);
64
- let useContentLengthFraming = true;
65
-
66
- // Keep the event loop alive after stdin is closed (Windows Hermes workaround).
67
- // Node.js exits when no active handles remain; the stdin 'data' listener is
68
- // the only handle. On Windows, Hermes may close the stdin pipe after the
69
- // initial handshake, causing the process to exit silently (exit 0).
70
- //
71
- // For Hermes on Windows: start a keepalive timer on stdin close, and only exit
72
- // when stdout also closes.
73
- // For all other agents (OfficeAce, WorkBuddy, etc.): stdin close is the
74
- // shutdown signal — exit cleanly so the host does not see CLOSE_TIMEOUT.
75
- const { harness } = detectAgent();
76
- const NEEDS_KEEPALIVE = harness === 'hermes' && platform() === 'win32';
77
-
78
- // 版本升级检测预热:异步、非阻塞;失败静默(离线/超时不影响会话)。
79
- process.nextTick(() => {
80
- getCachedUpdateInfo(readInstalledVersion() || '0.0.0').catch(() => {});
81
- });
82
-
83
- // 会话内首个非 check/upgrade 工具调用附加 _updateInfo,只消费一次。
84
- let hintConsumed = false;
85
- function decorateResult(name, result) {
86
- if (hintConsumed) return result;
87
- try {
88
- const hint = peekCachedUpdateInfo();
89
- if (!hint) return result;
90
- const decorated = applyUpdateHint(result, name, hint);
91
- if (decorated !== result) hintConsumed = true;
92
- return decorated;
93
- } catch {
94
- return result; // 兜底装饰失败绝不影响工具调用
95
- }
56
+ if (transport === 'remote') {
57
+ const { startRemoteServer } = await import('./mcp-server-remote.mjs');
58
+ startRemoteServer({ port: remotePort, host: remoteHost }).catch((error) => {
59
+ process.stderr.write(`Failed to start MCP remote server: ${error.message}\n`);
60
+ // eslint-disable-next-line n/no-process-exit -- fatal startup error; no active session to keep alive
61
+ process.exit(1);
62
+ });
63
+ } else {
64
+ runStdioServer();
96
65
  }
97
66
 
98
- let keepAlive = null;
99
- function onStdinClose() {
100
- if (keepAlive) return;
101
- if (NEEDS_KEEPALIVE) {
102
- keepAlive = setInterval(() => {}, 60000);
103
- } else {
104
- // eslint-disable-next-line n/no-process-exit -- stdin close is the shutdown signal; exit now without waiting for stdout
105
- process.exit(0);
106
- }
107
- }
108
- function onStdoutClose() {
109
- if (keepAlive) {
110
- clearInterval(keepAlive);
111
- keepAlive = null;
67
+ function runStdioServer() {
68
+ const updatePrewarm = () => {
69
+ getCachedUpdateInfo(readInstalledVersion() || '0.0.0').catch(() => {});
70
+ };
71
+
72
+ let buffer = Buffer.alloc(0);
73
+ let useContentLengthFraming = true;
74
+
75
+ // Keep the event loop alive after stdin is closed (Windows Hermes workaround).
76
+ // Node.js exits when no active handles remain; the stdin 'data' listener is
77
+ // the only handle. On Windows, Hermes may close the stdin pipe after the
78
+ // initial handshake, causing the process to exit silently (exit 0).
79
+ //
80
+ // For Hermes on Windows: start a keepalive timer on stdin close, and only exit
81
+ // when stdout also closes.
82
+ // For all other agents (OfficeAce, WorkBuddy, etc.): stdin close is the
83
+ // shutdown signal — exit cleanly so the host does not see CLOSE_TIMEOUT.
84
+ const { harness } = detectAgent();
85
+ const NEEDS_KEEPALIVE = harness === 'hermes' && platform() === 'win32';
86
+
87
+ // 版本升级检测预热:异步、非阻塞;失败静默(离线/超时不影响会话)。
88
+ process.nextTick(updatePrewarm);
89
+
90
+ let keepAlive = null;
91
+ function onStdinClose() {
92
+ if (keepAlive) return;
93
+ if (NEEDS_KEEPALIVE) {
94
+ keepAlive = setInterval(() => {}, 60000);
95
+ } else {
96
+ // eslint-disable-next-line n/no-process-exit -- stdin close is the shutdown signal; exit now without waiting for stdout
97
+ process.exit(0);
98
+ }
112
99
  }
113
- process.exitCode = 0;
114
- }
115
- stdin.on('close', onStdinClose);
116
- stdin.on('end', onStdinClose);
117
- stdout.on('close', onStdoutClose);
118
-
119
- stdin.on('data', (chunk) => {
120
- buffer = Buffer.concat([buffer, chunk]);
121
- readFrames();
122
- });
123
-
124
- function readFrames() {
125
- while (true) {
126
- const headerEnd = buffer.indexOf('\r\n\r\n');
127
- if (headerEnd !== -1) {
128
- useContentLengthFraming = true;
129
- const consumed = parseContentLengthFrame(headerEnd);
130
- if (!consumed) return;
131
- continue;
100
+ function onStdoutClose() {
101
+ if (keepAlive) {
102
+ clearInterval(keepAlive);
103
+ keepAlive = null;
132
104
  }
133
-
134
- const lf = buffer.indexOf('\n');
135
- if (lf !== -1) {
136
- useContentLengthFraming = false;
137
- const line = buffer.subarray(0, lf).toString('utf8').trim();
138
- buffer = buffer.subarray(lf + 1);
139
- if (line) void handleMessage(JSON.parse(line));
140
- continue;
105
+ process.exitCode = 0;
106
+ }
107
+ stdin.on('close', onStdinClose);
108
+ stdin.on('end', onStdinClose);
109
+ stdout.on('close', onStdoutClose);
110
+
111
+ stdin.on('data', (chunk) => {
112
+ buffer = Buffer.concat([buffer, chunk]);
113
+ readFrames();
114
+ });
115
+
116
+ function readFrames() {
117
+ while (true) {
118
+ const headerEnd = buffer.indexOf('\r\n\r\n');
119
+ if (headerEnd !== -1) {
120
+ useContentLengthFraming = true;
121
+ const consumed = parseContentLengthFrame(headerEnd);
122
+ if (!consumed) return;
123
+ continue;
124
+ }
125
+
126
+ const lf = buffer.indexOf('\n');
127
+ if (lf !== -1) {
128
+ useContentLengthFraming = false;
129
+ const line = buffer.subarray(0, lf).toString('utf8').trim();
130
+ buffer = buffer.subarray(lf + 1);
131
+ if (line) void handleMessage(JSON.parse(line));
132
+ continue;
133
+ }
134
+
135
+ return;
141
136
  }
142
-
143
- return;
144
137
  }
145
- }
146
138
 
147
- function parseContentLengthFrame(headerEnd) {
148
- const header = buffer.subarray(0, headerEnd).toString('utf8');
149
- const match = header.match(/Content-Length:\s*(\d+)/i);
150
- if (!match) {
151
- buffer = Buffer.alloc(0);
139
+ function parseContentLengthFrame(headerEnd) {
140
+ const header = buffer.subarray(0, headerEnd).toString('utf8');
141
+ const match = header.match(/Content-Length:\s*(\d+)/i);
142
+ if (!match) {
143
+ buffer = Buffer.alloc(0);
144
+ return true;
145
+ }
146
+ const length = Number(match[1]);
147
+ const bodyStart = headerEnd + 4;
148
+ const bodyEnd = bodyStart + length;
149
+ if (buffer.length < bodyEnd) return false;
150
+ const body = buffer.subarray(bodyStart, bodyEnd).toString('utf8');
151
+ buffer = buffer.subarray(bodyEnd);
152
+ void handleMessage(JSON.parse(body));
152
153
  return true;
153
154
  }
154
- const length = Number(match[1]);
155
- const bodyStart = headerEnd + 4;
156
- const bodyEnd = bodyStart + length;
157
- if (buffer.length < bodyEnd) return false;
158
- const body = buffer.subarray(bodyStart, bodyEnd).toString('utf8');
159
- buffer = buffer.subarray(bodyEnd);
160
- void handleMessage(JSON.parse(body));
161
- return true;
162
- }
163
-
164
- async function handleMessage(message) {
165
- if (!Object.hasOwn(message, 'id')) {
166
- if (message.method === 'notifications/initialized') return;
167
- return;
168
- }
169
- try {
170
- const result = await dispatch(message.method, message.params || {});
171
- writeMessage({ jsonrpc: '2.0', id: message.id, result });
172
- } catch (error) {
173
- writeMessage({
174
- jsonrpc: '2.0',
175
- id: message.id,
176
- error: {
177
- code: -32603,
178
- message: error.message,
179
- },
180
- });
181
- }
182
- }
183
-
184
- async function dispatch(method, params) {
185
- if (method === 'initialize') {
186
- const ci = params.clientInfo || {};
187
155
 
156
+ async function handleMessage(message) {
157
+ if (!Object.hasOwn(message, 'id')) {
158
+ if (message.method === 'notifications/initialized') return;
159
+ return;
160
+ }
188
161
  try {
189
- const { hdkitGenerateUserHash } = await import('./sandbox/hdkitservice-api.mjs');
190
- await Promise.race([
191
- hdkitGenerateUserHash(),
192
- new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 3000)),
193
- ]);
194
- } catch {}
195
-
196
- const agent = detectAgent(ci);
197
- initTelemetry({ harness: agent.harness, version: agent.version });
198
- return {
199
- protocolVersion: params.protocolVersion || '2024-11-05',
200
- capabilities: {
201
- tools: {},
202
- },
203
- serverInfo: {
204
- name: 'huaweicloud-devkit',
205
- version: pkgVersion,
206
- },
207
- };
208
- }
209
-
210
- if (method === 'tools/list') {
211
- return { tools: TOOL_DEFINITIONS };
212
- }
213
-
214
- if (method === 'tools/call') {
215
- const result = await callTool(params.name, params.arguments || {});
216
- const decorated = decorateResult(params.name, result);
217
- return {
218
- content: [
219
- {
220
- type: 'text',
221
- text: JSON.stringify(decorated, null, 2),
162
+ const result = await dispatch(message.method, message.params || {});
163
+ writeMessage({ jsonrpc: '2.0', id: message.id, result });
164
+ } catch (error) {
165
+ writeMessage({
166
+ jsonrpc: '2.0',
167
+ id: message.id,
168
+ error: {
169
+ code: -32603,
170
+ message: error.message,
222
171
  },
223
- ],
224
- isError: false,
225
- };
226
- }
227
-
228
- if (method === 'resources/list') {
229
- return { resources: [] };
172
+ });
173
+ }
230
174
  }
231
175
 
232
- throw new Error(`Unsupported method: ${method}`);
233
- }
234
-
235
- function writeMessage(message) {
236
- const json = JSON.stringify(message);
237
- if (useContentLengthFraming) {
238
- stdout.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`);
239
- } else {
240
- stdout.write(json + '\n');
176
+ function writeMessage(message) {
177
+ const json = JSON.stringify(message);
178
+ if (useContentLengthFraming) {
179
+ stdout.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`);
180
+ } else {
181
+ stdout.write(json + '\n');
182
+ }
241
183
  }
242
184
  }
@@ -4097,7 +4097,7 @@ async function cmdInstallHcloud() {
4097
4097
  if (r.status === 0) {
4098
4098
  console.log(' \x1b[32mPrivacy agreement accepted. KooCLI ready.\x1b[0m');
4099
4099
  } else {
4100
- console.log(' \x1b[33m无法写入配置目录。请在码道外终端运行: echo "y" | hcloud version\x1b[0m');
4100
+ console.log(' \x1b[33m无法写入配置目录。请在普通终端运行: echo "y" | hcloud version\x1b[0m');
4101
4101
  }
4102
4102
  } else {
4103
4103
  console.log(' \x1b[33m请手动接受隐私协议:在终端运行 hcloud version 并按提示操作\x1b[0m');