huaweicloud-devkit 1.1.0-next.2 → 1.1.0-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 (39) hide show
  1. package/README.md +64 -1
  2. package/README.zh-CN.md +63 -1
  3. package/package.json +3 -1
  4. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
  5. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
  6. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
  7. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
  8. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
  9. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +46 -21
  10. package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
  11. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +136 -24
  12. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +15 -14
  13. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +1 -1
  14. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +5 -1
  15. package/plugins/huaweicloud-core/skills/huawei-ecs/references/hc-activity.md +27 -0
  16. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +11 -11
  17. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +6 -5
  18. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +596 -93
  19. package/plugins/huaweicloud-core/skills/huawei-sandbox/references/framework-commands.md +0 -1
  20. package/plugins/huaweicloud-core/skills/huawei-sandbox/references/nginx-templates.md +8 -4
  21. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +19 -12
  22. package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +52 -0
  23. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +26 -26
  24. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +12 -6
  25. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +7 -4
  26. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +7 -7
  27. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +2 -0
  28. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +8 -0
  29. package/plugins/huaweicloud-core/src/auth/credentials.mjs +30 -1
  30. package/plugins/huaweicloud-core/src/auth/service.mjs +33 -0
  31. package/plugins/huaweicloud-core/src/detect-framework.mjs +25 -3
  32. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +67 -5
  33. package/plugins/huaweicloud-core/src/mcp-server.mjs +23 -0
  34. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +11 -3
  35. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +436 -21
  36. package/plugins/huaweicloud-core/src/search-market.mjs +1 -0
  37. package/plugins/huaweicloud-core/src/setup-cli.mjs +605 -50
  38. package/plugins/huaweicloud-core/src/tools.mjs +193 -18
  39. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +29 -8
@@ -6,7 +6,7 @@ import { spawnSync } from 'node:child_process';
6
6
 
7
7
  import { evaluateArtifacts, evaluateCommandRisk, evaluateDeployPlan } from './risk-rule-engine.mjs';
8
8
  import { classifyTextCommand, redactSecrets } from './safety-policy.mjs';
9
- import { planHcloudCommand, runHcloud } from './hcloud-cli.mjs';
9
+ import { planHcloudCommand, runHcloud, consumeApprovalToken } from './hcloud-cli.mjs';
10
10
  import { searchMarketplace } from './search-market.mjs';
11
11
  import { getServiceIcon } from './icon-library.mjs';
12
12
  import { detectFramework } from './detect-framework.mjs';
@@ -16,6 +16,8 @@ import {
16
16
  closeSession,
17
17
  uploadFileWithSession,
18
18
  uploadProjectWithSession,
19
+ deployNginx,
20
+ deployCheck,
19
21
  getCurrentWorkspaceId,
20
22
  setWorkspaceId,
21
23
  } from './sandbox/session-manager.mjs';
@@ -27,6 +29,7 @@ import {
27
29
  hdkitVoucherStatus,
28
30
  hdkitVoucherClaim,
29
31
  } from './sandbox/hdkitservice-api.mjs';
32
+ import { getCredentials } from './sandbox/hwlink-api.mjs';
30
33
  import { getAuthStatus, syncAuth } from './auth/service.mjs';
31
34
  import {
32
35
  readGlobalCredentials,
@@ -45,6 +48,10 @@ function codeartsSkillsDir() {
45
48
  const home = homedir();
46
49
  return join(home, '.codeartsdoer', 'skills');
47
50
  }
51
+ function codeartsWorkSkillsDir() {
52
+ const home = homedir();
53
+ return join(home, '.codeartswork', 'skills');
54
+ }
48
55
  function workbuddySkillsDir() {
49
56
  const home = homedir();
50
57
  return join(home, '.workbuddy', 'skills');
@@ -99,6 +106,10 @@ function hermesSkillsDir() {
99
106
  return join(home, '.hermes', 'skills');
100
107
  }
101
108
 
109
+ function codexDesktopSkillsDir() {
110
+ return join(homedir(), '.agents', 'skills');
111
+ }
112
+
102
113
  export function listSkillDirs(root) {
103
114
  if (!existsSync(root)) return [];
104
115
  try {
@@ -123,10 +134,12 @@ function resolveSkillsRoot() {
123
134
  SKILLS_ROOT_DEV,
124
135
  dshSkillsDir(),
125
136
  codeartsSkillsDir(),
137
+ codeartsWorkSkillsDir(),
126
138
  opencodeSkillsDir(),
127
139
  workbuddySkillsDir(),
128
140
  officeaceSkillsRoot(),
129
141
  hermesSkillsDir(),
142
+ codexDesktopSkillsDir(),
130
143
  ]) || SKILLS_ROOT_DEV
131
144
  );
132
145
  }
@@ -211,16 +224,16 @@ export const TOOL_DEFINITIONS = [
211
224
  'Run a write-capable hcloud command only after the exact command has been shown and explicitly approved by the user.',
212
225
  inputSchema: {
213
226
  type: 'object',
214
- required: ['args', 'approvedCommand', 'approvedByUser'],
227
+ required: ['args', 'approvalToken', 'approvedByUser'],
215
228
  properties: {
216
229
  args: {
217
230
  type: 'array',
218
231
  items: { type: 'string' },
219
232
  description: 'hcloud arguments, excluding the hcloud executable.',
220
233
  },
221
- approvedCommand: {
234
+ approvalToken: {
222
235
  type: 'string',
223
- description: 'The exact command string previously shown to the user.',
236
+ description: 'The approvalToken returned by huaweicloud_plan_cli_command.',
224
237
  },
225
238
  approvedByUser: {
226
239
  type: 'boolean',
@@ -446,7 +459,7 @@ export const TOOL_DEFINITIONS = [
446
459
  target: {
447
460
  type: 'string',
448
461
  description:
449
- 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, openclaw, atomcode, or all (default).',
462
+ 'Agent target to check: opencode, codex, codex-desktop, codearts, codearts-work, workbuddy, dsh, officeace, hermes, openclaw, atomcode, or all (default).',
450
463
  },
451
464
  },
452
465
  },
@@ -461,7 +474,7 @@ export const TOOL_DEFINITIONS = [
461
474
  target: {
462
475
  type: 'string',
463
476
  description:
464
- 'Agent target to report after sync: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, or all (default).',
477
+ 'Agent target to report after sync: opencode, codex, codex-desktop, codearts, codearts-work, workbuddy, dsh, officeace, hermes, or all (default).',
465
478
  },
466
479
  },
467
480
  },
@@ -549,7 +562,7 @@ export const TOOL_DEFINITIONS = [
549
562
  'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
550
563
  },
551
564
  username: { type: 'string', description: 'Login username (default: root)' },
552
- timeout_ms: { type: 'number', description: 'Per-command execution timeout in milliseconds (default: 120000)' },
565
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 60000)' },
553
566
  },
554
567
  },
555
568
  },
@@ -586,6 +599,72 @@ export const TOOL_DEFINITIONS = [
586
599
  },
587
600
  },
588
601
  },
602
+ {
603
+ name: 'huaweicloud_sandbox_deploy_nginx',
604
+ description:
605
+ 'Deploy an nginx configuration on the sandbox and reload. Takes nginxType, port, project, outputDir from framework detection and writes the correct template (SPA try_files, SSR reverse proxy, or static). Also fixes directory traverse permissions on the project path. Use this instead of manually constructing nginx config — it handles permissions, template selection, and reload in one call.',
606
+ inputSchema: {
607
+ type: 'object',
608
+ required: ['nginx_type', 'port', 'project', 'output_dir'],
609
+ properties: {
610
+ nginx_type: {
611
+ type: 'string',
612
+ description:
613
+ 'Nginx config type from framework detection: spa (try_files fallback for SPA/SSG/cross-platform), proxy (reverse proxy for SSR), or static (plain root for Hugo/Hexo).',
614
+ enum: ['spa', 'proxy', 'static'],
615
+ },
616
+ port: { type: 'number', description: 'Listen port (from framework detection).' },
617
+ project: { type: 'string', description: 'Project directory name under /workspace, e.g. movie-ticket.' },
618
+ output_dir: {
619
+ type: 'string',
620
+ description: 'Build output directory relative to /workspace/<project>, e.g. dist/build/h5.',
621
+ },
622
+ node_port: { type: 'number', description: 'Node.js app port for SSR (required when nginx_type=proxy).' },
623
+ public_port: { type: 'number', description: 'Public listen port for SSR proxy (optional, defaults to port).' },
624
+ config_name: {
625
+ type: 'string',
626
+ description:
627
+ 'Config file name (without .conf suffix). Defaults to the project name, ensuring each project gets its own config. Override with distinct names (e.g. admin, docs) for sub-app deployments.',
628
+ },
629
+ workspace_id: {
630
+ type: 'string',
631
+ description:
632
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
633
+ },
634
+ username: { type: 'string', description: 'Login username (default: root)' },
635
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 30000)' },
636
+ },
637
+ },
638
+ },
639
+ {
640
+ name: 'huaweicloud_sandbox_deploy_check',
641
+ description:
642
+ 'Run a deployment completeness check on the sandbox. Verifies nginx is serving, output directory exists, DevBridge tunnel is active and accessible, and QR code exists (cross-platform). Returns a score and nextStep to fix any missing items. Call this at the end of a deployment workflow to confirm everything is working before reporting success.',
643
+ inputSchema: {
644
+ type: 'object',
645
+ required: ['port', 'project', 'output_dir'],
646
+ properties: {
647
+ port: { type: 'number', description: 'App listen port (from framework detection).' },
648
+ project: { type: 'string', description: 'Project directory name under /workspace.' },
649
+ output_dir: {
650
+ type: 'string',
651
+ description: 'Build output directory relative to /workspace/<project>, e.g. dist/build/h5.',
652
+ },
653
+ framework_type: {
654
+ type: 'string',
655
+ description: 'Framework type from detect_framework. Set to cross-platform for QR code check.',
656
+ enum: ['spa', 'ssr', 'ssg', 'cross-platform', 'monorepo', 'static'],
657
+ },
658
+ workspace_id: {
659
+ type: 'string',
660
+ description:
661
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
662
+ },
663
+ username: { type: 'string', description: 'Login username (default: root)' },
664
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 30000)' },
665
+ },
666
+ },
667
+ },
589
668
  {
590
669
  name: 'huaweicloud_sandbox_check_user',
591
670
  description:
@@ -648,21 +727,27 @@ export const TOOL_DEFINITIONS = [
648
727
  },
649
728
  {
650
729
  name: 'huaweicloud_voucher_status',
651
- description: '查询激励金代金券领取状态。',
730
+ description: '查询代金券领取状态。',
652
731
  inputSchema: {
653
732
  type: 'object',
654
733
  properties: {
655
- domain_id: { type: 'string' },
734
+ domain_id: {
735
+ type: 'string',
736
+ description: 'Optional. Leave empty in production — account is resolved from IAM automatically.',
737
+ },
656
738
  },
657
739
  },
658
740
  },
659
741
  {
660
742
  name: 'huaweicloud_voucher_claim',
661
- description: '领取激励金代金券。领取前会自动检查是否已领取。',
743
+ description: '领取代金券(一人一次)。重复领取会返回已领取。',
662
744
  inputSchema: {
663
745
  type: 'object',
664
746
  properties: {
665
- domain_id: { type: 'string' },
747
+ domain_id: {
748
+ type: 'string',
749
+ description: 'Optional. Leave empty in production — account is resolved from IAM automatically.',
750
+ },
666
751
  },
667
752
  },
668
753
  },
@@ -818,6 +903,59 @@ export async function callTool(name, args = {}) {
818
903
  },
819
904
  );
820
905
  }
906
+ case 'huaweicloud_sandbox_deploy_nginx': {
907
+ if (!args.nginx_type || !args.port || !args.project || !args.output_dir) {
908
+ throw new Error('nginx_type, port, project, and output_dir are required.');
909
+ }
910
+ const sandboxWsId7 = args.workspace_id || getCurrentWorkspaceId();
911
+ if (!sandboxWsId7) {
912
+ throw new Error(
913
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
914
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
915
+ );
916
+ }
917
+ const sandboxUser7 = args.username || 'root';
918
+ const sandboxTimeout7 = args.timeout_ms || 60000;
919
+ return await deployNginx(
920
+ sandboxWsId7,
921
+ {
922
+ nginxType: args.nginx_type,
923
+ port: args.port,
924
+ project: args.project,
925
+ outputDir: args.output_dir,
926
+ nodePort: args.node_port,
927
+ publicPort: args.public_port,
928
+ configName: args.config_name,
929
+ },
930
+ sandboxUser7,
931
+ sandboxTimeout7,
932
+ );
933
+ }
934
+ case 'huaweicloud_sandbox_deploy_check': {
935
+ if (!args.port || !args.project || !args.output_dir) {
936
+ throw new Error('port, project, and output_dir are required.');
937
+ }
938
+ const sandboxWsId8 = args.workspace_id || getCurrentWorkspaceId();
939
+ if (!sandboxWsId8) {
940
+ throw new Error(
941
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
942
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
943
+ );
944
+ }
945
+ const sandboxUser8 = args.username || 'root';
946
+ const sandboxTimeout8 = args.timeout_ms || 30000;
947
+ return await deployCheck(
948
+ sandboxWsId8,
949
+ {
950
+ port: args.port,
951
+ project: args.project,
952
+ outputDir: args.output_dir,
953
+ frameworkType: args.framework_type,
954
+ },
955
+ sandboxUser8,
956
+ sandboxTimeout8,
957
+ );
958
+ }
821
959
  case 'huaweicloud_sandbox_check_user':
822
960
  return await hdkitCheckUser();
823
961
  case 'huaweicloud_sandbox_sign_agreement':
@@ -827,11 +965,38 @@ export async function callTool(name, args = {}) {
827
965
  const devStageId = connectResult?.dev_stage_id || connectResult?.devStageId;
828
966
  if (devStageId) {
829
967
  setWorkspaceId(devStageId);
968
+ try {
969
+ await execOneShot(devStageId, 'devbridge delete-all 2>/dev/null || true', 'root', 15000);
970
+ } catch {}
830
971
  }
831
972
  return connectResult;
832
973
  }
833
- case 'huaweicloud_sandbox_credentials':
834
- return await hdkitCredentials(args.session_id, args.dev_stage_id, args.enable_sts !== false);
974
+ case 'huaweicloud_sandbox_credentials': {
975
+ const devStageId = args.dev_stage_id || getCurrentWorkspaceId();
976
+ const credResult = await hdkitCredentials(args.session_id, devStageId, args.enable_sts !== false);
977
+ const sandboxWsIdCred = args.dev_stage_id || getCurrentWorkspaceId();
978
+ if (sandboxWsIdCred) {
979
+ try {
980
+ const { ak, sk, securitytoken } = getCredentials();
981
+ const credsScript = [
982
+ `export HW_ACCESS_KEY='${ak}'`,
983
+ `export HW_SECRET_KEY='${sk}'`,
984
+ securitytoken ? `export HW_SECURITY_TOKEN='${securitytoken}'` : '',
985
+ securitytoken ? `export X_HW_SECURITY_TOKEN='${securitytoken}'` : '',
986
+ ]
987
+ .filter(Boolean)
988
+ .join('\n');
989
+ const credsFile = '/tmp/hw_creds.sh';
990
+ await execOneShot(
991
+ sandboxWsIdCred,
992
+ `cat > ${credsFile} << 'HWCREDS_EOF'\n${credsScript}\nHWCREDS_EOF\nchmod 600 ${credsFile}`,
993
+ 'root',
994
+ 15000,
995
+ );
996
+ } catch {}
997
+ }
998
+ return credResult;
999
+ }
835
1000
  case 'huaweicloud_voucher_status':
836
1001
  return await hdkitVoucherStatus(args.domain_id);
837
1002
  case 'huaweicloud_voucher_claim':
@@ -1066,12 +1231,17 @@ async function runApprovedCommand(args = {}) {
1066
1231
  if (args.approvedByUser !== true) {
1067
1232
  throw new Error('approvedByUser must be true after explicit user approval for this exact command.');
1068
1233
  }
1069
- const strictPlan = planHcloudCommand(args.args || [], { allowWrites: false });
1070
- const plan = planHcloudCommand(args.args || [], { allowWrites: true });
1071
- if (String(args.approvedCommand || '') !== plan.command) {
1072
- throw new Error('approvedCommand must exactly match the planned hcloud command.');
1234
+ const token = String(args.approvalToken || '');
1235
+ const storedArgs = consumeApprovalToken(token);
1236
+ if (!storedArgs || storedArgs.length === 0) {
1237
+ throw new Error('Invalid or expired approval token. Please re-plan the command.');
1238
+ }
1239
+ const providedArgs = Array.isArray(args.args) ? args.args.map(String) : [];
1240
+ if (JSON.stringify(storedArgs) !== JSON.stringify(providedArgs)) {
1241
+ throw new Error('Provided args do not match the approved plan. Use the exact args from the plan.');
1073
1242
  }
1074
- const result = await runHcloud(args.args || [], {
1243
+ const strictPlan = planHcloudCommand(providedArgs, { allowWrites: false });
1244
+ const result = await runHcloud(providedArgs, {
1075
1245
  allowWrites: true,
1076
1246
  timeoutMs: args.timeoutMs,
1077
1247
  maxRetries: args.maxRetries,
@@ -1184,6 +1354,11 @@ function serviceCatalog(intent = '') {
1184
1354
  skills: ['huawei-dds-dcs'],
1185
1355
  services: ['DDS', 'DCS'],
1186
1356
  },
1357
+ {
1358
+ keywords: ['voucher', 'coupon', 'incentive', 'credit', '领券', '代金券', '优惠券', '激励金', '领取'],
1359
+ skills: ['huawei-voucher'],
1360
+ services: ['Incentive Voucher'],
1361
+ },
1187
1362
  ];
1188
1363
  const matched = [];
1189
1364
  const tokens = new Set(it.split(/[\s,./-]+/).filter((t) => t.length > 0));
@@ -38,15 +38,36 @@ function closeQuietly(ws) {
38
38
  }
39
39
  }
40
40
 
41
- function escapeRegExp(text) {
42
- return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
43
- }
44
-
45
41
  function stripEchoedLine(text, line, options = {}) {
46
- const escaped = escapeRegExp(line);
47
- const stripped = text.replace(new RegExp(`(^|\\r?\\n)${escaped}\\r?\\n?`), '$1');
48
- if (!options.allowAttached || stripped !== text) return stripped;
49
- return text.replace(new RegExp(`${escaped}(?:\\r?\\n){0,2}$`), '');
42
+ if (text.startsWith(line)) {
43
+ text = text.slice(line.length);
44
+ text = text.replace(/^\r?\n/, '');
45
+ if (!options.allowAttached) return text;
46
+ }
47
+
48
+ for (const nl of ['\r\n', '\n']) {
49
+ const idx = text.indexOf(nl + line);
50
+ if (idx !== -1) {
51
+ const before = text.slice(0, idx + nl.length);
52
+ const after = text.slice(idx + nl.length + line.length);
53
+ text = before + after.replace(/^\r?\n/, '');
54
+ if (!options.allowAttached) return text;
55
+ break;
56
+ }
57
+ }
58
+
59
+ if (options.allowAttached) {
60
+ for (let trail = 2; trail >= 0; trail--) {
61
+ for (const nl of ['\r\n', '\n']) {
62
+ const suffix = nl.repeat(trail);
63
+ if (text.endsWith(line + suffix)) {
64
+ return text.slice(0, text.length - line.length - suffix.length);
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ return text;
50
71
  }
51
72
 
52
73
  function buildMarkers(nonce = randomBytes(8).toString('hex')) {