huaweicloud-devkit 1.0.2-dev.9 → 1.0.2-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.
Files changed (97) hide show
  1. package/.agents/plugins/marketplace.json +20 -20
  2. package/LICENSE +201 -201
  3. package/README.md +163 -138
  4. package/README.zh-CN.md +161 -138
  5. package/bin/setup.cjs +8 -8
  6. package/integrations/opencode/commands/huaweicloud-doctor.md +14 -14
  7. package/integrations/opencode/opencode.json +14 -14
  8. package/integrations/opencode/skills/huaweicloud-core/SKILL.md +32 -32
  9. package/package.json +46 -44
  10. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +44 -44
  11. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +43 -43
  12. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +44 -44
  13. package/plugins/huaweicloud-core/.mcp.json +15 -15
  14. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +44 -44
  15. package/plugins/huaweicloud-core/hooks/hooks.json +26 -26
  16. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +136 -136
  17. package/plugins/huaweicloud-core/safety/policy.json +99 -99
  18. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +199 -199
  19. package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +164 -164
  20. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +48 -48
  21. package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +50 -50
  22. package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +101 -101
  23. package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +90 -90
  24. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +190 -190
  25. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +124 -124
  26. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +55 -55
  27. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +70 -70
  28. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +56 -56
  29. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +97 -97
  30. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +29 -29
  31. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +23 -23
  32. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +185 -185
  33. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +133 -133
  34. package/plugins/huaweicloud-core/skills/huawei-ecs/references/evs.md +31 -31
  35. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +55 -55
  36. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +52 -52
  37. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +131 -131
  38. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +58 -58
  39. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +71 -71
  40. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/deploy-workflow.md +119 -119
  41. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +142 -142
  42. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +56 -56
  43. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +65 -65
  44. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +111 -111
  45. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +122 -122
  46. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +56 -56
  47. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +65 -65
  48. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +133 -127
  49. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +49 -49
  50. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +26 -26
  51. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +100 -100
  52. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +40 -40
  53. package/plugins/huaweicloud-core/skills/huawei-obs/references/static-website.md +56 -56
  54. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +224 -224
  55. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +135 -134
  56. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +69 -69
  57. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +84 -84
  58. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +76 -76
  59. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +59 -59
  60. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +50 -50
  61. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +63 -63
  62. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +56 -56
  63. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +4 -4
  64. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +51 -47
  65. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +4 -4
  66. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +128 -128
  67. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +4 -4
  68. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +110 -95
  69. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +4 -4
  70. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +19 -19
  71. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +27 -27
  72. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +60 -60
  73. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +4 -4
  74. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +83 -83
  75. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +4 -4
  76. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +105 -87
  77. package/plugins/huaweicloud-core/src/auth/credentials.mjs +95 -95
  78. package/plugins/huaweicloud-core/src/auth/service.mjs +63 -63
  79. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +265 -265
  80. package/plugins/huaweicloud-core/src/mcp-server.mjs +129 -129
  81. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +155 -0
  82. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +87 -0
  83. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +137 -137
  84. package/plugins/huaweicloud-core/src/safety-policy.mjs +302 -302
  85. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +91 -87
  86. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +162 -153
  87. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +112 -105
  88. package/plugins/huaweicloud-core/src/search-market.mjs +174 -169
  89. package/plugins/huaweicloud-core/src/setup-cli.mjs +2017 -1594
  90. package/plugins/huaweicloud-core/src/tools.mjs +1008 -993
  91. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +427 -427
  92. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +132 -132
  93. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +227 -227
  94. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +202 -202
  95. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +158 -158
  96. package/plugins/huaweicloud-core/src/ws-exec/index.js +19 -19
  97. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +338 -338
@@ -1,427 +1,427 @@
1
- import {
2
- DEFAULT_TIMEOUT_MS,
3
- WebSocketExecError,
4
- buildMarkers,
5
- buildShellCommands,
6
- cleanCommandOutput,
7
- } from './ws-exec-client.js';
8
- import { HwlinkWebSocketMultiplexer } from './hwlink-multiplexer.js';
9
- import { HwlinkTerminalChannel } from './hwlink-terminal-channel.js';
10
-
11
- const decoder = new TextDecoder();
12
- const encoder = new TextEncoder();
13
-
14
- function normalizeCommand(command) {
15
- if (!command || !String(command).trim()) {
16
- throw new WebSocketExecError('missing command', 2);
17
- }
18
- return String(command).trim();
19
- }
20
-
21
- function normalizeTimeout(timeoutMs) {
22
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
23
- throw new WebSocketExecError('timeoutMs must be a positive number of milliseconds', 2);
24
- }
25
- return timeoutMs;
26
- }
27
-
28
- function normalizeSource(source) {
29
- const numericSource = Number(source);
30
- if (!Number.isInteger(numericSource) || numericSource < -0x80000000 || numericSource > 0xffffffff) {
31
- throw new WebSocketExecError('hwlink source must be an int32 or uint32 number', 2);
32
- }
33
- return numericSource;
34
- }
35
-
36
- function normalizeUrl(url) {
37
- if (!url || !String(url).trim()) {
38
- throw new WebSocketExecError('hwlink url is required', 2);
39
- }
40
- return String(url);
41
- }
42
-
43
- function createHwlinkTerminal(options = {}) {
44
- const {
45
- url,
46
- source,
47
- username = 'root',
48
- WebSocketImpl = globalThis.WebSocket,
49
- protocol = 'devenv',
50
- cols,
51
- rows,
52
- onFrame,
53
- onData,
54
- onError,
55
- onClose,
56
- trace = false,
57
- } = options;
58
-
59
- const normalizedUrl = normalizeUrl(url);
60
- const normalizedSource = normalizeSource(source);
61
- const mux = new HwlinkWebSocketMultiplexer(normalizedUrl, normalizedSource, {
62
- WebSocketImpl,
63
- protocol,
64
- onFrame,
65
- trace,
66
- });
67
- const term = new HwlinkTerminalChannel(username);
68
- let closed = false;
69
- let readySettled = false;
70
- let readyResolve;
71
- let readyReject;
72
-
73
- const ready = new Promise((resolve, reject) => {
74
- readyResolve = resolve;
75
- readyReject = reject;
76
- });
77
-
78
- function settleReady(error) {
79
- if (readySettled) return;
80
- readySettled = true;
81
- if (error) readyReject(error);
82
- else readyResolve(handle);
83
- }
84
-
85
- function handleError(error, prefix) {
86
- const wrapped = new WebSocketExecError(`${prefix}: ${error.message}`, 1, {
87
- cause: error,
88
- phase: readySettled ? 'open' : 'opening',
89
- });
90
- settleReady(wrapped);
91
- if (onError) onError(wrapped);
92
- close();
93
- }
94
-
95
- function handleClose() {
96
- settleReady(new WebSocketExecError('hwlink terminal closed before ready', 1, {
97
- phase: 'opening',
98
- }));
99
- if (closed) return;
100
- closed = true;
101
- if (onClose) onClose();
102
- }
103
-
104
- term.onReady(() => {
105
- if (Number.isFinite(cols) && Number.isFinite(rows)) {
106
- term.resize(cols, rows);
107
- }
108
- settleReady();
109
- });
110
- term.onData((data) => {
111
- if (onData) onData(data);
112
- });
113
- term.onError((error) => handleError(error, 'hwlink terminal error'));
114
- term.onClose(handleClose);
115
- mux.onError = (error) => handleError(error, 'hwlink websocket error');
116
- mux.onClose = handleClose;
117
-
118
- function close() {
119
- if (closed) return;
120
- closed = true;
121
- term.close();
122
- mux.close();
123
- }
124
-
125
- const handle = {
126
- url: normalizedUrl,
127
- source: normalizedSource,
128
- username,
129
- mux,
130
- term,
131
- ready,
132
- close,
133
- resize: (nextCols, nextRows) => term.resize(nextCols, nextRows),
134
- sendInput: (data) => term.sendInput(data),
135
- sendText: (text) => term.sendText(text),
136
- };
137
-
138
- term.attach(mux);
139
- return handle;
140
- }
141
-
142
- class HwlinkTerminalExecSession {
143
- constructor(options = {}) {
144
- const {
145
- url,
146
- source,
147
- username = 'root',
148
- timeoutMs = DEFAULT_TIMEOUT_MS,
149
- WebSocketImpl = globalThis.WebSocket,
150
- protocol = 'devenv',
151
- cols,
152
- rows,
153
- onFrame,
154
- onData,
155
- trace = false,
156
- } = options;
157
-
158
- this.url = normalizeUrl(url);
159
- this.source = normalizeSource(source);
160
- this.username = username;
161
- this.timeoutMs = normalizeTimeout(timeoutMs);
162
- this.onData = onData;
163
- this.initialCols = cols;
164
- this.initialRows = rows;
165
- this.state = 'opening';
166
- this.readyBuffer = '';
167
- this.inputEchoed = false;
168
- this.pending = null;
169
- this.queue = Promise.resolve();
170
- this.readyMarkers = buildMarkers();
171
- const { readyCommand } = buildShellCommands(this.readyMarkers);
172
- this.readyCommand = readyCommand;
173
-
174
- this.readyPromise = new Promise((resolve, reject) => {
175
- this.resolveReady = resolve;
176
- this.rejectReady = reject;
177
- });
178
-
179
- this.readyTimeout = setTimeout(() => {
180
- this.fail(new WebSocketExecError(`hwlink terminal ready timeout after ${this.timeoutMs}ms`, 124, {
181
- partialOutput: this.readyBuffer,
182
- phase: 'opening',
183
- }));
184
- }, this.timeoutMs);
185
-
186
- try {
187
- this.mux = new HwlinkWebSocketMultiplexer(this.url, this.source, {
188
- WebSocketImpl,
189
- protocol,
190
- onFrame,
191
- trace,
192
- });
193
- } catch (error) {
194
- clearTimeout(this.readyTimeout);
195
- throw new WebSocketExecError(error.message, 2, { cause: error, phase: 'opening' });
196
- }
197
-
198
- this.term = new HwlinkTerminalChannel(username);
199
- this.term.onData((data) => this.handleTerminalData(data));
200
- this.term.onError((error) => {
201
- this.fail(new WebSocketExecError(`hwlink terminal error: ${error.message}`, 1, {
202
- cause: error,
203
- phase: this.state,
204
- }));
205
- });
206
- this.term.onReady(() => {
207
- if (Number.isFinite(this.initialCols) && Number.isFinite(this.initialRows)) {
208
- this.term.resize(this.initialCols, this.initialRows);
209
- }
210
- this.sendLine(this.readyCommand);
211
- });
212
- this.term.onClose(() => {
213
- if (this.state !== 'closed') {
214
- this.fail(new WebSocketExecError('hwlink terminal closed before completion marker', 1, {
215
- phase: this.state,
216
- }));
217
- }
218
- });
219
- this.mux.onClose = () => {
220
- if (this.state !== 'closed') {
221
- this.fail(new WebSocketExecError('hwlink websocket closed before completion marker', 1, {
222
- phase: this.state,
223
- }));
224
- }
225
- };
226
- this.mux.onError = (error) => {
227
- if (this.state !== 'closed') {
228
- this.fail(new WebSocketExecError(`hwlink websocket error: ${error.message}`, 1, {
229
- cause: error,
230
- phase: this.state,
231
- }));
232
- }
233
- };
234
-
235
- this.term.attach(this.mux);
236
- }
237
-
238
- ready() {
239
- return this.readyPromise;
240
- }
241
-
242
- exec(command, options = {}) {
243
- const run = () => this.runExec(command, options);
244
- const result = this.queue.then(run, run);
245
- this.queue = result.catch(() => {});
246
- return result;
247
- }
248
-
249
- resize(cols, rows) {
250
- this.term.resize(cols, rows);
251
- }
252
-
253
- close() {
254
- if (this.state === 'closed') return;
255
- const wasOpening = this.state === 'opening';
256
- const pending = this.pending;
257
- this.state = 'closed';
258
- clearTimeout(this.readyTimeout);
259
- this.pending = null;
260
-
261
- if (wasOpening) {
262
- this.rejectReady(new WebSocketExecError('hwlink terminal session closed before ready', 1, {
263
- phase: 'opening',
264
- }));
265
- }
266
-
267
- if (pending) {
268
- clearTimeout(pending.timeout);
269
- pending.reject(new WebSocketExecError('hwlink terminal session closed before completion marker', 1, {
270
- phase: 'running',
271
- }));
272
- }
273
-
274
- this.term.close();
275
- this.mux.close();
276
- }
277
-
278
- fail(error) {
279
- if (this.state === 'closed') return;
280
-
281
- const wasOpening = this.state === 'opening';
282
- const pending = this.pending;
283
- this.state = 'closed';
284
- this.pending = null;
285
- clearTimeout(this.readyTimeout);
286
-
287
- if (wasOpening) {
288
- this.rejectReady(error);
289
- }
290
-
291
- if (pending) {
292
- clearTimeout(pending.timeout);
293
- pending.reject(error);
294
- }
295
-
296
- this.term.close();
297
- this.mux.close();
298
- }
299
-
300
- handleTerminalData(data) {
301
- if (this.state === 'closed') return;
302
-
303
- const chunk = decoder.decode(data, { stream: true });
304
- if (this.onData) this.onData(data, chunk);
305
-
306
- if (this.state === 'opening') {
307
- this.readyBuffer += chunk;
308
- this.tryCompleteReady();
309
- return;
310
- }
311
-
312
- if (this.pending) {
313
- this.pending.buffer += chunk;
314
- this.tryCompletePending();
315
- }
316
- }
317
-
318
- tryCompleteReady() {
319
- const readyIndex = this.readyBuffer.indexOf(this.readyMarkers.readyMarker);
320
- if (readyIndex === -1) return;
321
-
322
- this.inputEchoed = this.readyBuffer.slice(0, readyIndex).includes(this.readyCommand);
323
- this.readyBuffer = '';
324
- this.state = 'ready';
325
- clearTimeout(this.readyTimeout);
326
- this.resolveReady(this);
327
- }
328
-
329
- runExec(command, options = {}) {
330
- if (this.state !== 'ready') {
331
- return Promise.reject(new WebSocketExecError('hwlink terminal exec session is not ready', 1, {
332
- phase: this.state,
333
- }));
334
- }
335
-
336
- const shellCommand = normalizeCommand(command);
337
- const timeoutMs = normalizeTimeout(options.timeoutMs === undefined ? this.timeoutMs : options.timeoutMs);
338
- const markers = buildMarkers();
339
- const { doneCommand } = buildShellCommands(markers);
340
-
341
- return new Promise((resolve, reject) => {
342
- this.pending = {
343
- buffer: '',
344
- command: shellCommand,
345
- doneCommand,
346
- markers,
347
- reject,
348
- resolve,
349
- timeout: setTimeout(() => {
350
- this.fail(new WebSocketExecError(`hwlink terminal exec timeout after ${timeoutMs}ms`, 124, {
351
- partialOutput: this.pending ? this.pending.buffer : '',
352
- phase: 'running',
353
- }));
354
- }, timeoutMs),
355
- };
356
-
357
- this.sendLine(shellCommand);
358
- this.sendLine(doneCommand);
359
- });
360
- }
361
-
362
- tryCompletePending() {
363
- const pending = this.pending;
364
- if (!pending) return;
365
-
366
- const doneMatch = pending.buffer.match(pending.markers.donePattern);
367
- if (!doneMatch || doneMatch.index === undefined) return;
368
-
369
- const rawOutput = pending.buffer.slice(0, doneMatch.index);
370
- const stdout = cleanCommandOutput(rawOutput, {
371
- inputEchoed: this.inputEchoed,
372
- command: pending.command,
373
- doneCommand: pending.doneCommand,
374
- });
375
-
376
- clearTimeout(pending.timeout);
377
- this.pending = null;
378
- pending.resolve({
379
- stdout,
380
- exitCode: Number(doneMatch[1]),
381
- url: this.url,
382
- source: this.source,
383
- username: this.username,
384
- command: pending.command,
385
- });
386
- }
387
-
388
- sendLine(line) {
389
- this.term.sendInput(encoder.encode(`${line}\n`));
390
- }
391
- }
392
-
393
- async function connectHwlinkTerminalSession(options = {}) {
394
- const session = new HwlinkTerminalExecSession(options);
395
- await session.ready();
396
- return session;
397
- }
398
-
399
- async function connectHwlinkInteractiveTerminal(options = {}) {
400
- const terminal = createHwlinkTerminal(options);
401
- await terminal.ready;
402
- return terminal;
403
- }
404
-
405
- async function executeHwlinkCommand(options = {}) {
406
- const { command, timeoutMs = DEFAULT_TIMEOUT_MS, ...sessionOptions } = options;
407
- const shellCommand = normalizeCommand(command);
408
- const normalizedTimeoutMs = normalizeTimeout(timeoutMs);
409
- const session = await connectHwlinkTerminalSession({
410
- ...sessionOptions,
411
- timeoutMs: normalizedTimeoutMs,
412
- });
413
- try {
414
- return await session.exec(shellCommand, { timeoutMs: normalizedTimeoutMs });
415
- } finally {
416
- session.close();
417
- }
418
- }
419
-
420
- export {
421
- HwlinkTerminalExecSession,
422
- connectHwlinkInteractiveTerminal,
423
- connectHwlinkTerminalSession,
424
- createHwlinkTerminal,
425
- executeHwlinkCommand,
426
- normalizeSource,
427
- };
1
+ import {
2
+ DEFAULT_TIMEOUT_MS,
3
+ WebSocketExecError,
4
+ buildMarkers,
5
+ buildShellCommands,
6
+ cleanCommandOutput,
7
+ } from './ws-exec-client.js';
8
+ import { HwlinkWebSocketMultiplexer } from './hwlink-multiplexer.js';
9
+ import { HwlinkTerminalChannel } from './hwlink-terminal-channel.js';
10
+
11
+ const decoder = new TextDecoder();
12
+ const encoder = new TextEncoder();
13
+
14
+ function normalizeCommand(command) {
15
+ if (!command || !String(command).trim()) {
16
+ throw new WebSocketExecError('missing command', 2);
17
+ }
18
+ return String(command).trim();
19
+ }
20
+
21
+ function normalizeTimeout(timeoutMs) {
22
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
23
+ throw new WebSocketExecError('timeoutMs must be a positive number of milliseconds', 2);
24
+ }
25
+ return timeoutMs;
26
+ }
27
+
28
+ function normalizeSource(source) {
29
+ const numericSource = Number(source);
30
+ if (!Number.isInteger(numericSource) || numericSource < -0x80000000 || numericSource > 0xffffffff) {
31
+ throw new WebSocketExecError('hwlink source must be an int32 or uint32 number', 2);
32
+ }
33
+ return numericSource;
34
+ }
35
+
36
+ function normalizeUrl(url) {
37
+ if (!url || !String(url).trim()) {
38
+ throw new WebSocketExecError('hwlink url is required', 2);
39
+ }
40
+ return String(url);
41
+ }
42
+
43
+ function createHwlinkTerminal(options = {}) {
44
+ const {
45
+ url,
46
+ source,
47
+ username = 'root',
48
+ WebSocketImpl = globalThis.WebSocket,
49
+ protocol = 'devenv',
50
+ cols,
51
+ rows,
52
+ onFrame,
53
+ onData,
54
+ onError,
55
+ onClose,
56
+ trace = false,
57
+ } = options;
58
+
59
+ const normalizedUrl = normalizeUrl(url);
60
+ const normalizedSource = normalizeSource(source);
61
+ const mux = new HwlinkWebSocketMultiplexer(normalizedUrl, normalizedSource, {
62
+ WebSocketImpl,
63
+ protocol,
64
+ onFrame,
65
+ trace,
66
+ });
67
+ const term = new HwlinkTerminalChannel(username);
68
+ let closed = false;
69
+ let readySettled = false;
70
+ let readyResolve;
71
+ let readyReject;
72
+
73
+ const ready = new Promise((resolve, reject) => {
74
+ readyResolve = resolve;
75
+ readyReject = reject;
76
+ });
77
+
78
+ function settleReady(error) {
79
+ if (readySettled) return;
80
+ readySettled = true;
81
+ if (error) readyReject(error);
82
+ else readyResolve(handle);
83
+ }
84
+
85
+ function handleError(error, prefix) {
86
+ const wrapped = new WebSocketExecError(`${prefix}: ${error.message}`, 1, {
87
+ cause: error,
88
+ phase: readySettled ? 'open' : 'opening',
89
+ });
90
+ settleReady(wrapped);
91
+ if (onError) onError(wrapped);
92
+ close();
93
+ }
94
+
95
+ function handleClose() {
96
+ settleReady(new WebSocketExecError('hwlink terminal closed before ready', 1, {
97
+ phase: 'opening',
98
+ }));
99
+ if (closed) return;
100
+ closed = true;
101
+ if (onClose) onClose();
102
+ }
103
+
104
+ term.onReady(() => {
105
+ if (Number.isFinite(cols) && Number.isFinite(rows)) {
106
+ term.resize(cols, rows);
107
+ }
108
+ settleReady();
109
+ });
110
+ term.onData((data) => {
111
+ if (onData) onData(data);
112
+ });
113
+ term.onError((error) => handleError(error, 'hwlink terminal error'));
114
+ term.onClose(handleClose);
115
+ mux.onError = (error) => handleError(error, 'hwlink websocket error');
116
+ mux.onClose = handleClose;
117
+
118
+ function close() {
119
+ if (closed) return;
120
+ closed = true;
121
+ term.close();
122
+ mux.close();
123
+ }
124
+
125
+ const handle = {
126
+ url: normalizedUrl,
127
+ source: normalizedSource,
128
+ username,
129
+ mux,
130
+ term,
131
+ ready,
132
+ close,
133
+ resize: (nextCols, nextRows) => term.resize(nextCols, nextRows),
134
+ sendInput: (data) => term.sendInput(data),
135
+ sendText: (text) => term.sendText(text),
136
+ };
137
+
138
+ term.attach(mux);
139
+ return handle;
140
+ }
141
+
142
+ class HwlinkTerminalExecSession {
143
+ constructor(options = {}) {
144
+ const {
145
+ url,
146
+ source,
147
+ username = 'root',
148
+ timeoutMs = DEFAULT_TIMEOUT_MS,
149
+ WebSocketImpl = globalThis.WebSocket,
150
+ protocol = 'devenv',
151
+ cols,
152
+ rows,
153
+ onFrame,
154
+ onData,
155
+ trace = false,
156
+ } = options;
157
+
158
+ this.url = normalizeUrl(url);
159
+ this.source = normalizeSource(source);
160
+ this.username = username;
161
+ this.timeoutMs = normalizeTimeout(timeoutMs);
162
+ this.onData = onData;
163
+ this.initialCols = cols;
164
+ this.initialRows = rows;
165
+ this.state = 'opening';
166
+ this.readyBuffer = '';
167
+ this.inputEchoed = false;
168
+ this.pending = null;
169
+ this.queue = Promise.resolve();
170
+ this.readyMarkers = buildMarkers();
171
+ const { readyCommand } = buildShellCommands(this.readyMarkers);
172
+ this.readyCommand = readyCommand;
173
+
174
+ this.readyPromise = new Promise((resolve, reject) => {
175
+ this.resolveReady = resolve;
176
+ this.rejectReady = reject;
177
+ });
178
+
179
+ this.readyTimeout = setTimeout(() => {
180
+ this.fail(new WebSocketExecError(`hwlink terminal ready timeout after ${this.timeoutMs}ms`, 124, {
181
+ partialOutput: this.readyBuffer,
182
+ phase: 'opening',
183
+ }));
184
+ }, this.timeoutMs);
185
+
186
+ try {
187
+ this.mux = new HwlinkWebSocketMultiplexer(this.url, this.source, {
188
+ WebSocketImpl,
189
+ protocol,
190
+ onFrame,
191
+ trace,
192
+ });
193
+ } catch (error) {
194
+ clearTimeout(this.readyTimeout);
195
+ throw new WebSocketExecError(error.message, 2, { cause: error, phase: 'opening' });
196
+ }
197
+
198
+ this.term = new HwlinkTerminalChannel(username);
199
+ this.term.onData((data) => this.handleTerminalData(data));
200
+ this.term.onError((error) => {
201
+ this.fail(new WebSocketExecError(`hwlink terminal error: ${error.message}`, 1, {
202
+ cause: error,
203
+ phase: this.state,
204
+ }));
205
+ });
206
+ this.term.onReady(() => {
207
+ if (Number.isFinite(this.initialCols) && Number.isFinite(this.initialRows)) {
208
+ this.term.resize(this.initialCols, this.initialRows);
209
+ }
210
+ this.sendLine(this.readyCommand);
211
+ });
212
+ this.term.onClose(() => {
213
+ if (this.state !== 'closed') {
214
+ this.fail(new WebSocketExecError('hwlink terminal closed before completion marker', 1, {
215
+ phase: this.state,
216
+ }));
217
+ }
218
+ });
219
+ this.mux.onClose = () => {
220
+ if (this.state !== 'closed') {
221
+ this.fail(new WebSocketExecError('hwlink websocket closed before completion marker', 1, {
222
+ phase: this.state,
223
+ }));
224
+ }
225
+ };
226
+ this.mux.onError = (error) => {
227
+ if (this.state !== 'closed') {
228
+ this.fail(new WebSocketExecError(`hwlink websocket error: ${error.message}`, 1, {
229
+ cause: error,
230
+ phase: this.state,
231
+ }));
232
+ }
233
+ };
234
+
235
+ this.term.attach(this.mux);
236
+ }
237
+
238
+ ready() {
239
+ return this.readyPromise;
240
+ }
241
+
242
+ exec(command, options = {}) {
243
+ const run = () => this.runExec(command, options);
244
+ const result = this.queue.then(run, run);
245
+ this.queue = result.catch(() => {});
246
+ return result;
247
+ }
248
+
249
+ resize(cols, rows) {
250
+ this.term.resize(cols, rows);
251
+ }
252
+
253
+ close() {
254
+ if (this.state === 'closed') return;
255
+ const wasOpening = this.state === 'opening';
256
+ const pending = this.pending;
257
+ this.state = 'closed';
258
+ clearTimeout(this.readyTimeout);
259
+ this.pending = null;
260
+
261
+ if (wasOpening) {
262
+ this.rejectReady(new WebSocketExecError('hwlink terminal session closed before ready', 1, {
263
+ phase: 'opening',
264
+ }));
265
+ }
266
+
267
+ if (pending) {
268
+ clearTimeout(pending.timeout);
269
+ pending.reject(new WebSocketExecError('hwlink terminal session closed before completion marker', 1, {
270
+ phase: 'running',
271
+ }));
272
+ }
273
+
274
+ this.term.close();
275
+ this.mux.close();
276
+ }
277
+
278
+ fail(error) {
279
+ if (this.state === 'closed') return;
280
+
281
+ const wasOpening = this.state === 'opening';
282
+ const pending = this.pending;
283
+ this.state = 'closed';
284
+ this.pending = null;
285
+ clearTimeout(this.readyTimeout);
286
+
287
+ if (wasOpening) {
288
+ this.rejectReady(error);
289
+ }
290
+
291
+ if (pending) {
292
+ clearTimeout(pending.timeout);
293
+ pending.reject(error);
294
+ }
295
+
296
+ this.term.close();
297
+ this.mux.close();
298
+ }
299
+
300
+ handleTerminalData(data) {
301
+ if (this.state === 'closed') return;
302
+
303
+ const chunk = decoder.decode(data, { stream: true });
304
+ if (this.onData) this.onData(data, chunk);
305
+
306
+ if (this.state === 'opening') {
307
+ this.readyBuffer += chunk;
308
+ this.tryCompleteReady();
309
+ return;
310
+ }
311
+
312
+ if (this.pending) {
313
+ this.pending.buffer += chunk;
314
+ this.tryCompletePending();
315
+ }
316
+ }
317
+
318
+ tryCompleteReady() {
319
+ const readyIndex = this.readyBuffer.indexOf(this.readyMarkers.readyMarker);
320
+ if (readyIndex === -1) return;
321
+
322
+ this.inputEchoed = this.readyBuffer.slice(0, readyIndex).includes(this.readyCommand);
323
+ this.readyBuffer = '';
324
+ this.state = 'ready';
325
+ clearTimeout(this.readyTimeout);
326
+ this.resolveReady(this);
327
+ }
328
+
329
+ runExec(command, options = {}) {
330
+ if (this.state !== 'ready') {
331
+ return Promise.reject(new WebSocketExecError('hwlink terminal exec session is not ready', 1, {
332
+ phase: this.state,
333
+ }));
334
+ }
335
+
336
+ const shellCommand = normalizeCommand(command);
337
+ const timeoutMs = normalizeTimeout(options.timeoutMs === undefined ? this.timeoutMs : options.timeoutMs);
338
+ const markers = buildMarkers();
339
+ const { doneCommand } = buildShellCommands(markers);
340
+
341
+ return new Promise((resolve, reject) => {
342
+ this.pending = {
343
+ buffer: '',
344
+ command: shellCommand,
345
+ doneCommand,
346
+ markers,
347
+ reject,
348
+ resolve,
349
+ timeout: setTimeout(() => {
350
+ this.fail(new WebSocketExecError(`hwlink terminal exec timeout after ${timeoutMs}ms`, 124, {
351
+ partialOutput: this.pending ? this.pending.buffer : '',
352
+ phase: 'running',
353
+ }));
354
+ }, timeoutMs),
355
+ };
356
+
357
+ this.sendLine(shellCommand);
358
+ this.sendLine(doneCommand);
359
+ });
360
+ }
361
+
362
+ tryCompletePending() {
363
+ const pending = this.pending;
364
+ if (!pending) return;
365
+
366
+ const doneMatch = pending.buffer.match(pending.markers.donePattern);
367
+ if (!doneMatch || doneMatch.index === undefined) return;
368
+
369
+ const rawOutput = pending.buffer.slice(0, doneMatch.index);
370
+ const stdout = cleanCommandOutput(rawOutput, {
371
+ inputEchoed: this.inputEchoed,
372
+ command: pending.command,
373
+ doneCommand: pending.doneCommand,
374
+ });
375
+
376
+ clearTimeout(pending.timeout);
377
+ this.pending = null;
378
+ pending.resolve({
379
+ stdout,
380
+ exitCode: Number(doneMatch[1]),
381
+ url: this.url,
382
+ source: this.source,
383
+ username: this.username,
384
+ command: pending.command,
385
+ });
386
+ }
387
+
388
+ sendLine(line) {
389
+ this.term.sendInput(encoder.encode(`${line}\n`));
390
+ }
391
+ }
392
+
393
+ async function connectHwlinkTerminalSession(options = {}) {
394
+ const session = new HwlinkTerminalExecSession(options);
395
+ await session.ready();
396
+ return session;
397
+ }
398
+
399
+ async function connectHwlinkInteractiveTerminal(options = {}) {
400
+ const terminal = createHwlinkTerminal(options);
401
+ await terminal.ready;
402
+ return terminal;
403
+ }
404
+
405
+ async function executeHwlinkCommand(options = {}) {
406
+ const { command, timeoutMs = DEFAULT_TIMEOUT_MS, ...sessionOptions } = options;
407
+ const shellCommand = normalizeCommand(command);
408
+ const normalizedTimeoutMs = normalizeTimeout(timeoutMs);
409
+ const session = await connectHwlinkTerminalSession({
410
+ ...sessionOptions,
411
+ timeoutMs: normalizedTimeoutMs,
412
+ });
413
+ try {
414
+ return await session.exec(shellCommand, { timeoutMs: normalizedTimeoutMs });
415
+ } finally {
416
+ session.close();
417
+ }
418
+ }
419
+
420
+ export {
421
+ HwlinkTerminalExecSession,
422
+ connectHwlinkInteractiveTerminal,
423
+ connectHwlinkTerminalSession,
424
+ createHwlinkTerminal,
425
+ executeHwlinkCommand,
426
+ normalizeSource,
427
+ };