huaweicloud-devkit 1.0.3-dev.0 → 1.0.3-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 +99 -41
  3. package/README.zh-CN.md +99 -41
  4. package/bin/setup.cjs +5 -3
  5. package/integrations/opencode/opencode.json +1 -4
  6. package/package.json +61 -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 +123 -2
  70. package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
  71. package/plugins/huaweicloud-core/src/auth/service.mjs +1 -1
  72. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
  73. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +25 -7
  74. package/plugins/huaweicloud-core/src/icon-library.mjs +171 -0
  75. package/plugins/huaweicloud-core/src/mcp-server.mjs +30 -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 +122 -68
  80. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +14 -10
  81. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +34 -27
  82. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  83. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +521 -8
  84. package/plugins/huaweicloud-core/src/search-market.mjs +53 -14
  85. package/plugins/huaweicloud-core/src/setup-cli.mjs +1741 -215
  86. package/plugins/huaweicloud-core/src/tools.mjs +667 -123
  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 +12 -15
  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 +267 -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 +9 -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
@@ -17,9 +17,13 @@ function regexFrom(pattern) {
17
17
  }
18
18
 
19
19
  function isSecretKeyName(key, policy = DEFAULT_POLICY) {
20
- const normalized = String(key).toLowerCase().replace(/[^a-z0-9]/g, '');
20
+ const normalized = String(key)
21
+ .toLowerCase()
22
+ .replace(/[^a-z0-9]/g, '');
21
23
  if (
22
- /access.*key|secret.*key|security.*token|xauth.*token|authorization|password|passwd|adminpass|credential|private.*key/.test(normalized)
24
+ /access.*key|secret.*key|security.*token|xauth.*token|authorization|password|passwd|adminpass|credential|private.*key/.test(
25
+ normalized,
26
+ )
23
27
  ) {
24
28
  return true;
25
29
  }
@@ -28,7 +32,10 @@ function isSecretKeyName(key, policy = DEFAULT_POLICY) {
28
32
 
29
33
  function redactString(text) {
30
34
  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>')
35
+ .replace(
36
+ /((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|authorization|password|passwd|adminPass|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi,
37
+ '$1<redacted>',
38
+ )
32
39
  .replace(/(AK|SK)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;]+)/g, '$1=<redacted>');
33
40
  }
34
41
 
@@ -78,15 +85,11 @@ function matchesAny(value, patterns) {
78
85
 
79
86
  function hasWritePrefix(operation, policy) {
80
87
  const normalized = String(operation);
81
- return policy.writeOperationPrefixes.some((prefix) =>
82
- new RegExp(`(^|[A-Za-z0-9])${prefix}`, 'i').test(normalized),
83
- );
88
+ return policy.writeOperationPrefixes.some((prefix) => new RegExp(`(^|[A-Za-z0-9])${prefix}`, 'i').test(normalized));
84
89
  }
85
90
 
86
91
  function hasReadPrefix(operation, policy) {
87
- return policy.readOperationPrefixes.some((prefix) =>
88
- new RegExp(`^${prefix}`, 'i').test(operation),
89
- );
92
+ return policy.readOperationPrefixes.some((prefix) => new RegExp(`^${prefix}`, 'i').test(operation));
90
93
  }
91
94
 
92
95
  function isLocalMetadataCommand(args) {
@@ -119,26 +122,31 @@ export function classifyHcloudArgs(args, options = {}) {
119
122
  }
120
123
 
121
124
  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);
125
+ return applyCommandRiskRules(
126
+ {
127
+ decision: 'allow',
128
+ risk: 'local_metadata',
129
+ reason: 'KooCLI local help and version commands are read-only and do not call Huawei Cloud resource APIs.',
130
+ service,
131
+ operation,
132
+ args: normalizedArgs,
133
+ },
134
+ normalizedArgs,
135
+ options,
136
+ );
130
137
  }
131
138
 
132
139
  if (service.toLowerCase() === 'configure') {
133
140
  const subcommand = operation.toLowerCase();
134
141
  if (
135
- policy.blockedConfigureSubcommands.map((cmd) => cmd.toLowerCase()).includes(subcommand)
136
- && options.allowCredentialRead !== true
142
+ policy.blockedConfigureSubcommands.map((cmd) => cmd.toLowerCase()).includes(subcommand) &&
143
+ options.allowCredentialRead !== true
137
144
  ) {
138
145
  return {
139
146
  decision: 'deny',
140
147
  risk: 'credential',
141
- reason: 'Direct hcloud configure inspection may expose profile credentials. Use the redacted toolkit tools instead.',
148
+ reason:
149
+ 'Direct hcloud configure inspection may expose profile credentials. Use the redacted toolkit tools instead.',
142
150
  };
143
151
  }
144
152
  }
@@ -160,7 +168,8 @@ export function classifyHcloudArgs(args, options = {}) {
160
168
  }
161
169
 
162
170
  const readOnly = hasReadPrefix(operation, policy);
163
- const executionOps = /(^|\.)(Invoke|SyncInvoke|AsyncInvoke|Send|Trigger|Execute|Start|Reboot|Restart|Stop|Publish|Deploy)/i;
171
+ const executionOps =
172
+ /(^|\.)(Invoke|SyncInvoke|AsyncInvoke|Send|Trigger|Execute|Start|Reboot|Restart|Stop|Publish|Deploy)/i;
164
173
  const isExecution = executionOps.test(operation) && !readOnly;
165
174
  const isWrite = !readOnly && hasWritePrefix(operation, policy);
166
175
 
@@ -176,11 +185,30 @@ export function classifyHcloudArgs(args, options = {}) {
176
185
  return {
177
186
  decision: 'deny',
178
187
  risk: 'write',
179
- reason: 'Huawei Cloud write operation blocked until the agent presents a plan and receives explicit user approval.',
188
+ reason:
189
+ 'Huawei Cloud write operation blocked until the agent presents a plan and receives explicit user approval.',
180
190
  };
181
191
  }
182
192
 
183
- const obsutilWrites = ['mb', 'cp', 'mv', 'rm', 'delete', 'mkdir', 'sync', 'restore', 'chattri', 'bucketpolicy', 'lifecycle', 'cors', 'website', 'sign', 'share-add', 'share-update', 'share-rm'];
193
+ const obsutilWrites = [
194
+ 'mb',
195
+ 'cp',
196
+ 'mv',
197
+ 'rm',
198
+ 'delete',
199
+ 'mkdir',
200
+ 'sync',
201
+ 'restore',
202
+ 'chattri',
203
+ 'bucketpolicy',
204
+ 'lifecycle',
205
+ 'cors',
206
+ 'website',
207
+ 'sign',
208
+ 'share-add',
209
+ 'share-update',
210
+ 'share-rm',
211
+ ];
184
212
  const obsutilReads = ['ls', 'stat', 'cat', 'help', 'version'];
185
213
  const isObs = service.toLowerCase() === 'obs' || service.toLowerCase() === 'hcloud obs';
186
214
  const isObsWrite = isObs && obsutilWrites.includes(operation);
@@ -193,64 +221,86 @@ export function classifyHcloudArgs(args, options = {}) {
193
221
  };
194
222
  }
195
223
  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);
224
+ return applyCommandRiskRules(
225
+ {
226
+ decision: 'allow',
227
+ risk: 'read_only',
228
+ reason: 'OBS read-only operation.',
229
+ service,
230
+ operation,
231
+ args: normalizedArgs,
232
+ },
233
+ normalizedArgs,
234
+ options,
235
+ );
204
236
  }
205
237
  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);
238
+ return applyCommandRiskRules(
239
+ {
240
+ decision: 'allow',
241
+ risk: 'write',
242
+ reason: 'OBS write operation approved by user.',
243
+ service,
244
+ operation,
245
+ args: normalizedArgs,
246
+ },
247
+ normalizedArgs,
248
+ options,
249
+ );
214
250
  }
215
251
 
216
252
  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);
253
+ return applyCommandRiskRules(
254
+ {
255
+ decision: 'allow',
256
+ risk: 'execution',
257
+ reason: 'Huawei Cloud execution/trigger operation approved by user.',
258
+ service,
259
+ operation,
260
+ args: normalizedArgs,
261
+ },
262
+ normalizedArgs,
263
+ options,
264
+ );
225
265
  }
226
266
 
227
267
  if (isWrite && options.allowWrites) {
228
- return applyCommandRiskRules({
268
+ return applyCommandRiskRules(
269
+ {
270
+ decision: 'allow',
271
+ risk: 'write',
272
+ reason: 'Huawei Cloud write operation approved by user.',
273
+ service,
274
+ operation,
275
+ args: normalizedArgs,
276
+ },
277
+ normalizedArgs,
278
+ options,
279
+ );
280
+ }
281
+
282
+ return applyCommandRiskRules(
283
+ {
229
284
  decision: 'allow',
230
- risk: 'write',
231
- reason: 'Huawei Cloud write operation approved by user.',
285
+ risk: readOnly ? 'read_only' : 'unknown_read',
286
+ reason: readOnly
287
+ ? 'Command appears to be a read-only Huawei Cloud operation.'
288
+ : 'Command does not match a known write or secret operation; treat output as untrusted and redact it.',
232
289
  service,
233
290
  operation,
234
291
  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);
292
+ },
293
+ normalizedArgs,
294
+ options,
295
+ );
248
296
  }
249
297
 
250
298
  function splitSimpleCommand(command) {
251
- return String(command)
252
- .match(/"[^"]*"|'[^']*'|\S+/g)
253
- ?.map((part) => part.replace(/^['"]|['"]$/g, '')) || [];
299
+ return (
300
+ String(command)
301
+ .match(/"[^"]*"|'[^']*'|\S+/g)
302
+ ?.map((part) => part.replace(/^['"]|['"]$/g, '')) || []
303
+ );
254
304
  }
255
305
 
256
306
  export function classifyTextCommand(command, options = {}) {
@@ -261,11 +311,15 @@ export function classifyTextCommand(command, options = {}) {
261
311
  return {
262
312
  decision: 'deny',
263
313
  risk: 'credential',
264
- reason: 'Reading Huawei Cloud credential or profile files is blocked. Use redacted profile inspection tools instead.',
314
+ reason:
315
+ 'Reading Huawei Cloud credential or profile files is blocked. Use redacted profile inspection tools instead.',
265
316
  };
266
317
  }
267
318
 
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)) {
319
+ if (
320
+ /(^|\s)(env|printenv|Get-ChildItem\s+Env:|gci\s+Env:|dir\s+Env:)/i.test(text) &&
321
+ /HUAWEICLOUD|HWC_|HCLOUD|OS_/i.test(text)
322
+ ) {
269
323
  return {
270
324
  decision: 'deny',
271
325
  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,6 @@
1
1
  import crypto from 'node:crypto';
2
- import { resolveCredentials } from '../auth/credentials.mjs';
2
+ import { resolveCredentialsWithRuntime } from '../auth/credentials.mjs';
3
+ import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
3
4
 
4
5
  const BASE_URL = process.env.HWLINK_ENDPOINT || 'https://devstation.myhuaweicloud.com';
5
6
 
@@ -13,9 +14,7 @@ function hmacSha256(key, data) {
13
14
 
14
15
  function urlEncode(str) {
15
16
  const hex = (c) => '%' + (c < 16 ? '0' : '') + c.toString(16).toUpperCase();
16
- const noEscape = new Set(
17
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split('')
18
- );
17
+ const noEscape = new Set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split(''));
19
18
  let out = '';
20
19
  for (const ch of str) {
21
20
  const c = ch.codePointAt(0);
@@ -53,9 +52,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
53
52
  .join('/') +
54
53
  '/';
55
54
 
56
- const signedHeaders = securitytoken
57
- ? 'host;x-sdk-date;x-security-token'
58
- : 'host;x-sdk-date';
55
+ const signedHeaders = securitytoken ? 'host;x-sdk-date;x-security-token' : 'host;x-sdk-date';
59
56
  const canonicalHeaders = securitytoken
60
57
  ? `host:${host}\nx-sdk-date:${ts}\nx-security-token:${securitytoken}\n`
61
58
  : `host:${host}\nx-sdk-date:${ts}\n`;
@@ -63,14 +60,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
63
60
  const bodyStr = body ? JSON.stringify(body) : '';
64
61
  const payloadHash = sha256Hex(bodyStr);
65
62
 
66
- const canonicalRequest = [
67
- method,
68
- curi,
69
- cqs,
70
- canonicalHeaders,
71
- signedHeaders,
72
- payloadHash,
73
- ].join('\n');
63
+ const canonicalRequest = [method, curi, cqs, canonicalHeaders, signedHeaders, payloadHash].join('\n');
74
64
 
75
65
  const stringToSign = `SDK-HMAC-SHA256\n${ts}\n${sha256Hex(canonicalRequest)}`;
76
66
  const signature = hmacSha256(sk, stringToSign);
@@ -87,7 +77,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
87
77
  }
88
78
 
89
79
  export function getCredentials() {
90
- const credentials = resolveCredentials();
80
+ const credentials = resolveCredentialsWithRuntime();
91
81
  return { ak: credentials.ak, sk: credentials.sk, securitytoken: credentials.securityToken };
92
82
  }
93
83
 
@@ -96,17 +86,35 @@ async function apiGet(path, query, ak, sk, securitytoken) {
96
86
  const qs = sortedQs(query);
97
87
  const fullPath = qs ? `${path}?${qs}` : path;
98
88
  const headers = signRequest('GET', path, query, undefined, ak, sk, securitytoken);
99
- const resp = await fetch(`${BASE_URL}${fullPath}`, { headers });
89
+ const url = `${BASE_URL}${fullPath}`;
90
+ const dispatcher = await getProxyDispatcher(url);
91
+ const fetchOpts = { headers };
92
+ if (dispatcher) {
93
+ fetchOpts.dispatcher = dispatcher;
94
+ const { fetch: undiciFetch } = await import('undici');
95
+ const resp = await undiciFetch(url, fetchOpts);
96
+ return { status: resp.status, data: await resp.json() };
97
+ }
98
+ const resp = await fetch(url, fetchOpts);
100
99
  return { status: resp.status, data: await resp.json() };
101
100
  }
102
101
 
103
102
  async function apiPost(path, body, ak, sk, securitytoken) {
104
103
  const headers = signRequest('POST', path, {}, body, ak, sk, securitytoken);
105
- const resp = await fetch(`${BASE_URL}${path}`, {
104
+ const url = `${BASE_URL}${path}`;
105
+ const dispatcher = await getProxyDispatcher(url);
106
+ const fetchOpts = {
106
107
  method: 'POST',
107
108
  headers: { ...headers, 'Content-Type': 'application/json' },
108
109
  body: JSON.stringify(body),
109
- });
110
+ };
111
+ if (dispatcher) {
112
+ fetchOpts.dispatcher = dispatcher;
113
+ const { fetch: undiciFetch } = await import('undici');
114
+ const resp = await undiciFetch(url, fetchOpts);
115
+ return { status: resp.status, data: await resp.json() };
116
+ }
117
+ const resp = await fetch(url, fetchOpts);
110
118
  return { status: resp.status, data: await resp.json() };
111
119
  }
112
120
 
@@ -114,7 +122,9 @@ export async function createConnection(envId, ak, sk, securitytoken) {
114
122
  const { status, data } = await apiPost(
115
123
  `/open-api-public/v1/devenvs/${envId}/connections`,
116
124
  { source: 'CLI' },
117
- ak, sk, securitytoken
125
+ ak,
126
+ sk,
127
+ securitytoken,
118
128
  );
119
129
 
120
130
  if (status !== 200 || data?.error_code !== '0000' || !data?.result?.connection_id) {
@@ -129,13 +139,12 @@ export async function createConnection(envId, ak, sk, securitytoken) {
129
139
  const { data: getData } = await apiGet(
130
140
  `/open-api-public/v1/devenvs/${envId}/connections/${connectionId}`,
131
141
  {},
132
- ak, sk, securitytoken
142
+ ak,
143
+ sk,
144
+ securitytoken,
133
145
  );
134
146
 
135
- if (
136
- getData?.result?.connection_info?.url &&
137
- getData.result.connection_info.extensions?.source != null
138
- ) {
147
+ if (getData?.result?.connection_info?.url && getData.result.connection_info.extensions?.source != null) {
139
148
  const u = new URL(getData.result.connection_info.url);
140
149
  u.searchParams.set('source', String(getData.result.connection_info.extensions.source));
141
150
  process.stderr.write(`\rConnection ${connectionId} established (${i}s).\n`);
@@ -149,5 +158,3 @@ export async function createConnection(envId, ak, sk, securitytoken) {
149
158
 
150
159
  throw new Error(`Timed out waiting for connection ${connectionId}`);
151
160
  }
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()