huaweicloud-devkit 1.0.2-next.2 → 1.0.2-next.21

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 +81 -51
  3. package/README.zh-CN.md +81 -49
  4. package/bin/setup.cjs +5 -3
  5. package/integrations/opencode/opencode.json +1 -4
  6. package/package.json +61 -44
  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-cloud-find-skills/SKILL.md +13 -12
  23. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +64 -17
  24. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
  25. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
  26. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
  27. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +39 -30
  28. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
  29. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
  30. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +39 -36
  31. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +15 -4
  32. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
  33. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
  34. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +40 -39
  35. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
  36. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
  37. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
  38. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
  39. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
  40. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +27 -27
  41. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
  42. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
  43. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +25 -25
  44. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +45 -44
  45. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
  46. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
  47. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
  48. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
  49. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +63 -58
  50. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +127 -42
  51. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +26 -26
  52. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
  53. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +13 -12
  54. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
  55. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
  56. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +18 -18
  57. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
  58. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
  59. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +17 -1
  60. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
  61. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +27 -1
  62. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
  63. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +56 -53
  64. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
  65. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
  66. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
  67. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
  68. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
  69. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +33 -33
  70. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
  71. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +108 -5
  72. package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
  73. package/plugins/huaweicloud-core/src/auth/service.mjs +1 -1
  74. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
  75. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +25 -7
  76. package/plugins/huaweicloud-core/src/icon-library.mjs +171 -0
  77. package/plugins/huaweicloud-core/src/mcp-server.mjs +30 -3
  78. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +20 -102
  79. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +11 -4
  80. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
  81. package/plugins/huaweicloud-core/src/safety-policy.mjs +122 -68
  82. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +9 -9
  83. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +24 -26
  84. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  85. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +510 -10
  86. package/plugins/huaweicloud-core/src/search-market.mjs +46 -12
  87. package/plugins/huaweicloud-core/src/setup-cli.mjs +1251 -181
  88. package/plugins/huaweicloud-core/src/tools.mjs +600 -122
  89. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
  90. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
  91. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +12 -15
  92. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
  93. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
  94. package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +267 -0
  95. package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
  96. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +9 -5
@@ -1,5 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { classifyHcloudArgs, redactSecrets, assertAllowed } from './safety-policy.mjs';
3
+ import { getProxySettings } from './proxy/proxy-config.mjs';
3
4
 
4
5
  const DEFAULT_TIMEOUT_MS = 60_000;
5
6
  const DEFAULT_FORCE_KILL_AFTER_MS = 2_000;
@@ -46,7 +47,7 @@ export async function runHcloud(args, options = {}) {
46
47
  attempts: attempt + 1,
47
48
  };
48
49
  }
49
- await wait((options.retryBaseDelayMs ?? 500) * (2 ** attempt));
50
+ await wait((options.retryBaseDelayMs ?? 500) * 2 ** attempt);
50
51
  }
51
52
  throw new Error('Unreachable retry state.');
52
53
  }
@@ -59,12 +60,20 @@ function runHcloudOnce(plan, options) {
59
60
  const cwd = options.cwd || undefined;
60
61
 
61
62
  return new Promise((resolve) => {
63
+ const proxySettings = getProxySettings();
64
+ const proxyEnv = {};
65
+ if (proxySettings) {
66
+ if (proxySettings.https_proxy) proxyEnv.HTTPS_PROXY = proxySettings.https_proxy;
67
+ if (proxySettings.http_proxy) proxyEnv.HTTP_PROXY = proxySettings.http_proxy;
68
+ if (proxySettings.no_proxy) proxyEnv.NO_PROXY = proxySettings.no_proxy;
69
+ }
62
70
  const child = spawn(executable, [...executableArgs, ...plan.rawArgs], {
63
71
  shell: false,
64
72
  windowsHide: true,
65
73
  cwd,
66
74
  env: {
67
75
  ...process.env,
76
+ ...proxyEnv,
68
77
  ...options.env,
69
78
  },
70
79
  });
@@ -151,7 +160,10 @@ function runHcloudOnce(plan, options) {
151
160
  return;
152
161
  }
153
162
  finish({
154
- ok: code === 0 || (code !== 0 && /successfully|succ?ess.*\[200\]|create bucket successfully|upload successfully/i.test(stdout + stderr)),
163
+ ok:
164
+ code === 0 ||
165
+ (code !== 0 &&
166
+ /successfully|succ?ess.*\[200\]|create bucket successfully|upload successfully/i.test(stdout + stderr)),
155
167
  exitCode: code,
156
168
  signal,
157
169
  stdout: redactOutput(stdout),
@@ -165,7 +177,9 @@ function runHcloudOnce(plan, options) {
165
177
  function isRetryableNetworkError(result) {
166
178
  if (result.code === 'TIMEOUT') return false;
167
179
  const text = `${result.error || ''}\n${result.stdout || ''}\n${result.stderr || ''}`;
168
- return /\[NETWORK_ERROR\]|connection timed out|ECONNRESET|ETIMEDOUT|temporary failure|TLS handshake timeout/i.test(text);
180
+ return /\[NETWORK_ERROR\]|connection timed out|ECONNRESET|ETIMEDOUT|temporary failure|TLS handshake timeout/i.test(
181
+ text,
182
+ );
169
183
  }
170
184
 
171
185
  function wait(ms) {
@@ -194,7 +208,11 @@ const REQUIRED_PARAMS = {
194
208
  'ECS CreateServers': ['server.flavorRef', 'server.imageRef', 'server.nics.1.subnet_id'],
195
209
  'VPC CreateVpc': ['vpc.cidr'],
196
210
  'VPC CreateSubnet': ['subnet.vpc_id', 'subnet.cidr'],
197
- 'VPC CreateSecurityGroupRule': ['security_group_rule.security_group_id', 'security_group_rule.direction', 'security_group_rule.protocol'],
211
+ 'VPC CreateSecurityGroupRule': [
212
+ 'security_group_rule.security_group_id',
213
+ 'security_group_rule.direction',
214
+ 'security_group_rule.protocol',
215
+ ],
198
216
  'EIP CreatePublicip': ['bandwidth.share_type', 'publicip.type'],
199
217
  'FunctionGraph CreateFunction': ['func_name', 'runtime', 'handler', 'memory_size', 'package', 'timeout'],
200
218
  'FunctionGraph CreateFunctionTrigger': ['function_urn', 'trigger_type_code'],
@@ -208,9 +226,9 @@ const PARAM_VALUE_HINTS = {
208
226
  'server.imageRef': 'Run `hcloud IMS ListImages --cli-region=<r> --__imagetype=gold` to find valid image IDs',
209
227
  'server.nics.1.subnet_id': 'Run `hcloud VPC ListSubnets --cli-region=<r>` to find subnet IDs',
210
228
  'subnet.vpc_id': 'Run `hcloud VPC ListVpcs --cli-region=<r>` to find VPC IDs',
211
- 'func_name': 'Function name must be unique within project',
212
- 'runtime': 'Run `hcloud FunctionGraph ListRuntimes` to see available runtimes',
213
- 'spec_id': 'APIG spec: BASIC (no public IP) or PROFESSIONAL (requires --loadbalancer_provider)',
229
+ func_name: 'Function name must be unique within project',
230
+ runtime: 'Run `hcloud FunctionGraph ListRuntimes` to see available runtimes',
231
+ spec_id: 'APIG spec: BASIC (no public IP) or PROFESSIONAL (requires --loadbalancer_provider)',
214
232
  'obs://': 'Bucket name must be globally unique and DNS-compliant (lowercase, numbers, hyphens only)',
215
233
  };
216
234
 
@@ -0,0 +1,171 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { getProxyDispatcher } from './proxy/proxy-agent.mjs';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const SNAPSHOT_PATH = join(__dirname, 'data', 'icons-manifest.v1.json');
8
+ const MANIFEST_URL = 'https://open.huaweicloud.com/openplatform/icons/manifest.v1.json';
9
+ const ICONS_PAGE_URL = 'https://open.huaweicloud.com/openplatform/icons.html';
10
+ const HTTP_TIMEOUT_MS = 10000;
11
+ const MAX_RESULTS = 5;
12
+
13
+ let cachedManifest = null;
14
+
15
+ export function clearIconCache() {
16
+ cachedManifest = null;
17
+ }
18
+
19
+ async function fetchLiveManifest() {
20
+ const controller = new AbortController();
21
+ const timer = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS);
22
+ try {
23
+ const dispatcher = await getProxyDispatcher(MANIFEST_URL);
24
+ const fetchOpts = {
25
+ headers: { 'User-Agent': 'huaweicloud-devkit/1.0' },
26
+ signal: controller.signal,
27
+ };
28
+ if (dispatcher) fetchOpts.dispatcher = dispatcher;
29
+ const resp = await fetch(MANIFEST_URL, fetchOpts);
30
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
31
+ const data = await resp.json();
32
+ if (!data || !Array.isArray(data.icons)) {
33
+ throw new Error('Manifest is missing the icons array.');
34
+ }
35
+ return data;
36
+ } finally {
37
+ clearTimeout(timer);
38
+ }
39
+ }
40
+
41
+ function loadSnapshot() {
42
+ const manifest = JSON.parse(readFileSync(SNAPSHOT_PATH, 'utf8'));
43
+ if (!manifest || !Array.isArray(manifest.icons)) {
44
+ throw new Error('Bundled icons manifest is corrupted.');
45
+ }
46
+ return manifest;
47
+ }
48
+
49
+ async function loadManifest() {
50
+ if (cachedManifest) return cachedManifest;
51
+ if (process.env.HUAWEICLOUD_ICONS_OFFLINE === '1') {
52
+ cachedManifest = { manifest: loadSnapshot(), source: 'snapshot' };
53
+ return cachedManifest;
54
+ }
55
+ try {
56
+ const live = await fetchLiveManifest();
57
+ cachedManifest = { manifest: live, source: 'live' };
58
+ } catch {
59
+ cachedManifest = { manifest: loadSnapshot(), source: 'snapshot' };
60
+ }
61
+ return cachedManifest;
62
+ }
63
+
64
+ function normalizeToken(token) {
65
+ return token.toLowerCase();
66
+ }
67
+
68
+ function scoreIcon(icon, tokens, index) {
69
+ let total = 0;
70
+ const matched = [];
71
+ for (const token of tokens) {
72
+ let best = 0;
73
+ const id = (icon.id || '').toLowerCase();
74
+ const name = (icon.name || '').toLowerCase();
75
+ if (id === token || (index.aliases[token] || []).includes(id)) {
76
+ best = 10;
77
+ } else if (name === token) {
78
+ best = 9;
79
+ } else if (name.startsWith(token)) {
80
+ best = 8;
81
+ } else if (name.includes(token)) {
82
+ best = 7;
83
+ } else if (id.includes(token)) {
84
+ best = 5;
85
+ } else if ((index.aliases[id] || []).some((a) => a.includes(token))) {
86
+ best = 6;
87
+ } else if ((index.tags[id] || []).some((t) => t.includes(token))) {
88
+ best = 4;
89
+ } else if ((index.categories[id] || '').includes(token)) {
90
+ best = 3;
91
+ } else if ((index.descriptions[id] || '').includes(token)) {
92
+ best = 2;
93
+ }
94
+ if (best > 0) {
95
+ total += best;
96
+ matched.push(token);
97
+ }
98
+ }
99
+ return [total, matched];
100
+ }
101
+
102
+ export async function getServiceIcon(service = '', category = '') {
103
+ const query = String(service || '').trim();
104
+ const catFilter = String(category || '')
105
+ .trim()
106
+ .toLowerCase();
107
+ if (!query && !catFilter) {
108
+ return {
109
+ ok: false,
110
+ error:
111
+ 'service or category is required. Examples: ecs, obs, modelarts, 对象存储, 虚拟私有云, or a category such as 计算 / 存储 / 人工智能.',
112
+ iconsPageUrl: ICONS_PAGE_URL,
113
+ };
114
+ }
115
+ const { manifest, source } = await loadManifest();
116
+ const tokens = query.replace(/[,;]/g, ' ').split(/\s+/).filter(Boolean).map(normalizeToken);
117
+
118
+ const index = { aliases: {}, tags: {}, categories: {}, descriptions: {} };
119
+ for (const icon of manifest.icons) {
120
+ const id = (icon.id || '').toLowerCase();
121
+ index.aliases[id] = (icon.aliases || []).map((a) => String(a).toLowerCase());
122
+ index.tags[id] = (icon.tags || []).map((t) => String(t).toLowerCase());
123
+ index.categories[id] = [
124
+ String(icon.category || '').toLowerCase(),
125
+ String(icon.subcategory || '').toLowerCase(),
126
+ ].join(' ');
127
+ index.descriptions[id] = String(icon.description || '').toLowerCase();
128
+ }
129
+
130
+ const results = [];
131
+ for (const icon of manifest.icons) {
132
+ const categoryMatches = !catFilter || String(icon.category || '').toLowerCase() === catFilter;
133
+ if (!categoryMatches) continue;
134
+ const [score, matched] = tokens.length ? scoreIcon(icon, tokens, index) : [0, []];
135
+ if (tokens.length && score === 0) continue;
136
+ results.push({
137
+ id: icon.id,
138
+ name: icon.name,
139
+ category: icon.category,
140
+ subcategory: icon.subcategory || undefined,
141
+ description: icon.description || undefined,
142
+ aliases: icon.aliases,
143
+ product_url: icon.product_url,
144
+ logo: {
145
+ source_url: icon.logo?.source_url,
146
+ local_path: icon.logo?.local_path,
147
+ },
148
+ architecture: icon.architecture
149
+ ? { status: icon.architecture.status, local_path: icon.architecture.local_path }
150
+ : undefined,
151
+ score,
152
+ matched,
153
+ });
154
+ }
155
+ results.sort((a, b) => b.score - a.score);
156
+
157
+ return {
158
+ ok: true,
159
+ query,
160
+ category: catFilter || undefined,
161
+ source,
162
+ manifestGeneratedAt: manifest.generated_at,
163
+ count: results.length,
164
+ iconsPageUrl: ICONS_PAGE_URL,
165
+ note:
166
+ source === 'live'
167
+ ? 'Live manifest from open.huaweicloud.com.'
168
+ : 'Live manifest unreachable; using bundled snapshot. Upgrade the package to refresh the bundled snapshot.',
169
+ results: results.slice(0, MAX_RESULTS),
170
+ };
171
+ }
@@ -1,10 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import { stdin, stdout } from 'node:process';
3
- import { rmSync, existsSync } from 'node:fs';
4
- import { resolve, dirname } from 'node:path';
3
+ import { rmSync, existsSync, readFileSync } from 'node:fs';
4
+ import { resolve, dirname, join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
7
7
 
8
+ try {
9
+ const { readProxyConfig } = await import('./proxy/proxy-config.mjs');
10
+ const proxyConfig = readProxyConfig();
11
+ if (proxyConfig) {
12
+ if (proxyConfig.https_proxy || proxyConfig.HTTPS_PROXY) {
13
+ process.env.HTTPS_PROXY = process.env.HTTPS_PROXY || proxyConfig.https_proxy || proxyConfig.HTTPS_PROXY;
14
+ }
15
+ if (proxyConfig.http_proxy || proxyConfig.HTTP_PROXY) {
16
+ process.env.HTTP_PROXY = process.env.HTTP_PROXY || proxyConfig.http_proxy || proxyConfig.HTTP_PROXY;
17
+ }
18
+ }
19
+ } catch {}
20
+
8
21
  // The MCP server is now loaded by a live agent session. Clear the install marker
9
22
  // in this plugin dir so `doctor` no longer reports "restart needed".
10
23
  try {
@@ -13,6 +26,20 @@ try {
13
26
  if (existsSync(marker)) rmSync(marker, { force: true });
14
27
  } catch {}
15
28
 
29
+ const __dirname = dirname(fileURLToPath(import.meta.url));
30
+ const pluginRoot = resolve(__dirname, '..');
31
+ const packageRoot = resolve(pluginRoot, '..', '..');
32
+ let pkgVersion = '0.0.0';
33
+ for (const base of [pluginRoot, packageRoot]) {
34
+ try {
35
+ const version = JSON.parse(readFileSync(join(base, 'package.json'), 'utf8')).version;
36
+ if (version) {
37
+ pkgVersion = version;
38
+ break;
39
+ }
40
+ } catch {}
41
+ }
42
+
16
43
  let buffer = Buffer.alloc(0);
17
44
  let useContentLengthFraming = true;
18
45
 
@@ -90,7 +117,7 @@ async function dispatch(method, params) {
90
117
  },
91
118
  serverInfo: {
92
119
  name: 'huaweicloud-devkit',
93
- version: '0.1.0',
120
+ version: pkgVersion,
94
121
  },
95
122
  };
96
123
  }
@@ -3,6 +3,15 @@ import { getProxyUrlForTarget } from './proxy-config.mjs';
3
3
  let cachedDispatcher = undefined;
4
4
  let cachedDispatcherProxyUrl = null;
5
5
 
6
+ async function importUndici() {
7
+ try {
8
+ /* eslint-disable-next-line n/no-missing-import */
9
+ return await import('node:undici');
10
+ } catch {
11
+ return await import('undici');
12
+ }
13
+ }
14
+
6
15
  export async function getProxyDispatcher(targetUrl) {
7
16
  const proxyUrl = getProxyUrlForTarget(targetUrl);
8
17
  if (!proxyUrl) return undefined;
@@ -11,7 +20,7 @@ export async function getProxyDispatcher(targetUrl) {
11
20
  return cachedDispatcher;
12
21
  }
13
22
 
14
- const { ProxyAgent } = await import('undici');
23
+ const { ProxyAgent } = await importUndici();
15
24
  cachedDispatcher = new ProxyAgent(proxyUrl);
16
25
  cachedDispatcherProxyUrl = proxyUrl;
17
26
  return cachedDispatcher;
@@ -29,7 +38,7 @@ export async function createProxyWebSocket(url, protocols) {
29
38
  }
30
39
 
31
40
  const dispatcher = await getProxyDispatcher(url);
32
- const { WebSocket: UndiciWebSocket } = await import('undici');
41
+ const { WebSocket: UndiciWebSocket } = await importUndici();
33
42
 
34
43
  const wsOptions = { dispatcher };
35
44
  if (protocols) {
@@ -43,113 +52,22 @@ export async function createProxyWebSocket(url, protocols) {
43
52
  return new UndiciWebSocket(url, wsOptions);
44
53
  }
45
54
 
46
- export function getWebSocketImpl(targetUrl) {
55
+ export async function getWebSocketImpl(targetUrl) {
47
56
  const proxyUrl = getProxyUrlForTarget(targetUrl);
48
57
  if (!proxyUrl) return globalThis.WebSocket;
49
58
 
50
- return (url, protocols) => {
51
- const proxyUrlForTarget = getProxyUrlForTarget(url);
52
- if (!proxyUrlForTarget) {
53
- return new globalThis.WebSocket(url, protocols);
54
- }
55
-
56
- const wsPromise = (async () => {
57
- const dispatcher = await getProxyDispatcher(url);
58
- const { WebSocket: UndiciWebSocket } = await import('undici');
59
+ const dispatcher = await getProxyDispatcher(targetUrl);
60
+ const { WebSocket: UndiciWebSocket } = await importUndici();
59
61
 
60
- const wsOptions = { dispatcher };
62
+ class ProxyWebSocket extends UndiciWebSocket {
63
+ constructor(url, protocols) {
64
+ const options = { dispatcher };
61
65
  if (protocols) {
62
- if (Array.isArray(protocols)) {
63
- wsOptions.protocols = protocols;
64
- } else {
65
- wsOptions.protocols = [protocols];
66
- }
67
- }
68
-
69
- return new UndiciWebSocket(url, wsOptions);
70
- })();
71
-
72
- return createSyncProxyWebSocketWrapper(wsPromise);
73
- };
74
- }
75
-
76
- function createSyncProxyWebSocketWrapper(wsPromise) {
77
- const pendingEvents = [];
78
- const eventHandlers = new Map();
79
- let ws = null;
80
- let settled = false;
81
- let readyState = 0;
82
- let binaryType = 'arraybuffer';
83
-
84
- const wrapper = {
85
- get readyState() { return ws ? ws.readyState : readyState; },
86
- get url() { return ws ? ws.url : ''; },
87
- get protocol() { return ws ? ws.protocol : ''; },
88
- get binaryType() { return ws ? ws.binaryType : binaryType; },
89
- set binaryType(v) {
90
- binaryType = v;
91
- if (ws) ws.binaryType = v;
92
- },
93
-
94
- send(data, callback) {
95
- if (ws) {
96
- if (ws.send.length >= 2) return ws.send(data, callback);
97
- try { ws.send(data); if (callback) callback(null); } catch (e) { if (callback) callback(e); }
98
- return;
99
- }
100
- pendingEvents.push({ type: 'send', data, callback });
101
- },
102
-
103
- close(code, reason) {
104
- if (ws) { ws.close(code, reason); return; }
105
- readyState = 3;
106
- pendingEvents.push({ type: 'close', code, reason });
107
- },
108
-
109
- addEventListener(type, handler) {
110
- if (!eventHandlers.has(type)) eventHandlers.set(type, new Set());
111
- eventHandlers.get(type).add(handler);
112
- if (ws) ws.addEventListener(type, handler);
113
- },
114
-
115
- removeEventListener(type, handler) {
116
- const handlers = eventHandlers.get(type);
117
- if (handlers) handlers.delete(handler);
118
- if (ws) ws.removeEventListener(type, handler);
119
- },
120
-
121
- on(type, handler) { wrapper.addEventListener(type, handler); },
122
- off(type, handler) { wrapper.removeEventListener(type, handler); },
123
- };
124
-
125
- function flushPendingEvents() {
126
- for (const evt of pendingEvents) {
127
- if (evt.type === 'send') {
128
- if (ws.send.length >= 2) ws.send(evt.data, evt.callback);
129
- else { try { ws.send(evt.data); if (evt.callback) evt.callback(null); } catch (e) { if (evt.callback) evt.callback(e); } }
130
- } else if (evt.type === 'close') {
131
- ws.close(evt.code, evt.reason);
66
+ options.protocols = Array.isArray(protocols) ? protocols : [protocols];
132
67
  }
68
+ super(url, options);
133
69
  }
134
- pendingEvents.length = 0;
135
70
  }
136
71
 
137
- wsPromise.then((resolvedWs) => {
138
- ws = resolvedWs;
139
- if ('binaryType' in ws) ws.binaryType = binaryType;
140
- for (const [type, handlers] of eventHandlers) {
141
- for (const handler of handlers) {
142
- ws.addEventListener(type, handler);
143
- }
144
- }
145
- flushPendingEvents();
146
- }).catch((err) => {
147
- readyState = 3;
148
- const handlers = eventHandlers.get('error');
149
- if (handlers) for (const h of handlers) h(err instanceof Error ? err : new Error(String(err)));
150
- const closeHandlers = eventHandlers.get('close');
151
- if (closeHandlers) for (const h of closeHandlers) h({ code: 1006, reason: err.message || String(err) });
152
- });
153
-
154
- return wrapper;
72
+ return ProxyWebSocket;
155
73
  }
@@ -47,6 +47,10 @@ function shouldBypassProxy(hostname, noProxyList) {
47
47
  if (!p) continue;
48
48
  if (p === lower) return true;
49
49
  if (p === '*') return true;
50
+ if (p.startsWith('*.')) {
51
+ const domain = p.slice(1);
52
+ if (lower.endsWith(domain) || lower === p.slice(2)) return true;
53
+ }
50
54
  if (p.startsWith('.') && lower.endsWith(p)) return true;
51
55
  if (lower.endsWith('.' + p)) return true;
52
56
  }
@@ -60,13 +64,16 @@ export function getProxySettings(targetUrl) {
60
64
 
61
65
  const fileConfig = readProxyConfig();
62
66
 
63
- const https_proxy = envHttps || (fileConfig?.https_proxy || '');
64
- const http_proxy = envHttp || (fileConfig?.http_proxy || '');
65
- const no_proxy = envNoProxy || (fileConfig?.no_proxy || '');
67
+ const https_proxy = envHttps || fileConfig?.https_proxy || fileConfig?.HTTPS_PROXY || '';
68
+ const http_proxy = envHttp || fileConfig?.http_proxy || fileConfig?.HTTP_PROXY || '';
69
+ const no_proxy = envNoProxy || fileConfig?.no_proxy || fileConfig?.NO_PROXY || '';
66
70
 
67
71
  if (!https_proxy && !http_proxy) return null;
68
72
 
69
- const noProxyList = no_proxy.split(',').map((s) => s.trim()).filter(Boolean);
73
+ const noProxyList = no_proxy
74
+ .split(',')
75
+ .map((s) => s.trim())
76
+ .filter(Boolean);
70
77
 
71
78
  if (targetUrl) {
72
79
  const parsed = new URL(targetUrl);
@@ -18,7 +18,10 @@ export function loadRiskRules(options = {}) {
18
18
 
19
19
  function redactEvidence(text) {
20
20
  return String(text)
21
- .replace(/((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|authorization|password|passwd|adminPass|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi, '$1<redacted>')
21
+ .replace(
22
+ /((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|authorization|password|passwd|adminPass|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi,
23
+ '$1<redacted>',
24
+ )
22
25
  .replace(/(AK|SK)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;]+)/g, '$1=<redacted>');
23
26
  }
24
27