huaweicloud-devkit 1.1.2-next.0 → 1.1.2-next.2

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 (125) hide show
  1. package/.agents/plugins/marketplace.json +18 -18
  2. package/README.md +182 -38
  3. package/README.zh-CN.md +180 -36
  4. package/bin/dsh-postinstall.cjs +77 -0
  5. package/bin/setup.cjs +7 -4
  6. package/cordis.patch.yml +13 -0
  7. package/integrations/atomcode/hooks/huaweicloud-telemetry.js +181 -0
  8. package/integrations/atomcode/hooks.json +9 -0
  9. package/integrations/dsh/hook-plugin.mjs +148 -0
  10. package/integrations/hermes/hooks/huaweicloud-telemetry.py +238 -0
  11. package/integrations/hermes/manifest.yaml +43 -0
  12. package/integrations/opencode/hooks/skill-tracker.js +115 -0
  13. package/integrations/opencode/opencode.json +1 -4
  14. package/integrations/workbuddy/hooks/telemetry-tracker.py +226 -0
  15. package/package.json +74 -43
  16. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -17
  17. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +23 -33
  18. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -17
  19. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
  20. package/plugins/huaweicloud-core/.mcp.json +2 -5
  21. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -18
  22. package/plugins/huaweicloud-core/assets/icon.png +0 -0
  23. package/plugins/huaweicloud-core/assets/logo-dark.png +0 -0
  24. package/plugins/huaweicloud-core/assets/logo.png +0 -0
  25. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +104 -28
  26. package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
  27. package/plugins/huaweicloud-core/safety/policy.json +75 -99
  28. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +196 -35
  29. package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +38 -34
  30. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +21 -20
  31. package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +19 -19
  32. package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +38 -32
  33. package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +30 -30
  34. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
  35. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
  36. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
  37. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +40 -31
  38. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
  39. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
  40. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +45 -37
  41. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +57 -6
  42. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
  43. package/plugins/huaweicloud-core/skills/huawei-ecs/references/hc-activity.md +27 -0
  44. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
  45. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +44 -39
  46. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
  47. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
  48. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
  49. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
  50. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
  51. package/plugins/huaweicloud-core/skills/huawei-iac/SKILL.md +93 -0
  52. package/plugins/huaweicloud-core/skills/huawei-iac/references/architectures.md +83 -0
  53. package/plugins/huaweicloud-core/skills/huawei-iac/references/resource-catalog.md +107 -0
  54. package/plugins/huaweicloud-core/skills/huawei-iac/references/state-file.md +104 -0
  55. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +30 -30
  56. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
  57. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
  58. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +33 -25
  59. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +46 -45
  60. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
  61. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
  62. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
  63. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
  64. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +71 -61
  65. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +776 -44
  66. package/plugins/huaweicloud-core/skills/huawei-sandbox/references/framework-commands.md +84 -0
  67. package/plugins/huaweicloud-core/skills/huawei-sandbox/references/nginx-templates.md +100 -0
  68. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +36 -29
  69. package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +57 -0
  70. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
  71. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +22 -15
  72. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
  73. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
  74. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +23 -20
  75. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
  76. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
  77. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +21 -2
  78. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
  79. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +30 -4
  80. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
  81. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +68 -54
  82. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
  83. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
  84. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
  85. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
  86. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
  87. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +36 -34
  88. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
  89. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +142 -5
  90. package/plugins/huaweicloud-core/src/auth/credentials.mjs +217 -5
  91. package/plugins/huaweicloud-core/src/auth/reconcile.mjs +174 -0
  92. package/plugins/huaweicloud-core/src/auth/service.mjs +57 -2
  93. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
  94. package/plugins/huaweicloud-core/src/detect-framework.mjs +366 -0
  95. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +203 -16
  96. package/plugins/huaweicloud-core/src/icon-library.mjs +176 -0
  97. package/plugins/huaweicloud-core/src/mcp-protocol.mjs +88 -0
  98. package/plugins/huaweicloud-core/src/mcp-server-remote.mjs +107 -0
  99. package/plugins/huaweicloud-core/src/mcp-server.mjs +154 -99
  100. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +20 -102
  101. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +11 -4
  102. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
  103. package/plugins/huaweicloud-core/src/safety-policy.mjs +123 -68
  104. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +48 -12
  105. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +25 -26
  106. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  107. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +943 -10
  108. package/plugins/huaweicloud-core/src/sandbox/uninstall-cleanup.mjs +50 -0
  109. package/plugins/huaweicloud-core/src/search-market.mjs +49 -14
  110. package/plugins/huaweicloud-core/src/setup-cli.mjs +3091 -498
  111. package/plugins/huaweicloud-core/src/telemetry/agent-detect.mjs +40 -0
  112. package/plugins/huaweicloud-core/src/telemetry/agent-registry.mjs +328 -0
  113. package/plugins/huaweicloud-core/src/telemetry/telemetry.mjs +378 -0
  114. package/plugins/huaweicloud-core/src/tools.mjs +1272 -146
  115. package/plugins/huaweicloud-core/src/update-check.mjs +383 -0
  116. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
  117. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
  118. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +13 -16
  119. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
  120. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
  121. package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +268 -0
  122. package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
  123. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +40 -13
  124. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +0 -190
  125. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +0 -124
@@ -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
 
@@ -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,13 +1,20 @@
1
1
  import { getCredentials } from './hwlink-api.mjs';
2
2
  import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
3
+ import { cacheUserHash } from '../telemetry/telemetry.mjs';
3
4
 
4
- const HDKIT_BASE_URL =
5
- process.env.HDKITSERVICE_ENDPOINT ||
6
- 'https://devkit.huaweicloud.com/rest/developer/server/hdkitservice/';
5
+ function getHdkitBaseUrl() {
6
+ return process.env.HDKITSERVICE_ENDPOINT || 'https://devkit.huaweicloud.com/rest/developer/server/hdkitservice/';
7
+ }
7
8
 
8
9
  async function hdkitRequest(method, path, body, timeoutMs = 300000) {
9
10
  const { ak, sk, securitytoken } = getCredentials();
10
11
 
12
+ if (!ak || !sk) {
13
+ throw new Error(
14
+ 'Huawei Cloud credentials are not configured. ' +
15
+ 'Run "npx huaweicloud-devkit auth init" or set HW_ACCESS_KEY/HW_SECRET_KEY.',
16
+ );
17
+ }
11
18
  const headers = {
12
19
  'Content-Type': 'application/json',
13
20
  'X-HW-AK': ak,
@@ -17,7 +24,7 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
17
24
  headers['X-HW-Security-Token'] = securitytoken;
18
25
  }
19
26
 
20
- const url = `${HDKIT_BASE_URL}${path}`;
27
+ const url = `${getHdkitBaseUrl()}${path}`;
21
28
  const controller = new AbortController();
22
29
  const timer = setTimeout(() => controller.abort(), timeoutMs);
23
30
 
@@ -30,8 +37,13 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
30
37
  body: body ? JSON.stringify(body) : undefined,
31
38
  signal: controller.signal,
32
39
  };
33
- if (dispatcher) fetchOpts.dispatcher = dispatcher;
34
- resp = await fetch(url, fetchOpts);
40
+ if (dispatcher) {
41
+ fetchOpts.dispatcher = dispatcher;
42
+ const { fetch: undiciFetch } = await import('undici');
43
+ resp = await undiciFetch(url, fetchOpts);
44
+ } else {
45
+ resp = await fetch(url, fetchOpts);
46
+ }
35
47
  } finally {
36
48
  clearTimeout(timer);
37
49
  }
@@ -45,12 +57,12 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
45
57
  }
46
58
 
47
59
  if (!resp.ok) {
48
- const err = new Error(
49
- data.message || `hdkitservice error: ${data.code || resp.status}`
50
- );
51
- err.code = data.code;
60
+ const code = data.code || `HTTP_${resp.status}`;
61
+ const trace = data.traceId ? ` [trace: ${data.traceId}]` : '';
62
+ const err = new Error(`${code}: ${data.message || 'hdkitservice error'}${trace}`);
63
+ err.code = code;
52
64
  err.status = resp.status;
53
- err.traceId = data.traceId; // 后端实际返回驼峰 traceId
65
+ err.traceId = data.traceId;
54
66
  throw err;
55
67
  }
56
68
 
@@ -58,7 +70,15 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
58
70
  }
59
71
 
60
72
  export async function hdkitCheckUser() {
61
- return await hdkitRequest('GET', 'check-user', undefined, 30000);
73
+ const result = await hdkitRequest('GET', 'check-user', undefined, 30000);
74
+ if (result.userHash) cacheUserHash(result.userHash);
75
+ return result;
76
+ }
77
+
78
+ export async function hdkitGenerateUserHash() {
79
+ const result = await hdkitRequest('GET', 'user/generatorUserIDHash', undefined, 30000);
80
+ if (result.userHash) cacheUserHash(result.userHash);
81
+ return result;
62
82
  }
63
83
 
64
84
  export async function hdkitSignAgreement() {
@@ -88,4 +108,20 @@ export async function hdkitCredentials(sessionId, devStageId, enableSts = true)
88
108
  return await hdkitRequest('POST', 'credentials', body);
89
109
  }
90
110
 
111
+ export async function hdkitVoucherStatus(domainId) {
112
+ try {
113
+ const path = domainId ? `voucher/status?domain_id=${encodeURIComponent(domainId)}` : 'voucher/status';
114
+ return await hdkitRequest('GET', path, undefined, 30000);
115
+ } catch (error) {
116
+ return { claimed: false, message: 'Incentive service unavailable, please try again later' };
117
+ }
118
+ }
91
119
 
120
+ export async function hdkitVoucherClaim(domainId) {
121
+ try {
122
+ const body = domainId ? { domain_id: domainId } : {};
123
+ return await hdkitRequest('POST', 'voucher/claim', body);
124
+ } catch (error) {
125
+ return { claimed: false, message: 'Incentive service unavailable, please try again later' };
126
+ }
127
+ }