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,5 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
+
2
3
  import { classifyHcloudArgs, redactSecrets, assertAllowed } from './safety-policy.mjs';
4
+ import { getProxySettings } from './proxy/proxy-config.mjs';
3
5
 
4
6
  const DEFAULT_TIMEOUT_MS = 60_000;
5
7
  const DEFAULT_FORCE_KILL_AFTER_MS = 2_000;
@@ -8,7 +10,7 @@ const DEFAULT_MAX_RETRIES = 1;
8
10
  export function planHcloudCommand(args, options = {}) {
9
11
  const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
10
12
  const classification = classifyHcloudArgs(normalizedArgs, options);
11
- const command = ['hcloud', ...normalizedArgs].map(quoteShellArg).join(' ');
13
+ const command = ['hcloud', ...normalizedArgs].map((arg) => quoteShellArg(arg)).join(' ');
12
14
  const warnings = planningWarnings(normalizedArgs);
13
15
  const paramValidation = validateRequiredParams(normalizedArgs);
14
16
  if (paramValidation.missing.length > 0) {
@@ -46,7 +48,7 @@ export async function runHcloud(args, options = {}) {
46
48
  attempts: attempt + 1,
47
49
  };
48
50
  }
49
- await wait((options.retryBaseDelayMs ?? 500) * (2 ** attempt));
51
+ await wait((options.retryBaseDelayMs ?? 500) * 2 ** attempt);
50
52
  }
51
53
  throw new Error('Unreachable retry state.');
52
54
  }
@@ -59,12 +61,20 @@ function runHcloudOnce(plan, options) {
59
61
  const cwd = options.cwd || undefined;
60
62
 
61
63
  return new Promise((resolve) => {
64
+ const proxySettings = getProxySettings();
65
+ const proxyEnv = {};
66
+ if (proxySettings) {
67
+ if (proxySettings.https_proxy) proxyEnv.HTTPS_PROXY = proxySettings.https_proxy;
68
+ if (proxySettings.http_proxy) proxyEnv.HTTP_PROXY = proxySettings.http_proxy;
69
+ if (proxySettings.no_proxy) proxyEnv.NO_PROXY = proxySettings.no_proxy;
70
+ }
62
71
  const child = spawn(executable, [...executableArgs, ...plan.rawArgs], {
63
72
  shell: false,
64
73
  windowsHide: true,
65
74
  cwd,
66
75
  env: {
67
76
  ...process.env,
77
+ ...proxyEnv,
68
78
  ...options.env,
69
79
  },
70
80
  });
@@ -151,7 +161,10 @@ function runHcloudOnce(plan, options) {
151
161
  return;
152
162
  }
153
163
  finish({
154
- ok: code === 0 || (code !== 0 && /successfully|succ?ess.*\[200\]|create bucket successfully|upload successfully/i.test(stdout + stderr)),
164
+ ok:
165
+ code === 0 ||
166
+ (code !== 0 &&
167
+ /successfully|succ?ess.*\[200\]|create bucket successfully|upload successfully/i.test(stdout + stderr)),
155
168
  exitCode: code,
156
169
  signal,
157
170
  stdout: redactOutput(stdout),
@@ -165,7 +178,9 @@ function runHcloudOnce(plan, options) {
165
178
  function isRetryableNetworkError(result) {
166
179
  if (result.code === 'TIMEOUT') return false;
167
180
  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);
181
+ return /\[NETWORK_ERROR\]|connection timed out|ECONNRESET|ETIMEDOUT|temporary failure|TLS handshake timeout/i.test(
182
+ text,
183
+ );
169
184
  }
170
185
 
171
186
  function wait(ms) {
@@ -194,7 +209,11 @@ const REQUIRED_PARAMS = {
194
209
  'ECS CreateServers': ['server.flavorRef', 'server.imageRef', 'server.nics.1.subnet_id'],
195
210
  'VPC CreateVpc': ['vpc.cidr'],
196
211
  'VPC CreateSubnet': ['subnet.vpc_id', 'subnet.cidr'],
197
- 'VPC CreateSecurityGroupRule': ['security_group_rule.security_group_id', 'security_group_rule.direction', 'security_group_rule.protocol'],
212
+ 'VPC CreateSecurityGroupRule': [
213
+ 'security_group_rule.security_group_id',
214
+ 'security_group_rule.direction',
215
+ 'security_group_rule.protocol',
216
+ ],
198
217
  'EIP CreatePublicip': ['bandwidth.share_type', 'publicip.type'],
199
218
  'FunctionGraph CreateFunction': ['func_name', 'runtime', 'handler', 'memory_size', 'package', 'timeout'],
200
219
  'FunctionGraph CreateFunctionTrigger': ['function_urn', 'trigger_type_code'],
@@ -208,9 +227,9 @@ const PARAM_VALUE_HINTS = {
208
227
  'server.imageRef': 'Run `hcloud IMS ListImages --cli-region=<r> --__imagetype=gold` to find valid image IDs',
209
228
  'server.nics.1.subnet_id': 'Run `hcloud VPC ListSubnets --cli-region=<r>` to find subnet IDs',
210
229
  '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)',
230
+ func_name: 'Function name must be unique within project',
231
+ runtime: 'Run `hcloud FunctionGraph ListRuntimes` to see available runtimes',
232
+ spec_id: 'APIG spec: BASIC (no public IP) or PROFESSIONAL (requires --loadbalancer_provider)',
214
233
  'obs://': 'Bucket name must be globally unique and DNS-compliant (lowercase, numbers, hyphens only)',
215
234
  };
216
235
 
@@ -0,0 +1,176 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ import { getProxyDispatcher } from './proxy/proxy-agent.mjs';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const SNAPSHOT_PATH = join(__dirname, 'data', 'icons-manifest.v1.json');
9
+ const MANIFEST_URL = 'https://open.huaweicloud.com/openplatform/icons/manifest.v1.json';
10
+ const ICONS_PAGE_URL = 'https://open.huaweicloud.com/openplatform/icons.html';
11
+ const HTTP_TIMEOUT_MS = 10000;
12
+ const MAX_RESULTS = 5;
13
+
14
+ let cachedManifest = null;
15
+
16
+ export function clearIconCache() {
17
+ cachedManifest = null;
18
+ }
19
+
20
+ async function fetchLiveManifest() {
21
+ const controller = new AbortController();
22
+ const timer = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS);
23
+ try {
24
+ const dispatcher = await getProxyDispatcher(MANIFEST_URL);
25
+ const fetchOpts = {
26
+ headers: { 'User-Agent': 'huaweicloud-devkit/1.0' },
27
+ signal: controller.signal,
28
+ };
29
+ if (dispatcher) fetchOpts.dispatcher = dispatcher;
30
+ const resp = await fetch(MANIFEST_URL, fetchOpts);
31
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
32
+ const data = await resp.json();
33
+ if (!data || !Array.isArray(data.icons)) {
34
+ throw new Error('Manifest is missing the icons array.');
35
+ }
36
+ return data;
37
+ } finally {
38
+ clearTimeout(timer);
39
+ }
40
+ }
41
+
42
+ function loadSnapshot() {
43
+ const manifest = JSON.parse(readFileSync(SNAPSHOT_PATH, 'utf8'));
44
+ if (!manifest || !Array.isArray(manifest.icons)) {
45
+ throw new Error('Bundled icons manifest is corrupted.');
46
+ }
47
+ return manifest;
48
+ }
49
+
50
+ async function loadManifest() {
51
+ if (cachedManifest) return cachedManifest;
52
+ if (process.env.HUAWEICLOUD_ICONS_OFFLINE === '1') {
53
+ cachedManifest = { manifest: loadSnapshot(), source: 'snapshot' };
54
+ return cachedManifest;
55
+ }
56
+ try {
57
+ const live = await fetchLiveManifest();
58
+ cachedManifest = { manifest: live, source: 'live' };
59
+ } catch {
60
+ cachedManifest = { manifest: loadSnapshot(), source: 'snapshot' };
61
+ }
62
+ return cachedManifest;
63
+ }
64
+
65
+ function normalizeToken(token) {
66
+ return token.toLowerCase();
67
+ }
68
+
69
+ function scoreIcon(icon, tokens, index) {
70
+ let total = 0;
71
+ const matched = [];
72
+ for (const token of tokens) {
73
+ let best = 0;
74
+ const id = (icon.id || '').toLowerCase();
75
+ const name = (icon.name || '').toLowerCase();
76
+ if (id === token || (index.aliases[token] || []).includes(id)) {
77
+ best = 10;
78
+ } else if (name === token) {
79
+ best = 9;
80
+ } else if (name.startsWith(token)) {
81
+ best = 8;
82
+ } else if (name.includes(token)) {
83
+ best = 7;
84
+ } else if (id.includes(token)) {
85
+ best = 5;
86
+ } else if ((index.aliases[id] || []).some((a) => a.includes(token))) {
87
+ best = 6;
88
+ } else if ((index.tags[id] || []).some((t) => t.includes(token))) {
89
+ best = 4;
90
+ } else if ((index.categories[id] || '').includes(token)) {
91
+ best = 3;
92
+ } else if ((index.descriptions[id] || '').includes(token)) {
93
+ best = 2;
94
+ }
95
+ if (best > 0) {
96
+ total += best;
97
+ matched.push(token);
98
+ }
99
+ }
100
+ return [total, matched];
101
+ }
102
+
103
+ export async function getServiceIcon(service = '', category = '') {
104
+ const query = String(service || '').trim();
105
+ const catFilter = String(category || '')
106
+ .trim()
107
+ .toLowerCase();
108
+ if (!query && !catFilter) {
109
+ return {
110
+ ok: false,
111
+ error:
112
+ 'service or category is required. Examples: ecs, obs, modelarts, 对象存储, 虚拟私有云, or a category such as 计算 / 存储 / 人工智能.',
113
+ iconsPageUrl: ICONS_PAGE_URL,
114
+ };
115
+ }
116
+ const { manifest, source } = await loadManifest();
117
+ const tokens = query
118
+ .replace(/[,;]/g, ' ')
119
+ .split(/\s+/)
120
+ .filter(Boolean)
121
+ .map((t) => normalizeToken(t));
122
+
123
+ const index = { aliases: {}, tags: {}, categories: {}, descriptions: {} };
124
+ for (const icon of manifest.icons) {
125
+ const id = (icon.id || '').toLowerCase();
126
+ index.aliases[id] = (icon.aliases || []).map((a) => String(a).toLowerCase());
127
+ index.tags[id] = (icon.tags || []).map((t) => String(t).toLowerCase());
128
+ index.categories[id] = [
129
+ String(icon.category || '').toLowerCase(),
130
+ String(icon.subcategory || '').toLowerCase(),
131
+ ].join(' ');
132
+ index.descriptions[id] = String(icon.description || '').toLowerCase();
133
+ }
134
+
135
+ const results = [];
136
+ for (const icon of manifest.icons) {
137
+ const categoryMatches = !catFilter || String(icon.category || '').toLowerCase() === catFilter;
138
+ if (!categoryMatches) continue;
139
+ const [score, matched] = tokens.length ? scoreIcon(icon, tokens, index) : [0, []];
140
+ if (tokens.length && score === 0) continue;
141
+ results.push({
142
+ id: icon.id,
143
+ name: icon.name,
144
+ category: icon.category,
145
+ subcategory: icon.subcategory || undefined,
146
+ description: icon.description || undefined,
147
+ aliases: icon.aliases,
148
+ product_url: icon.product_url,
149
+ logo: {
150
+ source_url: icon.logo?.source_url,
151
+ local_path: icon.logo?.local_path,
152
+ },
153
+ architecture: icon.architecture
154
+ ? { status: icon.architecture.status, local_path: icon.architecture.local_path }
155
+ : undefined,
156
+ score,
157
+ matched,
158
+ });
159
+ }
160
+ results.sort((a, b) => b.score - a.score);
161
+
162
+ return {
163
+ ok: true,
164
+ query,
165
+ category: catFilter || undefined,
166
+ source,
167
+ manifestGeneratedAt: manifest.generated_at,
168
+ count: results.length,
169
+ iconsPageUrl: ICONS_PAGE_URL,
170
+ note:
171
+ source === 'live'
172
+ ? 'Live manifest from open.huaweicloud.com.'
173
+ : 'Live manifest unreachable; using bundled snapshot. Upgrade the package to refresh the bundled snapshot.',
174
+ results: results.slice(0, MAX_RESULTS),
175
+ };
176
+ }
@@ -1,10 +1,24 @@
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
7
  import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
7
8
 
9
+ try {
10
+ const { readProxyConfig } = await import('./proxy/proxy-config.mjs');
11
+ const proxyConfig = readProxyConfig();
12
+ if (proxyConfig) {
13
+ if (proxyConfig.https_proxy || proxyConfig.HTTPS_PROXY) {
14
+ process.env.HTTPS_PROXY = process.env.HTTPS_PROXY || proxyConfig.https_proxy || proxyConfig.HTTPS_PROXY;
15
+ }
16
+ if (proxyConfig.http_proxy || proxyConfig.HTTP_PROXY) {
17
+ process.env.HTTP_PROXY = process.env.HTTP_PROXY || proxyConfig.http_proxy || proxyConfig.HTTP_PROXY;
18
+ }
19
+ }
20
+ } catch {}
21
+
8
22
  // The MCP server is now loaded by a live agent session. Clear the install marker
9
23
  // in this plugin dir so `doctor` no longer reports "restart needed".
10
24
  try {
@@ -13,6 +27,20 @@ try {
13
27
  if (existsSync(marker)) rmSync(marker, { force: true });
14
28
  } catch {}
15
29
 
30
+ const __dirname = dirname(fileURLToPath(import.meta.url));
31
+ const pluginRoot = resolve(__dirname, '..');
32
+ const packageRoot = resolve(pluginRoot, '..', '..');
33
+ let pkgVersion = '0.0.0';
34
+ for (const base of [pluginRoot, packageRoot]) {
35
+ try {
36
+ const version = JSON.parse(readFileSync(join(base, 'package.json'), 'utf8')).version;
37
+ if (version) {
38
+ pkgVersion = version;
39
+ break;
40
+ }
41
+ } catch {}
42
+ }
43
+
16
44
  let buffer = Buffer.alloc(0);
17
45
  let useContentLengthFraming = true;
18
46
 
@@ -90,7 +118,7 @@ async function dispatch(method, params) {
90
118
  },
91
119
  serverInfo: {
92
120
  name: 'huaweicloud-devkit',
93
- version: '0.1.0',
121
+ version: pkgVersion,
94
122
  },
95
123
  };
96
124
  }
@@ -0,0 +1,73 @@
1
+ import { getProxyUrlForTarget } from './proxy-config.mjs';
2
+
3
+ let cachedDispatcher = undefined;
4
+ let cachedDispatcherProxyUrl = null;
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
+
15
+ export async function getProxyDispatcher(targetUrl) {
16
+ const proxyUrl = getProxyUrlForTarget(targetUrl);
17
+ if (!proxyUrl) return undefined;
18
+
19
+ if (cachedDispatcher && cachedDispatcherProxyUrl === proxyUrl) {
20
+ return cachedDispatcher;
21
+ }
22
+
23
+ const { ProxyAgent } = await importUndici();
24
+ cachedDispatcher = new ProxyAgent(proxyUrl);
25
+ cachedDispatcherProxyUrl = proxyUrl;
26
+ return cachedDispatcher;
27
+ }
28
+
29
+ export function clearProxyDispatcherCache() {
30
+ cachedDispatcher = undefined;
31
+ cachedDispatcherProxyUrl = null;
32
+ }
33
+
34
+ export async function createProxyWebSocket(url, protocols) {
35
+ const proxyUrl = getProxyUrlForTarget(url);
36
+ if (!proxyUrl) {
37
+ return new globalThis.WebSocket(url, protocols);
38
+ }
39
+
40
+ const dispatcher = await getProxyDispatcher(url);
41
+ const { WebSocket: UndiciWebSocket } = await importUndici();
42
+
43
+ const wsOptions = { dispatcher };
44
+ if (protocols) {
45
+ if (Array.isArray(protocols)) {
46
+ wsOptions.protocols = protocols;
47
+ } else {
48
+ wsOptions.protocols = [protocols];
49
+ }
50
+ }
51
+
52
+ return new UndiciWebSocket(url, wsOptions);
53
+ }
54
+
55
+ export async function getWebSocketImpl(targetUrl) {
56
+ const proxyUrl = getProxyUrlForTarget(targetUrl);
57
+ if (!proxyUrl) return globalThis.WebSocket;
58
+
59
+ const dispatcher = await getProxyDispatcher(targetUrl);
60
+ const { WebSocket: UndiciWebSocket } = await importUndici();
61
+
62
+ class ProxyWebSocket extends UndiciWebSocket {
63
+ constructor(url, protocols) {
64
+ const options = { dispatcher };
65
+ if (protocols) {
66
+ options.protocols = Array.isArray(protocols) ? protocols : [protocols];
67
+ }
68
+ super(url, options);
69
+ }
70
+ }
71
+
72
+ return ProxyWebSocket;
73
+ }
@@ -0,0 +1,94 @@
1
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+
5
+ function baseHome() {
6
+ return process.env.HUAWEICLOUD_HOME || homedir();
7
+ }
8
+
9
+ export function proxyConfigPath() {
10
+ return join(baseHome(), '.config', 'huaweicloud', 'proxy.json');
11
+ }
12
+
13
+ export function readProxyConfig() {
14
+ const path = proxyConfigPath();
15
+ if (!existsSync(path)) return null;
16
+ try {
17
+ return JSON.parse(readFileSync(path, 'utf8'));
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ export function writeProxyConfig(config = {}) {
24
+ const path = proxyConfigPath();
25
+ mkdirSync(dirname(path), { recursive: true });
26
+ const payload = {
27
+ https_proxy: String(config.https_proxy || ''),
28
+ http_proxy: String(config.http_proxy || ''),
29
+ no_proxy: String(config.no_proxy || ''),
30
+ };
31
+ writeFileSync(path, JSON.stringify(payload, null, 2), { encoding: 'utf8' });
32
+ return path;
33
+ }
34
+
35
+ export function clearProxyConfig() {
36
+ const path = proxyConfigPath();
37
+ if (!existsSync(path)) return false;
38
+ rmSync(path);
39
+ return true;
40
+ }
41
+
42
+ function shouldBypassProxy(hostname, noProxyList) {
43
+ if (!noProxyList.length) return false;
44
+ const lower = hostname.toLowerCase();
45
+ for (const pattern of noProxyList) {
46
+ const p = pattern.trim().toLowerCase();
47
+ if (!p) continue;
48
+ if (p === lower) return true;
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
+ }
54
+ if (p.startsWith('.') && lower.endsWith(p)) return true;
55
+ if (lower.endsWith('.' + p)) return true;
56
+ }
57
+ return false;
58
+ }
59
+
60
+ export function getProxySettings(targetUrl) {
61
+ const envHttps = process.env.HTTPS_PROXY || process.env.https_proxy || '';
62
+ const envHttp = process.env.HTTP_PROXY || process.env.http_proxy || '';
63
+ const envNoProxy = process.env.NO_PROXY || process.env.no_proxy || '';
64
+
65
+ const fileConfig = readProxyConfig();
66
+
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 || '';
70
+
71
+ if (!https_proxy && !http_proxy) return null;
72
+
73
+ const noProxyList = no_proxy
74
+ .split(',')
75
+ .map((s) => s.trim())
76
+ .filter(Boolean);
77
+
78
+ if (targetUrl) {
79
+ const parsed = new URL(targetUrl);
80
+ if (shouldBypassProxy(parsed.hostname, noProxyList)) return null;
81
+ const isHttps = parsed.protocol === 'https:';
82
+ const proxyUrl = isHttps ? https_proxy : http_proxy;
83
+ if (!proxyUrl) return null;
84
+ return { proxyUrl, noProxyList, targetProtocol: parsed.protocol };
85
+ }
86
+
87
+ return { https_proxy, http_proxy, no_proxy, noProxyList };
88
+ }
89
+
90
+ export function getProxyUrlForTarget(targetUrl) {
91
+ const settings = getProxySettings(targetUrl);
92
+ if (!settings) return null;
93
+ return settings.proxyUrl || null;
94
+ }
@@ -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