huaweicloud-devkit 1.0.2-dev.1 → 1.0.2-dev.11

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.
Files changed (33) hide show
  1. package/README.md +27 -3
  2. package/README.zh-CN.md +27 -3
  3. package/package.json +1 -1
  4. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
  5. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
  6. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
  7. package/plugins/huaweicloud-core/.mcp.json +2 -1
  8. package/plugins/huaweicloud-core/safety/policy.json +15 -1
  9. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +21 -0
  10. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +1 -1
  11. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +1 -1
  12. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +1 -1
  13. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +2 -2
  14. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +134 -0
  15. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +2 -1
  16. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +3 -1
  17. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +1 -1
  18. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +87 -0
  19. package/plugins/huaweicloud-core/src/auth/credentials.mjs +95 -0
  20. package/plugins/huaweicloud-core/src/auth/service.mjs +63 -0
  21. package/plugins/huaweicloud-core/src/mcp-server.mjs +11 -0
  22. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +87 -0
  23. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +153 -0
  24. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +105 -0
  25. package/plugins/huaweicloud-core/src/setup-cli.mjs +560 -71
  26. package/plugins/huaweicloud-core/src/tools.mjs +159 -3
  27. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +427 -0
  28. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +132 -0
  29. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +227 -0
  30. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +202 -0
  31. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +158 -0
  32. package/plugins/huaweicloud-core/src/ws-exec/index.js +19 -0
  33. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +338 -0
@@ -0,0 +1,19 @@
1
+ import * as hwlinkExec from './hwlink-exec-client.js';
2
+ import * as hwlinkPacket from './hwlink-packet.js';
3
+ import * as wsExec from './ws-exec-client.js';
4
+ import { HwlinkWebSocketMultiplexer } from './hwlink-multiplexer.js';
5
+ import { HwlinkTerminalChannel } from './hwlink-terminal-channel.js';
6
+
7
+ export * from './ws-exec-client.js';
8
+ export * from './hwlink-exec-client.js';
9
+ export { HwlinkTerminalChannel } from './hwlink-terminal-channel.js';
10
+ export { HwlinkWebSocketMultiplexer } from './hwlink-multiplexer.js';
11
+ export { hwlinkPacket };
12
+
13
+ export default {
14
+ ...wsExec,
15
+ ...hwlinkExec,
16
+ HwlinkTerminalChannel,
17
+ HwlinkWebSocketMultiplexer,
18
+ hwlinkPacket,
19
+ };
@@ -0,0 +1,338 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { randomBytes } from 'node:crypto';
3
+
4
+ const DEFAULT_URL = 'ws://127.0.0.1:8080';
5
+ const DEFAULT_TIMEOUT_MS = 30000;
6
+
7
+ class WebSocketExecError extends Error {
8
+ constructor(message, exitCode, details = {}) {
9
+ super(message);
10
+ this.name = 'WebSocketExecError';
11
+ this.exitCode = exitCode;
12
+ Object.assign(this, details);
13
+ }
14
+ }
15
+
16
+ async function eventDataToString(data) {
17
+ if (typeof data === 'string') return data;
18
+ if (Buffer.isBuffer(data)) return data.toString('utf8');
19
+ if (data instanceof ArrayBuffer) return Buffer.from(data).toString('utf8');
20
+ if (ArrayBuffer.isView(data)) {
21
+ return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('utf8');
22
+ }
23
+ if (data && typeof data.arrayBuffer === 'function') {
24
+ return Buffer.from(await data.arrayBuffer()).toString('utf8');
25
+ }
26
+ return String(data);
27
+ }
28
+
29
+ function sendLine(ws, line) {
30
+ ws.send(`${line}\n`);
31
+ }
32
+
33
+ function closeQuietly(ws) {
34
+ try {
35
+ ws.close();
36
+ } catch {
37
+ // The caller is already settling the operation; close errors are not useful.
38
+ }
39
+ }
40
+
41
+ function escapeRegExp(text) {
42
+ return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
43
+ }
44
+
45
+ function stripEchoedLine(text, line, options = {}) {
46
+ const escaped = escapeRegExp(line);
47
+ const stripped = text.replace(new RegExp(`(^|\\r?\\n)${escaped}\\r?\\n?`), '$1');
48
+ if (!options.allowAttached || stripped !== text) return stripped;
49
+ return text.replace(new RegExp(`${escaped}(?:\\r?\\n){0,2}$`), '');
50
+ }
51
+
52
+ function buildMarkers(nonce = randomBytes(8).toString('hex')) {
53
+ const readyPrefix = '__WS_EXEC_READY_';
54
+ const donePrefix = '__WS_EXEC_DONE_';
55
+ const readySuffix = `${nonce}__`;
56
+ const doneSuffix = `${nonce}__:`;
57
+ const readyMarker = `${readyPrefix}${readySuffix}`;
58
+ const doneMarker = `${donePrefix}${doneSuffix}`;
59
+
60
+ return {
61
+ nonce,
62
+ readyPrefix,
63
+ donePrefix,
64
+ readySuffix,
65
+ doneSuffix,
66
+ readyMarker,
67
+ doneMarker,
68
+ donePattern: new RegExp(`${escapeRegExp(doneMarker)}(\\d+)`),
69
+ };
70
+ }
71
+
72
+ function buildShellCommands(markers) {
73
+ return {
74
+ readyCommand: `stty -echo 2>/dev/null; export PS1= PS2= PROMPT_COMMAND=; printf '\\n%s%s\\n' '${markers.readyPrefix}' '${markers.readySuffix}'`,
75
+ doneCommand: `__ws_exec_rc=$?; printf '\\n%s%s%d\\n' '${markers.donePrefix}' '${markers.doneSuffix}' "$__ws_exec_rc"`,
76
+ };
77
+ }
78
+
79
+ function cleanCommandOutput(output, { inputEchoed, command, doneCommand }) {
80
+ let cleaned = output;
81
+ if (inputEchoed) cleaned = stripEchoedLine(cleaned, command);
82
+ cleaned = stripEchoedLine(cleaned, doneCommand, { allowAttached: true });
83
+ return cleaned.replace(/^\r?\n/, '').replace(/\r?\n\r?\n$/, '\n');
84
+ }
85
+
86
+ function normalizeCommand(command) {
87
+ if (!command || !String(command).trim()) {
88
+ throw new WebSocketExecError('missing command', 2);
89
+ }
90
+ return String(command).trim();
91
+ }
92
+
93
+ function normalizeTimeout(timeoutMs) {
94
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
95
+ throw new WebSocketExecError('timeoutMs must be a positive number of milliseconds', 2);
96
+ }
97
+ return timeoutMs;
98
+ }
99
+
100
+ class WebSocketShellSession {
101
+ constructor(options = {}) {
102
+ const {
103
+ url = DEFAULT_URL,
104
+ timeoutMs = DEFAULT_TIMEOUT_MS,
105
+ WebSocketImpl = globalThis.WebSocket,
106
+ onFrame,
107
+ } = options;
108
+
109
+ if (typeof WebSocketImpl !== 'function') {
110
+ throw new WebSocketExecError('global WebSocket is unavailable; use Node.js 22+ or pass WebSocketImpl', 2);
111
+ }
112
+
113
+ this.url = url;
114
+ this.timeoutMs = normalizeTimeout(timeoutMs);
115
+ this.WebSocketImpl = WebSocketImpl;
116
+ this.onFrame = onFrame;
117
+ this.state = 'opening';
118
+ this.readyBuffer = '';
119
+ this.inputEchoed = false;
120
+ this.pending = null;
121
+ this.queue = Promise.resolve();
122
+ this.ws = new WebSocketImpl(url);
123
+
124
+ this.readyMarkers = buildMarkers();
125
+ const { readyCommand } = buildShellCommands(this.readyMarkers);
126
+ this.readyCommand = readyCommand;
127
+
128
+ this.readyPromise = new Promise((resolve, reject) => {
129
+ this.resolveReady = resolve;
130
+ this.rejectReady = reject;
131
+ });
132
+
133
+ this.readyTimeout = setTimeout(() => {
134
+ this.fail(
135
+ new WebSocketExecError(`exec ready timeout after ${this.timeoutMs}ms`, 124, {
136
+ partialOutput: this.readyBuffer,
137
+ phase: 'opening',
138
+ }),
139
+ );
140
+ }, this.timeoutMs);
141
+
142
+ this.ws.addEventListener('open', () => {
143
+ sendLine(this.ws, this.readyCommand);
144
+ });
145
+
146
+ this.ws.addEventListener('message', (event) => {
147
+ this.handleMessage(event).catch((error) => {
148
+ this.fail(
149
+ new WebSocketExecError(`exec message handling error: ${error.message}`, 1, {
150
+ cause: error,
151
+ phase: this.state,
152
+ }),
153
+ );
154
+ });
155
+ });
156
+
157
+ this.ws.addEventListener('close', () => {
158
+ if (this.state !== 'closed') {
159
+ this.fail(new WebSocketExecError('exec websocket closed before completion marker', 1, { phase: this.state }));
160
+ }
161
+ });
162
+
163
+ this.ws.addEventListener('error', () => {
164
+ if (this.state !== 'closed') {
165
+ this.fail(new WebSocketExecError('exec websocket error', 1, { phase: this.state }));
166
+ }
167
+ });
168
+ }
169
+
170
+ ready() {
171
+ return this.readyPromise;
172
+ }
173
+
174
+ exec(command, options = {}) {
175
+ const run = () => this.runExec(command, options);
176
+ const result = this.queue.then(run, run);
177
+ this.queue = result.catch(() => {});
178
+ return result;
179
+ }
180
+
181
+ close() {
182
+ if (this.state === 'closed') return;
183
+ const wasOpening = this.state === 'opening';
184
+ const pending = this.pending;
185
+ this.state = 'closed';
186
+ clearTimeout(this.readyTimeout);
187
+ this.pending = null;
188
+
189
+ if (wasOpening) {
190
+ this.rejectReady(new WebSocketExecError('exec session closed before ready', 1, { phase: 'opening' }));
191
+ }
192
+
193
+ if (pending) {
194
+ clearTimeout(pending.timeout);
195
+ pending.reject(new WebSocketExecError('exec session closed before completion marker', 1, { phase: 'running' }));
196
+ }
197
+
198
+ closeQuietly(this.ws);
199
+ }
200
+
201
+ fail(error) {
202
+ if (this.state === 'closed') return;
203
+
204
+ const wasOpening = this.state === 'opening';
205
+ const pending = this.pending;
206
+ this.state = 'closed';
207
+ this.pending = null;
208
+ clearTimeout(this.readyTimeout);
209
+ closeQuietly(this.ws);
210
+
211
+ if (wasOpening) {
212
+ this.rejectReady(error);
213
+ }
214
+
215
+ if (pending) {
216
+ clearTimeout(pending.timeout);
217
+ pending.reject(error);
218
+ }
219
+ }
220
+
221
+ async handleMessage(event) {
222
+ if (this.state === 'closed') return;
223
+
224
+ const chunk = await eventDataToString(event.data);
225
+ if (this.onFrame) this.onFrame(chunk);
226
+
227
+ if (this.state === 'opening') {
228
+ this.readyBuffer += chunk;
229
+ this.tryCompleteReady();
230
+ return;
231
+ }
232
+
233
+ if (this.pending) {
234
+ this.pending.buffer += chunk;
235
+ this.tryCompletePending();
236
+ }
237
+ }
238
+
239
+ tryCompleteReady() {
240
+ const readyIndex = this.readyBuffer.indexOf(this.readyMarkers.readyMarker);
241
+ if (readyIndex === -1) return;
242
+
243
+ this.inputEchoed = this.readyBuffer.slice(0, readyIndex).includes(this.readyCommand);
244
+ this.readyBuffer = '';
245
+ this.state = 'ready';
246
+ clearTimeout(this.readyTimeout);
247
+ this.resolveReady(this);
248
+ }
249
+
250
+ runExec(command, options = {}) {
251
+ if (this.state !== 'ready') {
252
+ return Promise.reject(new WebSocketExecError('exec session is not ready', 1, { phase: this.state }));
253
+ }
254
+
255
+ const shellCommand = normalizeCommand(command);
256
+ const timeoutMs = normalizeTimeout(options.timeoutMs === undefined ? this.timeoutMs : options.timeoutMs);
257
+ const markers = buildMarkers();
258
+ const { doneCommand } = buildShellCommands(markers);
259
+
260
+ return new Promise((resolve, reject) => {
261
+ this.pending = {
262
+ buffer: '',
263
+ command: shellCommand,
264
+ doneCommand,
265
+ markers,
266
+ reject,
267
+ resolve,
268
+ timeout: setTimeout(() => {
269
+ this.fail(
270
+ new WebSocketExecError(`exec timeout after ${timeoutMs}ms`, 124, {
271
+ partialOutput: this.pending ? this.pending.buffer : '',
272
+ phase: 'running',
273
+ }),
274
+ );
275
+ }, timeoutMs),
276
+ };
277
+
278
+ sendLine(this.ws, shellCommand);
279
+ sendLine(this.ws, doneCommand);
280
+ });
281
+ }
282
+
283
+ tryCompletePending() {
284
+ const pending = this.pending;
285
+ if (!pending) return;
286
+
287
+ const doneMatch = pending.buffer.match(pending.markers.donePattern);
288
+ if (!doneMatch || doneMatch.index === undefined) return;
289
+
290
+ const rawOutput = pending.buffer.slice(0, doneMatch.index);
291
+ const stdout = cleanCommandOutput(rawOutput, {
292
+ inputEchoed: this.inputEchoed,
293
+ command: pending.command,
294
+ doneCommand: pending.doneCommand,
295
+ });
296
+
297
+ clearTimeout(pending.timeout);
298
+ this.pending = null;
299
+ pending.resolve({
300
+ stdout,
301
+ exitCode: Number(doneMatch[1]),
302
+ url: this.url,
303
+ command: pending.command,
304
+ });
305
+ }
306
+ }
307
+
308
+ async function connectShellSession(options = {}) {
309
+ const session = new WebSocketShellSession(options);
310
+ await session.ready();
311
+ return session;
312
+ }
313
+
314
+ async function executeCommand(options = {}) {
315
+ const { command, timeoutMs = DEFAULT_TIMEOUT_MS, ...sessionOptions } = options;
316
+ const shellCommand = normalizeCommand(command);
317
+ const normalizedTimeoutMs = normalizeTimeout(timeoutMs);
318
+ const session = await connectShellSession({ ...sessionOptions, timeoutMs: normalizedTimeoutMs });
319
+ try {
320
+ return await session.exec(shellCommand, { timeoutMs: normalizedTimeoutMs });
321
+ } finally {
322
+ session.close();
323
+ }
324
+ }
325
+
326
+ export {
327
+ DEFAULT_TIMEOUT_MS,
328
+ DEFAULT_URL,
329
+ WebSocketExecError,
330
+ WebSocketShellSession,
331
+ buildMarkers,
332
+ buildShellCommands,
333
+ cleanCommandOutput,
334
+ connectShellSession,
335
+ eventDataToString,
336
+ executeCommand,
337
+ stripEchoedLine,
338
+ };