huaweicloud-devkit 1.0.3-dev.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.
Files changed (96) hide show
  1. package/.agents/plugins/marketplace.json +18 -18
  2. package/README.md +114 -41
  3. package/README.zh-CN.md +114 -41
  4. package/bin/setup.cjs +7 -4
  5. package/integrations/opencode/opencode.json +1 -4
  6. package/package.json +63 -42
  7. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -17
  8. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +4 -17
  9. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -17
  10. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
  11. package/plugins/huaweicloud-core/.mcp.json +2 -5
  12. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -18
  13. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +32 -9
  14. package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
  15. package/plugins/huaweicloud-core/safety/policy.json +75 -99
  16. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +41 -13
  17. package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +34 -34
  18. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +18 -18
  19. package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +19 -19
  20. package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +34 -32
  21. package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +30 -30
  22. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
  23. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
  24. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
  25. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +39 -30
  26. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
  27. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
  28. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +39 -36
  29. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +15 -4
  30. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
  31. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
  32. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +40 -39
  33. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
  34. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
  35. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
  36. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
  37. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
  38. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +27 -27
  39. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
  40. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
  41. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +33 -25
  42. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +51 -44
  43. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
  44. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
  45. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
  46. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
  47. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +63 -58
  48. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +128 -42
  49. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +26 -26
  50. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
  51. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +13 -12
  52. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
  53. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
  54. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +18 -18
  55. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
  56. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
  57. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +25 -2
  58. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
  59. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +27 -1
  60. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
  61. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +65 -47
  62. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
  63. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
  64. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
  65. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
  66. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
  67. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +33 -33
  68. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
  69. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +134 -2
  70. package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
  71. package/plugins/huaweicloud-core/src/auth/service.mjs +2 -1
  72. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
  73. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +27 -8
  74. package/plugins/huaweicloud-core/src/icon-library.mjs +176 -0
  75. package/plugins/huaweicloud-core/src/mcp-server.mjs +31 -3
  76. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +73 -0
  77. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +94 -0
  78. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
  79. package/plugins/huaweicloud-core/src/safety-policy.mjs +123 -68
  80. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +14 -10
  81. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +35 -27
  82. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  83. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +526 -8
  84. package/plugins/huaweicloud-core/src/search-market.mjs +55 -16
  85. package/plugins/huaweicloud-core/src/setup-cli.mjs +1942 -206
  86. package/plugins/huaweicloud-core/src/tools.mjs +678 -131
  87. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
  88. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
  89. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +13 -16
  90. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
  91. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
  92. package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +268 -0
  93. package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
  94. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +11 -5
  95. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +0 -190
  96. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +0 -124
@@ -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));
@@ -17,9 +18,13 @@ function regexFrom(pattern) {
17
18
  }
18
19
 
19
20
  function isSecretKeyName(key, policy = DEFAULT_POLICY) {
20
- const normalized = String(key).toLowerCase().replace(/[^a-z0-9]/g, '');
21
+ const normalized = String(key)
22
+ .toLowerCase()
23
+ .replace(/[^a-z0-9]/g, '');
21
24
  if (
22
- /access.*key|secret.*key|security.*token|xauth.*token|authorization|password|passwd|adminpass|credential|private.*key/.test(normalized)
25
+ /access.*key|secret.*key|security.*token|xauth.*token|authorization|password|passwd|adminpass|credential|private.*key/.test(
26
+ normalized,
27
+ )
23
28
  ) {
24
29
  return true;
25
30
  }
@@ -28,7 +33,10 @@ function isSecretKeyName(key, policy = DEFAULT_POLICY) {
28
33
 
29
34
  function redactString(text) {
30
35
  return String(text)
31
- .replace(/((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|authorization|password|passwd|adminPass|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi, '$1<redacted>')
36
+ .replace(
37
+ /((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|authorization|password|passwd|adminPass|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi,
38
+ '$1<redacted>',
39
+ )
32
40
  .replace(/(AK|SK)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;]+)/g, '$1=<redacted>');
33
41
  }
34
42
 
@@ -78,15 +86,11 @@ function matchesAny(value, patterns) {
78
86
 
79
87
  function hasWritePrefix(operation, policy) {
80
88
  const normalized = String(operation);
81
- return policy.writeOperationPrefixes.some((prefix) =>
82
- new RegExp(`(^|[A-Za-z0-9])${prefix}`, 'i').test(normalized),
83
- );
89
+ return policy.writeOperationPrefixes.some((prefix) => new RegExp(`(^|[A-Za-z0-9])${prefix}`, 'i').test(normalized));
84
90
  }
85
91
 
86
92
  function hasReadPrefix(operation, policy) {
87
- return policy.readOperationPrefixes.some((prefix) =>
88
- new RegExp(`^${prefix}`, 'i').test(operation),
89
- );
93
+ return policy.readOperationPrefixes.some((prefix) => new RegExp(`^${prefix}`, 'i').test(operation));
90
94
  }
91
95
 
92
96
  function isLocalMetadataCommand(args) {
@@ -119,26 +123,31 @@ export function classifyHcloudArgs(args, options = {}) {
119
123
  }
120
124
 
121
125
  if (isLocalMetadataCommand(normalizedArgs)) {
122
- return applyCommandRiskRules({
123
- decision: 'allow',
124
- risk: 'local_metadata',
125
- reason: 'KooCLI local help and version commands are read-only and do not call Huawei Cloud resource APIs.',
126
- service,
127
- operation,
128
- args: normalizedArgs,
129
- }, normalizedArgs, options);
126
+ return applyCommandRiskRules(
127
+ {
128
+ decision: 'allow',
129
+ risk: 'local_metadata',
130
+ reason: 'KooCLI local help and version commands are read-only and do not call Huawei Cloud resource APIs.',
131
+ service,
132
+ operation,
133
+ args: normalizedArgs,
134
+ },
135
+ normalizedArgs,
136
+ options,
137
+ );
130
138
  }
131
139
 
132
140
  if (service.toLowerCase() === 'configure') {
133
141
  const subcommand = operation.toLowerCase();
134
142
  if (
135
- policy.blockedConfigureSubcommands.map((cmd) => cmd.toLowerCase()).includes(subcommand)
136
- && options.allowCredentialRead !== true
143
+ policy.blockedConfigureSubcommands.map((cmd) => cmd.toLowerCase()).includes(subcommand) &&
144
+ options.allowCredentialRead !== true
137
145
  ) {
138
146
  return {
139
147
  decision: 'deny',
140
148
  risk: 'credential',
141
- reason: 'Direct hcloud configure inspection may expose profile credentials. Use the redacted toolkit tools instead.',
149
+ reason:
150
+ 'Direct hcloud configure inspection may expose profile credentials. Use the redacted toolkit tools instead.',
142
151
  };
143
152
  }
144
153
  }
@@ -160,7 +169,8 @@ export function classifyHcloudArgs(args, options = {}) {
160
169
  }
161
170
 
162
171
  const readOnly = hasReadPrefix(operation, policy);
163
- const executionOps = /(^|\.)(Invoke|SyncInvoke|AsyncInvoke|Send|Trigger|Execute|Start|Reboot|Restart|Stop|Publish|Deploy)/i;
172
+ const executionOps =
173
+ /(^|\.)(Invoke|SyncInvoke|AsyncInvoke|Send|Trigger|Execute|Start|Reboot|Restart|Stop|Publish|Deploy)/i;
164
174
  const isExecution = executionOps.test(operation) && !readOnly;
165
175
  const isWrite = !readOnly && hasWritePrefix(operation, policy);
166
176
 
@@ -176,11 +186,30 @@ export function classifyHcloudArgs(args, options = {}) {
176
186
  return {
177
187
  decision: 'deny',
178
188
  risk: 'write',
179
- reason: 'Huawei Cloud write operation blocked until the agent presents a plan and receives explicit user approval.',
189
+ reason:
190
+ 'Huawei Cloud write operation blocked until the agent presents a plan and receives explicit user approval.',
180
191
  };
181
192
  }
182
193
 
183
- const obsutilWrites = ['mb', 'cp', 'mv', 'rm', 'delete', 'mkdir', 'sync', 'restore', 'chattri', 'bucketpolicy', 'lifecycle', 'cors', 'website', 'sign', 'share-add', 'share-update', 'share-rm'];
194
+ const obsutilWrites = [
195
+ 'mb',
196
+ 'cp',
197
+ 'mv',
198
+ 'rm',
199
+ 'delete',
200
+ 'mkdir',
201
+ 'sync',
202
+ 'restore',
203
+ 'chattri',
204
+ 'bucketpolicy',
205
+ 'lifecycle',
206
+ 'cors',
207
+ 'website',
208
+ 'sign',
209
+ 'share-add',
210
+ 'share-update',
211
+ 'share-rm',
212
+ ];
184
213
  const obsutilReads = ['ls', 'stat', 'cat', 'help', 'version'];
185
214
  const isObs = service.toLowerCase() === 'obs' || service.toLowerCase() === 'hcloud obs';
186
215
  const isObsWrite = isObs && obsutilWrites.includes(operation);
@@ -193,64 +222,86 @@ export function classifyHcloudArgs(args, options = {}) {
193
222
  };
194
223
  }
195
224
  if (isObsRead) {
196
- return applyCommandRiskRules({
197
- decision: 'allow',
198
- risk: 'read_only',
199
- reason: 'OBS read-only operation.',
200
- service,
201
- operation,
202
- args: normalizedArgs,
203
- }, normalizedArgs, options);
225
+ return applyCommandRiskRules(
226
+ {
227
+ decision: 'allow',
228
+ risk: 'read_only',
229
+ reason: 'OBS read-only operation.',
230
+ service,
231
+ operation,
232
+ args: normalizedArgs,
233
+ },
234
+ normalizedArgs,
235
+ options,
236
+ );
204
237
  }
205
238
  if (isObsWrite && options.allowWrites) {
206
- return applyCommandRiskRules({
207
- decision: 'allow',
208
- risk: 'write',
209
- reason: 'OBS write operation approved by user.',
210
- service,
211
- operation,
212
- args: normalizedArgs,
213
- }, normalizedArgs, options);
239
+ return applyCommandRiskRules(
240
+ {
241
+ decision: 'allow',
242
+ risk: 'write',
243
+ reason: 'OBS write operation approved by user.',
244
+ service,
245
+ operation,
246
+ args: normalizedArgs,
247
+ },
248
+ normalizedArgs,
249
+ options,
250
+ );
214
251
  }
215
252
 
216
253
  if (isExecution && options.allowWrites) {
217
- return applyCommandRiskRules({
218
- decision: 'allow',
219
- risk: 'execution',
220
- reason: 'Huawei Cloud execution/trigger operation approved by user.',
221
- service,
222
- operation,
223
- args: normalizedArgs,
224
- }, normalizedArgs, options);
254
+ return applyCommandRiskRules(
255
+ {
256
+ decision: 'allow',
257
+ risk: 'execution',
258
+ reason: 'Huawei Cloud execution/trigger operation approved by user.',
259
+ service,
260
+ operation,
261
+ args: normalizedArgs,
262
+ },
263
+ normalizedArgs,
264
+ options,
265
+ );
225
266
  }
226
267
 
227
268
  if (isWrite && options.allowWrites) {
228
- return applyCommandRiskRules({
269
+ return applyCommandRiskRules(
270
+ {
271
+ decision: 'allow',
272
+ risk: 'write',
273
+ reason: 'Huawei Cloud write operation approved by user.',
274
+ service,
275
+ operation,
276
+ args: normalizedArgs,
277
+ },
278
+ normalizedArgs,
279
+ options,
280
+ );
281
+ }
282
+
283
+ return applyCommandRiskRules(
284
+ {
229
285
  decision: 'allow',
230
- risk: 'write',
231
- reason: 'Huawei Cloud write operation approved by user.',
286
+ risk: readOnly ? 'read_only' : 'unknown_read',
287
+ reason: readOnly
288
+ ? 'Command appears to be a read-only Huawei Cloud operation.'
289
+ : 'Command does not match a known write or secret operation; treat output as untrusted and redact it.',
232
290
  service,
233
291
  operation,
234
292
  args: normalizedArgs,
235
- }, normalizedArgs, options);
236
- }
237
-
238
- return applyCommandRiskRules({
239
- decision: 'allow',
240
- risk: readOnly ? 'read_only' : 'unknown_read',
241
- reason: readOnly
242
- ? 'Command appears to be a read-only Huawei Cloud operation.'
243
- : 'Command does not match a known write or secret operation; treat output as untrusted and redact it.',
244
- service,
245
- operation,
246
- args: normalizedArgs,
247
- }, normalizedArgs, options);
293
+ },
294
+ normalizedArgs,
295
+ options,
296
+ );
248
297
  }
249
298
 
250
299
  function splitSimpleCommand(command) {
251
- return String(command)
252
- .match(/"[^"]*"|'[^']*'|\S+/g)
253
- ?.map((part) => part.replace(/^['"]|['"]$/g, '')) || [];
300
+ return (
301
+ String(command)
302
+ .match(/"[^"]*"|'[^']*'|\S+/g)
303
+ ?.map((part) => part.replace(/^['"]|['"]$/g, '')) || []
304
+ );
254
305
  }
255
306
 
256
307
  export function classifyTextCommand(command, options = {}) {
@@ -261,11 +312,15 @@ export function classifyTextCommand(command, options = {}) {
261
312
  return {
262
313
  decision: 'deny',
263
314
  risk: 'credential',
264
- reason: 'Reading Huawei Cloud credential or profile files is blocked. Use redacted profile inspection tools instead.',
315
+ reason:
316
+ 'Reading Huawei Cloud credential or profile files is blocked. Use redacted profile inspection tools instead.',
265
317
  };
266
318
  }
267
319
 
268
- if (/(^|\s)(env|printenv|Get-ChildItem\s+Env:|gci\s+Env:|dir\s+Env:)/i.test(text) && /HUAWEICLOUD|HWC_|HCLOUD|OS_/i.test(text)) {
320
+ if (
321
+ /(^|\s)(env|printenv|Get-ChildItem\s+Env:|gci\s+Env:|dir\s+Env:)/i.test(text) &&
322
+ /HUAWEICLOUD|HWC_|HCLOUD|OS_/i.test(text)
323
+ ) {
269
324
  return {
270
325
  decision: 'deny',
271
326
  risk: 'credential',
@@ -1,8 +1,8 @@
1
1
  import { getCredentials } from './hwlink-api.mjs';
2
+ import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
2
3
 
3
4
  const HDKIT_BASE_URL =
4
- process.env.HDKITSERVICE_ENDPOINT ||
5
- 'https://devkit.huaweicloud.com/rest/developer/server/hdkitservice/';
5
+ process.env.HDKITSERVICE_ENDPOINT || 'https://devkit.huaweicloud.com/rest/developer/server/hdkitservice/';
6
6
 
7
7
  async function hdkitRequest(method, path, body, timeoutMs = 300000) {
8
8
  const { ak, sk, securitytoken } = getCredentials();
@@ -22,12 +22,20 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
22
22
 
23
23
  let resp;
24
24
  try {
25
- resp = await fetch(url, {
25
+ const dispatcher = await getProxyDispatcher(url);
26
+ const fetchOpts = {
26
27
  method,
27
28
  headers,
28
29
  body: body ? JSON.stringify(body) : undefined,
29
30
  signal: controller.signal,
30
- });
31
+ };
32
+ if (dispatcher) {
33
+ fetchOpts.dispatcher = dispatcher;
34
+ const { fetch: undiciFetch } = await import('undici');
35
+ resp = await undiciFetch(url, fetchOpts);
36
+ } else {
37
+ resp = await fetch(url, fetchOpts);
38
+ }
31
39
  } finally {
32
40
  clearTimeout(timer);
33
41
  }
@@ -41,12 +49,10 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
41
49
  }
42
50
 
43
51
  if (!resp.ok) {
44
- const err = new Error(
45
- data.message || `hdkitservice error: ${data.code || resp.status}`
46
- );
52
+ const err = new Error(data.message || `hdkitservice error: ${data.code || resp.status}`);
47
53
  err.code = data.code;
48
54
  err.status = resp.status;
49
- err.traceId = data.trace_id;
55
+ err.traceId = data.traceId; // 后端实际返回驼峰 traceId
50
56
  throw err;
51
57
  }
52
58
 
@@ -83,5 +89,3 @@ export async function hdkitCredentials(sessionId, devStageId, enableSts = true)
83
89
 
84
90
  return await hdkitRequest('POST', 'credentials', body);
85
91
  }
86
-
87
-
@@ -1,5 +1,7 @@
1
1
  import crypto from 'node:crypto';
2
- import { resolveCredentials } from '../auth/credentials.mjs';
2
+
3
+ import { resolveCredentialsWithRuntime } from '../auth/credentials.mjs';
4
+ import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
3
5
 
4
6
  const BASE_URL = process.env.HWLINK_ENDPOINT || 'https://devstation.myhuaweicloud.com';
5
7
 
@@ -13,9 +15,7 @@ function hmacSha256(key, data) {
13
15
 
14
16
  function urlEncode(str) {
15
17
  const hex = (c) => '%' + (c < 16 ? '0' : '') + c.toString(16).toUpperCase();
16
- const noEscape = new Set(
17
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split('')
18
- );
18
+ const noEscape = new Set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split(''));
19
19
  let out = '';
20
20
  for (const ch of str) {
21
21
  const c = ch.codePointAt(0);
@@ -53,9 +53,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
53
53
  .join('/') +
54
54
  '/';
55
55
 
56
- const signedHeaders = securitytoken
57
- ? 'host;x-sdk-date;x-security-token'
58
- : 'host;x-sdk-date';
56
+ const signedHeaders = securitytoken ? 'host;x-sdk-date;x-security-token' : 'host;x-sdk-date';
59
57
  const canonicalHeaders = securitytoken
60
58
  ? `host:${host}\nx-sdk-date:${ts}\nx-security-token:${securitytoken}\n`
61
59
  : `host:${host}\nx-sdk-date:${ts}\n`;
@@ -63,14 +61,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
63
61
  const bodyStr = body ? JSON.stringify(body) : '';
64
62
  const payloadHash = sha256Hex(bodyStr);
65
63
 
66
- const canonicalRequest = [
67
- method,
68
- curi,
69
- cqs,
70
- canonicalHeaders,
71
- signedHeaders,
72
- payloadHash,
73
- ].join('\n');
64
+ const canonicalRequest = [method, curi, cqs, canonicalHeaders, signedHeaders, payloadHash].join('\n');
74
65
 
75
66
  const stringToSign = `SDK-HMAC-SHA256\n${ts}\n${sha256Hex(canonicalRequest)}`;
76
67
  const signature = hmacSha256(sk, stringToSign);
@@ -87,7 +78,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
87
78
  }
88
79
 
89
80
  export function getCredentials() {
90
- const credentials = resolveCredentials();
81
+ const credentials = resolveCredentialsWithRuntime();
91
82
  return { ak: credentials.ak, sk: credentials.sk, securitytoken: credentials.securityToken };
92
83
  }
93
84
 
@@ -96,17 +87,35 @@ async function apiGet(path, query, ak, sk, securitytoken) {
96
87
  const qs = sortedQs(query);
97
88
  const fullPath = qs ? `${path}?${qs}` : path;
98
89
  const headers = signRequest('GET', path, query, undefined, ak, sk, securitytoken);
99
- const resp = await fetch(`${BASE_URL}${fullPath}`, { headers });
90
+ const url = `${BASE_URL}${fullPath}`;
91
+ const dispatcher = await getProxyDispatcher(url);
92
+ const fetchOpts = { headers };
93
+ if (dispatcher) {
94
+ fetchOpts.dispatcher = dispatcher;
95
+ const { fetch: undiciFetch } = await import('undici');
96
+ const resp = await undiciFetch(url, fetchOpts);
97
+ return { status: resp.status, data: await resp.json() };
98
+ }
99
+ const resp = await fetch(url, fetchOpts);
100
100
  return { status: resp.status, data: await resp.json() };
101
101
  }
102
102
 
103
103
  async function apiPost(path, body, ak, sk, securitytoken) {
104
104
  const headers = signRequest('POST', path, {}, body, ak, sk, securitytoken);
105
- const resp = await fetch(`${BASE_URL}${path}`, {
105
+ const url = `${BASE_URL}${path}`;
106
+ const dispatcher = await getProxyDispatcher(url);
107
+ const fetchOpts = {
106
108
  method: 'POST',
107
109
  headers: { ...headers, 'Content-Type': 'application/json' },
108
110
  body: JSON.stringify(body),
109
- });
111
+ };
112
+ if (dispatcher) {
113
+ fetchOpts.dispatcher = dispatcher;
114
+ const { fetch: undiciFetch } = await import('undici');
115
+ const resp = await undiciFetch(url, fetchOpts);
116
+ return { status: resp.status, data: await resp.json() };
117
+ }
118
+ const resp = await fetch(url, fetchOpts);
110
119
  return { status: resp.status, data: await resp.json() };
111
120
  }
112
121
 
@@ -114,7 +123,9 @@ export async function createConnection(envId, ak, sk, securitytoken) {
114
123
  const { status, data } = await apiPost(
115
124
  `/open-api-public/v1/devenvs/${envId}/connections`,
116
125
  { source: 'CLI' },
117
- ak, sk, securitytoken
126
+ ak,
127
+ sk,
128
+ securitytoken,
118
129
  );
119
130
 
120
131
  if (status !== 200 || data?.error_code !== '0000' || !data?.result?.connection_id) {
@@ -129,13 +140,12 @@ export async function createConnection(envId, ak, sk, securitytoken) {
129
140
  const { data: getData } = await apiGet(
130
141
  `/open-api-public/v1/devenvs/${envId}/connections/${connectionId}`,
131
142
  {},
132
- ak, sk, securitytoken
143
+ ak,
144
+ sk,
145
+ securitytoken,
133
146
  );
134
147
 
135
- if (
136
- getData?.result?.connection_info?.url &&
137
- getData.result.connection_info.extensions?.source != null
138
- ) {
148
+ if (getData?.result?.connection_info?.url && getData.result.connection_info.extensions?.source != null) {
139
149
  const u = new URL(getData.result.connection_info.url);
140
150
  u.searchParams.set('source', String(getData.result.connection_info.extensions.source));
141
151
  process.stderr.write(`\rConnection ${connectionId} established (${i}s).\n`);
@@ -149,5 +159,3 @@ export async function createConnection(envId, ak, sk, securitytoken) {
149
159
 
150
160
  throw new Error(`Timed out waiting for connection ${connectionId}`);
151
161
  }
152
-
153
-
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python3
2
+ import hashlib, http.server, json, os, sys
3
+ class UploadHandler(http.server.BaseHTTPRequestHandler):
4
+ def do_POST(self):
5
+ if self.path != '/upload':
6
+ self.send_error(404)
7
+ return
8
+ target_path = self.headers.get('X-Target-Path', '/workspace/upload.tar.gz')
9
+ content_length = int(self.headers.get('Content-Length', 0))
10
+ os.makedirs(os.path.dirname(target_path), exist_ok=True)
11
+ md5 = hashlib.md5()
12
+ bytes_written = 0
13
+ with open(target_path, 'wb') as f:
14
+ remaining = content_length
15
+ while remaining > 0:
16
+ chunk = self.rfile.read(min(remaining, 65536))
17
+ if not chunk:
18
+ break
19
+ f.write(chunk)
20
+ md5.update(chunk)
21
+ bytes_written += len(chunk)
22
+ remaining -= len(chunk)
23
+ result = json.dumps({'ok': True, 'path': target_path, 'bytes': bytes_written, 'md5': md5.hexdigest()})
24
+ self.send_response(200)
25
+ self.send_header('Content-Type', 'application/json')
26
+ self.send_header('Content-Length', str(len(result)))
27
+ self.end_headers()
28
+ self.wfile.write(result.encode())
29
+ def do_GET(self):
30
+ if self.path == '/health':
31
+ body = b'ok'
32
+ self.send_response(200)
33
+ self.send_header('Content-Length', str(len(body)))
34
+ self.end_headers()
35
+ self.wfile.write(body)
36
+ else:
37
+ self.send_error(404)
38
+ def log_message(self, format, *args):
39
+ pass
40
+ if __name__ == '__main__':
41
+ port = int(sys.argv[1]) if len(sys.argv) > 1 else 8888
42
+ token = sys.argv[2] if len(sys.argv) > 2 else ''
43
+ server = http.server.HTTPServer(('127.0.0.1', port), UploadHandler)
44
+ server.upload_token = token
45
+ server.serve_forever()