huaweicloud-devkit 1.1.7 → 1.1.8-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.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +2 -1
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +19 -1
- package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/safety/policy.json +1 -1
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +57 -2
- package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +19 -16
- package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +6 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +7 -1
- package/plugins/huaweicloud-core/src/auth/service.mjs +36 -0
- package/plugins/huaweicloud-core/src/detect-framework.mjs +41 -5
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +46 -7
- package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +94 -1
- package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +1 -1
- package/plugins/huaweicloud-core/src/safety-policy.mjs +165 -33
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +181 -10
- package/plugins/huaweicloud-core/src/search-market.mjs +40 -1
- package/plugins/huaweicloud-core/src/setup-cli.mjs +34 -0
- package/plugins/huaweicloud-core/src/tools.mjs +266 -17
- package/rules/huawei-agent-rules.mdc +78 -0
|
@@ -566,6 +566,20 @@ function copyDir(src, dest) {
|
|
|
566
566
|
}
|
|
567
567
|
}
|
|
568
568
|
|
|
569
|
+
// Copy the global agent rules file (rules/huawei-agent-rules.mdc) into the
|
|
570
|
+
// plugin destination so installed agents can load behavioral constraints
|
|
571
|
+
// (#758 D4-23). Idempotent — safe to call on install and update.
|
|
572
|
+
const AGENT_RULES_FILE = 'huawei-agent-rules.mdc';
|
|
573
|
+
|
|
574
|
+
function injectAgentRules(pluginDest) {
|
|
575
|
+
const rulesSrc = join(PACKAGE_ROOT, 'rules', AGENT_RULES_FILE);
|
|
576
|
+
if (!existsSync(rulesSrc)) return;
|
|
577
|
+
const rulesDestDir = join(pluginDest, 'rules');
|
|
578
|
+
mkdirSync(rulesDestDir, { recursive: true });
|
|
579
|
+
copyFileVerified(rulesSrc, join(rulesDestDir, AGENT_RULES_FILE));
|
|
580
|
+
console.log(` Agent Rules -> ${join(rulesDestDir, AGENT_RULES_FILE)}`);
|
|
581
|
+
}
|
|
582
|
+
|
|
569
583
|
function installRuntimeDeps(pluginsDir) {
|
|
570
584
|
const pkgJson = {
|
|
571
585
|
name: 'huaweicloud-devkit',
|
|
@@ -866,6 +880,7 @@ async function installOpenCode() {
|
|
|
866
880
|
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
867
881
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
868
882
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
883
|
+
injectAgentRules(pluginDest);
|
|
869
884
|
const opcPlugins = join(configRoot('opencode'), 'plugins');
|
|
870
885
|
mkdirSync(opcPlugins, { recursive: true });
|
|
871
886
|
copyFileSync(pluginSrc, join(opcPlugins, 'skill-tracker.js'));
|
|
@@ -945,6 +960,7 @@ async function updateOpenCode() {
|
|
|
945
960
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
946
961
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
947
962
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
963
|
+
injectAgentRules(pluginDest);
|
|
948
964
|
const opcPlugins = join(configRoot('opencode'), 'plugins');
|
|
949
965
|
mkdirSync(opcPlugins, { recursive: true });
|
|
950
966
|
copyFileSync(pluginSrc, join(opcPlugins, 'skill-tracker.js'));
|
|
@@ -1045,6 +1061,7 @@ async function installOpenClaw() {
|
|
|
1045
1061
|
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1046
1062
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1047
1063
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1064
|
+
injectAgentRules(pluginDest);
|
|
1048
1065
|
|
|
1049
1066
|
const mcpServerAbsPath = join(pluginDest, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
1050
1067
|
writeMcpServersFile(pluginDest, mcpServerAbsPath, 'openclaw');
|
|
@@ -1105,6 +1122,7 @@ async function updateOpenClaw() {
|
|
|
1105
1122
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1106
1123
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1107
1124
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1125
|
+
injectAgentRules(pluginDest);
|
|
1108
1126
|
|
|
1109
1127
|
const mcpServerAbsPath = join(pluginDest, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
1110
1128
|
writeMcpServersFile(pluginDest, mcpServerAbsPath, 'openclaw');
|
|
@@ -1135,6 +1153,7 @@ async function installCodexDesktop() {
|
|
|
1135
1153
|
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1136
1154
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1137
1155
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1156
|
+
injectAgentRules(pluginDest);
|
|
1138
1157
|
|
|
1139
1158
|
// Copy assets (icons, logos) for Codex Desktop plugin UI
|
|
1140
1159
|
const codexAssetsSrc = join(PLUGIN_ROOT, 'assets');
|
|
@@ -1193,6 +1212,7 @@ async function updateCodexDesktop() {
|
|
|
1193
1212
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1194
1213
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1195
1214
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1215
|
+
injectAgentRules(pluginDest);
|
|
1196
1216
|
|
|
1197
1217
|
// Copy assets (icons, logos) for Codex Desktop plugin UI
|
|
1198
1218
|
const codexAssetsSrc = join(PLUGIN_ROOT, 'assets');
|
|
@@ -1328,6 +1348,7 @@ async function installCodeArts() {
|
|
|
1328
1348
|
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1329
1349
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1330
1350
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1351
|
+
injectAgentRules(pluginDest);
|
|
1331
1352
|
|
|
1332
1353
|
const codeartsHookDir = join(homedir(), '.codeartsdoer', 'plugins');
|
|
1333
1354
|
mkdirSync(codeartsHookDir, { recursive: true });
|
|
@@ -1357,6 +1378,7 @@ async function updateCodeArts() {
|
|
|
1357
1378
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1358
1379
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1359
1380
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1381
|
+
injectAgentRules(pluginDest);
|
|
1360
1382
|
|
|
1361
1383
|
const codeartsHookDir = join(homedir(), '.codeartsdoer', 'plugins');
|
|
1362
1384
|
mkdirSync(codeartsHookDir, { recursive: true });
|
|
@@ -1512,6 +1534,7 @@ async function installCodeArtsWork() {
|
|
|
1512
1534
|
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1513
1535
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1514
1536
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1537
|
+
injectAgentRules(pluginDest);
|
|
1515
1538
|
|
|
1516
1539
|
registerCodeartsWorkMcp();
|
|
1517
1540
|
installRuntimeDeps(pluginDest);
|
|
@@ -1529,6 +1552,7 @@ async function updateCodeArtsWork() {
|
|
|
1529
1552
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1530
1553
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1531
1554
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1555
|
+
injectAgentRules(pluginDest);
|
|
1532
1556
|
registerCodeartsWorkMcp();
|
|
1533
1557
|
mkdirSync(pluginDest, { recursive: true });
|
|
1534
1558
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
@@ -1792,6 +1816,7 @@ async function installWorkBuddy() {
|
|
|
1792
1816
|
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1793
1817
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1794
1818
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1819
|
+
injectAgentRules(pluginDest);
|
|
1795
1820
|
|
|
1796
1821
|
ensureWorkbuddyMcpConfig();
|
|
1797
1822
|
installRuntimeDeps(pluginDest);
|
|
@@ -1814,6 +1839,7 @@ async function updateWorkBuddy() {
|
|
|
1814
1839
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1815
1840
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1816
1841
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1842
|
+
injectAgentRules(pluginDest);
|
|
1817
1843
|
ensureWorkbuddyMcpConfig();
|
|
1818
1844
|
mkdirSync(pluginDest, { recursive: true });
|
|
1819
1845
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
@@ -1985,6 +2011,7 @@ async function installAtomCode() {
|
|
|
1985
2011
|
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1986
2012
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1987
2013
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
2014
|
+
injectAgentRules(pluginDest);
|
|
1988
2015
|
|
|
1989
2016
|
ensureAtomcodeMcpConfig();
|
|
1990
2017
|
deployAtomcodeHooks();
|
|
@@ -2005,6 +2032,7 @@ async function updateAtomCode() {
|
|
|
2005
2032
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
2006
2033
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
2007
2034
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
2035
|
+
injectAgentRules(pluginDest);
|
|
2008
2036
|
ensureAtomcodeMcpConfig();
|
|
2009
2037
|
deployAtomcodeHooks();
|
|
2010
2038
|
mkdirSync(pluginDest, { recursive: true });
|
|
@@ -2294,6 +2322,7 @@ async function installDsh() {
|
|
|
2294
2322
|
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
2295
2323
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
2296
2324
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
2325
|
+
injectAgentRules(pluginDest);
|
|
2297
2326
|
copyFileSync(hookSrc, join(pluginDest, 'hook-plugin.mjs'));
|
|
2298
2327
|
console.log(` Hook Plugin -> ${join(pluginDest, 'hook-plugin.mjs')}`);
|
|
2299
2328
|
ensureDshMcpPatch();
|
|
@@ -2317,6 +2346,7 @@ async function updateDsh() {
|
|
|
2317
2346
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
2318
2347
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
2319
2348
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
2349
|
+
injectAgentRules(pluginDest);
|
|
2320
2350
|
copyFileSync(hookSrc, join(pluginDest, 'hook-plugin.mjs'));
|
|
2321
2351
|
console.log(` Hook Plugin updated -> ${join(pluginDest, 'hook-plugin.mjs')}`);
|
|
2322
2352
|
ensureDshMcpPatch();
|
|
@@ -2425,6 +2455,7 @@ async function installOfficeAce() {
|
|
|
2425
2455
|
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
2426
2456
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
2427
2457
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
2458
|
+
injectAgentRules(pluginDest);
|
|
2428
2459
|
|
|
2429
2460
|
installRuntimeDeps(pluginDest);
|
|
2430
2461
|
if (ensureOfficeaceMcpInSqlite() === 'owner-missing') {
|
|
@@ -2453,6 +2484,7 @@ async function updateOfficeAce() {
|
|
|
2453
2484
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
2454
2485
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
2455
2486
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
2487
|
+
injectAgentRules(pluginDest);
|
|
2456
2488
|
installRuntimeDeps(pluginDest);
|
|
2457
2489
|
if (ensureOfficeaceMcpInSqlite() === 'owner-missing') {
|
|
2458
2490
|
console.log(
|
|
@@ -2950,6 +2982,7 @@ async function installHermes() {
|
|
|
2950
2982
|
}
|
|
2951
2983
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
2952
2984
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
2985
|
+
injectAgentRules(pluginDest);
|
|
2953
2986
|
copyDir(hooksDir, join(pluginDest, 'hooks'));
|
|
2954
2987
|
console.log(` Safety Hooks -> ${join(pluginDest, 'hooks')}`);
|
|
2955
2988
|
// Telemetry hook lives in integrations/hermes/hooks/ (platform-specific adapter)
|
|
@@ -2981,6 +3014,7 @@ async function updateHermes() {
|
|
|
2981
3014
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
2982
3015
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
2983
3016
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
3017
|
+
injectAgentRules(pluginDest);
|
|
2984
3018
|
copyDir(hooksDir, join(pluginDest, 'hooks'));
|
|
2985
3019
|
console.log(` Safety Hooks updated -> ${join(pluginDest, 'hooks')}`);
|
|
2986
3020
|
// Telemetry hook lives in integrations/hermes/hooks/ (platform-specific adapter)
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
uploadProjectWithSession,
|
|
21
21
|
deployNginx,
|
|
22
22
|
deployCheck,
|
|
23
|
+
exposeTunnel,
|
|
23
24
|
getCurrentWorkspaceId,
|
|
24
25
|
setWorkspaceId,
|
|
25
26
|
} from './sandbox/session-manager.mjs';
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
writeLastSync,
|
|
47
48
|
readCodeArtsCredentials,
|
|
48
49
|
globalCredentialsPath,
|
|
50
|
+
parseStsExpiry,
|
|
49
51
|
resolveCredentialsWithRuntime,
|
|
50
52
|
} from './auth/credentials.mjs';
|
|
51
53
|
import { trackToolInvoke, trackSkillRetrieve, clearUserHash } from './telemetry/telemetry.mjs';
|
|
@@ -734,6 +736,29 @@ export const TOOL_DEFINITIONS = [
|
|
|
734
736
|
},
|
|
735
737
|
},
|
|
736
738
|
},
|
|
739
|
+
{
|
|
740
|
+
name: 'huaweicloud_sandbox_expose_tunnel',
|
|
741
|
+
description:
|
|
742
|
+
'Expose a port on the sandbox to a public DevBridge tunnel URL (devbridge 0.2.x). Authenticates automatically (probing AK/SK vs API-Key builds, using /tmp/hw_creds.sh and /tmp/hw_api_key as injected by huaweicloud_sandbox_credentials), pre-cleans stale tunnels to avoid quota errors, starts the host, extracts the public URL (https://<id>-<port>.devbridge-s2.hwtunnel.com), and health-checks it before returning. Use the ACTUAL port reported by huaweicloud_sandbox_deploy_nginx (its "port" field auto-increments on conflict) — never the originally requested port. This automates the manual "Expose via DevBridge" 5-step flow. Returns ok, publicUrl, port, tunnelId, and warnings. If the API Key is missing on a release build it returns ok:false with instructions to create one.',
|
|
743
|
+
inputSchema: {
|
|
744
|
+
type: 'object',
|
|
745
|
+
required: ['port'],
|
|
746
|
+
properties: {
|
|
747
|
+
port: {
|
|
748
|
+
type: 'number',
|
|
749
|
+
description:
|
|
750
|
+
'Actual port nginx is listening on — use the "port" value returned by huaweicloud_sandbox_deploy_nginx, not the originally requested port.',
|
|
751
|
+
},
|
|
752
|
+
workspace_id: {
|
|
753
|
+
type: 'string',
|
|
754
|
+
description:
|
|
755
|
+
'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
|
|
756
|
+
},
|
|
757
|
+
username: { type: 'string', description: 'Login username (default: root)' },
|
|
758
|
+
timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 90000)' },
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
},
|
|
737
762
|
{
|
|
738
763
|
name: 'huaweicloud_sandbox_check_user',
|
|
739
764
|
description:
|
|
@@ -1404,6 +1429,23 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1404
1429
|
sandboxTimeout8,
|
|
1405
1430
|
);
|
|
1406
1431
|
}
|
|
1432
|
+
case 'huaweicloud_sandbox_expose_tunnel': {
|
|
1433
|
+
if (!args.port) {
|
|
1434
|
+
throw new Error(
|
|
1435
|
+
'port is required. Use the ACTUAL "port" value returned by huaweicloud_sandbox_deploy_nginx (it may have been auto-incremented on a port conflict).',
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
const sandboxWsId9 = args.workspace_id || getCurrentWorkspaceId();
|
|
1439
|
+
if (!sandboxWsId9) {
|
|
1440
|
+
throw new Error(
|
|
1441
|
+
'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
|
|
1442
|
+
'or set HW_WORKSPACE_ID environment variable before starting the agent.',
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
const sandboxUser9 = args.username || 'root';
|
|
1446
|
+
const sandboxTimeout9 = args.timeout_ms || 90000;
|
|
1447
|
+
return await exposeTunnel(sandboxWsId9, { port: args.port }, sandboxUser9, sandboxTimeout9);
|
|
1448
|
+
}
|
|
1407
1449
|
case 'huaweicloud_sandbox_check_user':
|
|
1408
1450
|
return await hdkitCheckUser();
|
|
1409
1451
|
case 'huaweicloud_sandbox_sign_agreement':
|
|
@@ -1750,11 +1792,80 @@ const SERVICE_EXAMPLES = {
|
|
|
1750
1792
|
DCS: { list: 'DCS ListInstances', create: 'DCS CreateInstance', show: 'DCS ShowInstance' },
|
|
1751
1793
|
};
|
|
1752
1794
|
|
|
1795
|
+
// DMS and DEW are Huawei Cloud aggregate service names that do not exist as
|
|
1796
|
+
// single KooCLI service identifiers. Each maps to real KooCLI sub-services.
|
|
1797
|
+
const AGGREGATE_SERVICE_MAP = {
|
|
1798
|
+
DMS: ['Kafka', 'RabbitMQ', 'RocketMQ'],
|
|
1799
|
+
DEW: ['KMS', 'CSMS'],
|
|
1800
|
+
};
|
|
1801
|
+
|
|
1802
|
+
// Identity-class notes per service. BSS leads: its operations are split between
|
|
1803
|
+
// Customer-level APIs (work with a normal IAM user) and Partner-level APIs
|
|
1804
|
+
// (only valid for a partner/dealer identity). Calling a Partner-level API with a
|
|
1805
|
+
// customer credential returns APIGW.0301 even when AK/SK are perfectly valid —
|
|
1806
|
+
// the common false-positive diagnosis is "cli-domain-id missing".
|
|
1807
|
+
const SERVICE_IDENTITY_NOTES = {
|
|
1808
|
+
BSS: [
|
|
1809
|
+
{
|
|
1810
|
+
identity: 'Customer-level (normal IAM user)',
|
|
1811
|
+
apis: [
|
|
1812
|
+
'ShowCustomerAccountBalances',
|
|
1813
|
+
'ListCustomerBillsFeeRecords',
|
|
1814
|
+
'ListCustomerCouponChangeRecords',
|
|
1815
|
+
'ListResourceUsage',
|
|
1816
|
+
'ListCosts',
|
|
1817
|
+
],
|
|
1818
|
+
note: 'Work with a normal IAM user that has BSS Administrator or Finance role.',
|
|
1819
|
+
},
|
|
1820
|
+
{
|
|
1821
|
+
identity: 'Partner-level (partner/dealer only)',
|
|
1822
|
+
apis: ['ListQuotaCoupons', 'ListSubCustomerCoupons', 'ListSubCustomerBillDetail'],
|
|
1823
|
+
note: 'APIGW.0301 here means the caller identity is not a partner — NOT bad AK/SK or a missing cli-domain-id.',
|
|
1824
|
+
},
|
|
1825
|
+
],
|
|
1826
|
+
};
|
|
1827
|
+
|
|
1753
1828
|
async function listOperations(service, options = {}) {
|
|
1754
1829
|
const serviceName = String(service || '').trim();
|
|
1755
1830
|
if (!/^[A-Za-z][A-Za-z0-9-]{1,63}$/.test(serviceName)) {
|
|
1756
1831
|
throw new Error('service must be a KooCLI service name such as ECS, VPC, IMS, OBS, RDS, or CDN.');
|
|
1757
1832
|
}
|
|
1833
|
+
const upperName = serviceName.toUpperCase();
|
|
1834
|
+
const subServices = AGGREGATE_SERVICE_MAP[upperName];
|
|
1835
|
+
if (subServices && subServices.length) {
|
|
1836
|
+
const results = await Promise.all(
|
|
1837
|
+
subServices.map(async (sub) => {
|
|
1838
|
+
const r = await runHcloud([sub, '--help'], {
|
|
1839
|
+
timeoutMs: options.timeoutMs,
|
|
1840
|
+
maxRetries: 0,
|
|
1841
|
+
});
|
|
1842
|
+
let result = r;
|
|
1843
|
+
if (!result.ok) {
|
|
1844
|
+
result = await runHcloud([sub, 'help'], {
|
|
1845
|
+
timeoutMs: options.timeoutMs,
|
|
1846
|
+
maxRetries: 0,
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
return {
|
|
1850
|
+
subService: sub,
|
|
1851
|
+
command: `hcloud ${sub} --help`,
|
|
1852
|
+
examples: SERVICE_EXAMPLES[sub.toUpperCase()] || {
|
|
1853
|
+
note: `No cached examples for ${sub}. Use the help text above to discover available operations.`,
|
|
1854
|
+
},
|
|
1855
|
+
result,
|
|
1856
|
+
};
|
|
1857
|
+
}),
|
|
1858
|
+
);
|
|
1859
|
+
return {
|
|
1860
|
+
service: serviceName,
|
|
1861
|
+
aggregate: true,
|
|
1862
|
+
aggregatedFrom: subServices,
|
|
1863
|
+
selectionRule:
|
|
1864
|
+
'DMS/DEW are aggregate service names. Use each sub-service help text below to select the exact KooCLI operation name.',
|
|
1865
|
+
identityNotes: SERVICE_IDENTITY_NOTES[upperName] || null,
|
|
1866
|
+
subServices: results,
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1758
1869
|
const isObs = /^obs$/i.test(serviceName);
|
|
1759
1870
|
const svc = isObs ? 'obs' : serviceName;
|
|
1760
1871
|
const args = isObs ? ['obs', 'help'] : [svc, '--help'];
|
|
@@ -1771,8 +1882,10 @@ async function listOperations(service, options = {}) {
|
|
|
1771
1882
|
return {
|
|
1772
1883
|
service: serviceName,
|
|
1773
1884
|
command: isObs ? 'hcloud obs help' : `hcloud ${svc} --help`,
|
|
1774
|
-
selectionRule:
|
|
1775
|
-
|
|
1885
|
+
selectionRule:
|
|
1886
|
+
'Use this help text to select the exact KooCLI operation name before planning any service command. After selecting an operation, run "hcloud <Service> <Operation> --help" to confirm exact parameter names before constructing the command.',
|
|
1887
|
+
identityNotes: SERVICE_IDENTITY_NOTES[upperName] || null,
|
|
1888
|
+
examples: SERVICE_EXAMPLES[upperName] || {
|
|
1776
1889
|
note: `No cached examples for ${serviceName}. Use the help text above to discover available operations.`,
|
|
1777
1890
|
},
|
|
1778
1891
|
result,
|
|
@@ -1816,22 +1929,79 @@ function serviceCatalog(intent = '') {
|
|
|
1816
1929
|
const it = String(intent).toLowerCase();
|
|
1817
1930
|
const routeMap = [
|
|
1818
1931
|
{
|
|
1819
|
-
keywords: [
|
|
1932
|
+
keywords: [
|
|
1933
|
+
'ecs',
|
|
1934
|
+
'server',
|
|
1935
|
+
'vm',
|
|
1936
|
+
'instance',
|
|
1937
|
+
'compute',
|
|
1938
|
+
'flavor',
|
|
1939
|
+
'image',
|
|
1940
|
+
'弹性云服务器',
|
|
1941
|
+
'云服务器',
|
|
1942
|
+
'服务器',
|
|
1943
|
+
'虚拟机',
|
|
1944
|
+
'镜像',
|
|
1945
|
+
],
|
|
1820
1946
|
skills: ['huawei-ecs'],
|
|
1821
1947
|
services: ['ECS'],
|
|
1822
1948
|
},
|
|
1823
1949
|
{
|
|
1824
|
-
keywords: [
|
|
1950
|
+
keywords: [
|
|
1951
|
+
'vpc',
|
|
1952
|
+
'subnet',
|
|
1953
|
+
'network',
|
|
1954
|
+
'security group',
|
|
1955
|
+
'eip',
|
|
1956
|
+
'nat',
|
|
1957
|
+
'vpn',
|
|
1958
|
+
'bandwidth',
|
|
1959
|
+
'虚拟私有云',
|
|
1960
|
+
'子网',
|
|
1961
|
+
'网络',
|
|
1962
|
+
'安全组',
|
|
1963
|
+
'弹性公网IP',
|
|
1964
|
+
'NAT网关',
|
|
1965
|
+
'VPN网关',
|
|
1966
|
+
'带宽',
|
|
1967
|
+
],
|
|
1825
1968
|
skills: ['huawei-vpc'],
|
|
1826
1969
|
services: ['VPC', 'EIP'],
|
|
1827
1970
|
},
|
|
1828
1971
|
{
|
|
1829
|
-
keywords: [
|
|
1972
|
+
keywords: [
|
|
1973
|
+
'obs',
|
|
1974
|
+
'bucket',
|
|
1975
|
+
'storage',
|
|
1976
|
+
'object',
|
|
1977
|
+
'static website',
|
|
1978
|
+
'static site',
|
|
1979
|
+
'hosting',
|
|
1980
|
+
'对象存储',
|
|
1981
|
+
'存储桶',
|
|
1982
|
+
'桶',
|
|
1983
|
+
'存储',
|
|
1984
|
+
'对象',
|
|
1985
|
+
'静态网站',
|
|
1986
|
+
'静态托管',
|
|
1987
|
+
],
|
|
1830
1988
|
skills: ['huawei-obs'],
|
|
1831
1989
|
services: ['OBS'],
|
|
1832
1990
|
},
|
|
1833
1991
|
{
|
|
1834
|
-
keywords: [
|
|
1992
|
+
keywords: [
|
|
1993
|
+
'functiongraph',
|
|
1994
|
+
'serverless',
|
|
1995
|
+
'function',
|
|
1996
|
+
'lambda',
|
|
1997
|
+
'trigger',
|
|
1998
|
+
'faas',
|
|
1999
|
+
'函数工作流',
|
|
2000
|
+
'函数',
|
|
2001
|
+
'触发器',
|
|
2002
|
+
'无服务器',
|
|
2003
|
+
'函数计算',
|
|
2004
|
+
],
|
|
1835
2005
|
skills: ['huawei-functiongraph'],
|
|
1836
2006
|
services: ['FunctionGraph'],
|
|
1837
2007
|
},
|
|
@@ -1840,20 +2010,60 @@ function serviceCatalog(intent = '') {
|
|
|
1840
2010
|
skills: ['huawei-cce'],
|
|
1841
2011
|
services: ['CCE', 'SWR'],
|
|
1842
2012
|
},
|
|
1843
|
-
{ keywords: ['apig', 'api gateway', 'publish', 'throttle'], skills: ['huawei-apig'], services: ['APIG'] },
|
|
1844
|
-
{ keywords: ['rds', 'mysql', 'postgresql', 'database', 'db'], skills: ['huawei-rds'], services: ['RDS'] },
|
|
1845
2013
|
{
|
|
1846
|
-
keywords: ['
|
|
2014
|
+
keywords: ['apig', 'api gateway', 'publish', 'throttle', 'API网关', '限流'],
|
|
2015
|
+
skills: ['huawei-apig'],
|
|
2016
|
+
services: ['APIG'],
|
|
2017
|
+
},
|
|
2018
|
+
{
|
|
2019
|
+
keywords: ['rds', 'mysql', 'postgresql', 'database', 'db', '关系型数据库', '数据库'],
|
|
2020
|
+
skills: ['huawei-rds'],
|
|
2021
|
+
services: ['RDS'],
|
|
2022
|
+
},
|
|
2023
|
+
{
|
|
2024
|
+
keywords: ['gaussdb', 'distributed', 'sharding', 'opengauss', '高斯数据库', '分布式数据库'],
|
|
1847
2025
|
skills: ['huawei-gaussdb'],
|
|
1848
2026
|
services: ['GaussDB'],
|
|
1849
2027
|
},
|
|
1850
2028
|
{
|
|
1851
|
-
keywords: [
|
|
2029
|
+
keywords: [
|
|
2030
|
+
'iam',
|
|
2031
|
+
'permission',
|
|
2032
|
+
'policy',
|
|
2033
|
+
'role',
|
|
2034
|
+
'user',
|
|
2035
|
+
'ak/sk',
|
|
2036
|
+
'access key',
|
|
2037
|
+
'agency',
|
|
2038
|
+
'身份认证',
|
|
2039
|
+
'身份管理',
|
|
2040
|
+
'权限',
|
|
2041
|
+
'策略',
|
|
2042
|
+
'角色',
|
|
2043
|
+
'用户',
|
|
2044
|
+
'访问密钥',
|
|
2045
|
+
'委托',
|
|
2046
|
+
],
|
|
1852
2047
|
skills: ['huawei-iam'],
|
|
1853
2048
|
services: ['IAM'],
|
|
1854
2049
|
},
|
|
1855
2050
|
{
|
|
1856
|
-
keywords: [
|
|
2051
|
+
keywords: [
|
|
2052
|
+
'dew',
|
|
2053
|
+
'secret',
|
|
2054
|
+
'kms',
|
|
2055
|
+
'encrypt',
|
|
2056
|
+
'decrypt',
|
|
2057
|
+
'certificate',
|
|
2058
|
+
'csms',
|
|
2059
|
+
'数据加密服务',
|
|
2060
|
+
'密钥管理',
|
|
2061
|
+
'密钥',
|
|
2062
|
+
'加密',
|
|
2063
|
+
'解密',
|
|
2064
|
+
'证书',
|
|
2065
|
+
'凭据管理',
|
|
2066
|
+
],
|
|
1857
2067
|
skills: ['huawei-dew'],
|
|
1858
2068
|
services: ['CSMS', 'KMS'],
|
|
1859
2069
|
},
|
|
@@ -1863,7 +2073,7 @@ function serviceCatalog(intent = '') {
|
|
|
1863
2073
|
services: ['ModelArts'],
|
|
1864
2074
|
},
|
|
1865
2075
|
{
|
|
1866
|
-
keywords: ['billing', 'cost', 'bill', 'budget', 'expense', 'bss'],
|
|
2076
|
+
keywords: ['billing', 'cost', 'bill', 'budget', 'expense', 'bss', '计费', '费用', '账单', '预算', '开销'],
|
|
1867
2077
|
skills: ['huawei-billing'],
|
|
1868
2078
|
services: ['BSS'],
|
|
1869
2079
|
},
|
|
@@ -1878,12 +2088,16 @@ function serviceCatalog(intent = '') {
|
|
|
1878
2088
|
services: ['SMN', 'DMS'],
|
|
1879
2089
|
},
|
|
1880
2090
|
{
|
|
1881
|
-
keywords: ['ces', 'monitor', 'alarm', 'metric', 'dashboard', 'cloud eye'],
|
|
2091
|
+
keywords: ['ces', 'monitor', 'alarm', 'metric', 'dashboard', 'cloud eye', '监控', '告警', '云监控'],
|
|
1882
2092
|
skills: ['huawei-cloud-eye'],
|
|
1883
2093
|
services: ['CES'],
|
|
1884
2094
|
},
|
|
1885
|
-
{ keywords: ['cts', 'audit', 'trace', 'tracker'], skills: ['huawei-cts'], services: ['CTS'] },
|
|
1886
|
-
{
|
|
2095
|
+
{ keywords: ['cts', 'audit', 'trace', 'tracker', '审计', '追踪'], skills: ['huawei-cts'], services: ['CTS'] },
|
|
2096
|
+
{
|
|
2097
|
+
keywords: ['cbr', 'backup', 'restore', 'vault', 'snapshot', '备份', '恢复', '快照', '存储库'],
|
|
2098
|
+
skills: ['huawei-cbr'],
|
|
2099
|
+
services: ['CBR'],
|
|
2100
|
+
},
|
|
1887
2101
|
{
|
|
1888
2102
|
keywords: ['deployment', 'deploy', 'ci/cd', 'pipeline', 'release'],
|
|
1889
2103
|
skills: ['huawei-deployment'],
|
|
@@ -1909,10 +2123,26 @@ function serviceCatalog(intent = '') {
|
|
|
1909
2123
|
services: ['Sandbox', 'DevStation'],
|
|
1910
2124
|
},
|
|
1911
2125
|
{
|
|
1912
|
-
keywords: [
|
|
2126
|
+
keywords: [
|
|
2127
|
+
'dds',
|
|
2128
|
+
'dcs',
|
|
2129
|
+
'mongodb',
|
|
2130
|
+
'redis',
|
|
2131
|
+
'memcached',
|
|
2132
|
+
'cache',
|
|
2133
|
+
'document db',
|
|
2134
|
+
'文档数据库',
|
|
2135
|
+
'缓存',
|
|
2136
|
+
'内存数据库',
|
|
2137
|
+
],
|
|
1913
2138
|
skills: ['huawei-dds-dcs'],
|
|
1914
2139
|
services: ['DDS', 'DCS'],
|
|
1915
2140
|
},
|
|
2141
|
+
{
|
|
2142
|
+
keywords: ['elb', 'load balancer', '负载均衡', '弹性负载均衡', '监听器', '证书'],
|
|
2143
|
+
skills: ['huawei-vpc'],
|
|
2144
|
+
services: ['ELB'],
|
|
2145
|
+
},
|
|
1916
2146
|
{
|
|
1917
2147
|
keywords: ['voucher', 'coupon', 'incentive', 'credit', '领券', '代金券', '优惠券', '激励金', '领取'],
|
|
1918
2148
|
skills: ['huawei-voucher'],
|
|
@@ -1923,7 +2153,9 @@ function serviceCatalog(intent = '') {
|
|
|
1923
2153
|
const tokens = new Set(it.split(/[\s,./-]+/).filter((t) => t.length > 0));
|
|
1924
2154
|
const cjk = /[\u4e00-\u9fff]/;
|
|
1925
2155
|
for (const route of routeMap) {
|
|
1926
|
-
if (
|
|
2156
|
+
if (
|
|
2157
|
+
route.keywords.some((kw) => (kw.includes(' ') || cjk.test(kw) ? it.includes(kw.toLowerCase()) : tokens.has(kw)))
|
|
2158
|
+
) {
|
|
1927
2159
|
matched.push(route);
|
|
1928
2160
|
}
|
|
1929
2161
|
}
|
|
@@ -2016,6 +2248,23 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
|
|
|
2016
2248
|
}
|
|
2017
2249
|
}
|
|
2018
2250
|
|
|
2251
|
+
// G: when the active credential set is a temporary STS token that has already
|
|
2252
|
+
// expired, surface that as the primary cause before generic auth guidance.
|
|
2253
|
+
let activeCreds;
|
|
2254
|
+
try {
|
|
2255
|
+
activeCreds = resolveCredentialsWithRuntime({ allowMissing: true });
|
|
2256
|
+
} catch {
|
|
2257
|
+
activeCreds = null;
|
|
2258
|
+
}
|
|
2259
|
+
if (activeCreds?.ak && activeCreds?.sk && activeCreds.securityToken) {
|
|
2260
|
+
const expiry = parseStsExpiry({ securityToken: activeCreds.securityToken });
|
|
2261
|
+
if (expiry !== null && expiry <= Date.now()) {
|
|
2262
|
+
suggestions.push(
|
|
2263
|
+
'CREDENTIAL_EXPIRED: the temporary STS security token has expired. Re-authenticate to obtain fresh credentials (e.g. run "npx huaweicloud-devkit auth init" or re-login / restart the session).',
|
|
2264
|
+
);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2019
2268
|
if (/auth|token|credential|ak|sk|401|403|unauthorized|forbidden|Incorrect IAM/i.test(combined)) {
|
|
2020
2269
|
if (svc === 'obs') {
|
|
2021
2270
|
suggestions.push(
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# HuaweiCloud DevKit — Agent Rules
|
|
2
|
+
|
|
3
|
+
> Agent 加载插件时自动读取,定义全局行为约束。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Core Principles
|
|
8
|
+
|
|
9
|
+
- **Always use the Huawei MCP server first** (sandboxed, observable, auditable). Fall back to `hcloud` CLI only when MCP is unavailable.
|
|
10
|
+
- **Before any task, check for available skills.** Use `huaweicloud_search_docs` or `huaweicloud_retrieve_skill` via MCP. Follow skill procedures over general knowledge.
|
|
11
|
+
- **When uncertain** about API parameters, quotas, limits, or error codes — search documentation via MCP. Do not guess.
|
|
12
|
+
- **Prefer IaC over direct CLI calls** for infrastructure creation (Terraform for Huawei Cloud, or CloudFormation equivalent).
|
|
13
|
+
- **Never use em dashes in resource names/tags.** Use hyphens only.
|
|
14
|
+
- **Match user language.** Respond in the same language the user writes in.
|
|
15
|
+
|
|
16
|
+
## Secret Safety
|
|
17
|
+
|
|
18
|
+
- **MUST load the `huawei-dew` skill** first for any secret, credential, API key, token, or password task.
|
|
19
|
+
- **MUST NOT call** `hcloud csms download-secret` or `hcloud csms show-secret` directly.
|
|
20
|
+
- **MUST NOT call** `hcloud kms decrypt` directly.
|
|
21
|
+
- **MUST use** `{{resolve:csms:secret-id:SecretString:key}}` with the MCP proxy — secrets resolve at runtime, never enter agent context.
|
|
22
|
+
- **NEVER** echo AK/SK values in conversation or logs.
|
|
23
|
+
|
|
24
|
+
## IAM Security
|
|
25
|
+
|
|
26
|
+
- **Least privilege by default.** Start with no permissions, add only what is needed.
|
|
27
|
+
- **MUST NOT create IAM users** — use IAM Identity Center (OneAccess) or temporary credentials.
|
|
28
|
+
- **MUST NOT create long-term AK/SK** — use temporary STS tokens.
|
|
29
|
+
- **MUST scope resource ARNs** — no `*` wildcards.
|
|
30
|
+
- **Confused deputy protection:** Add `g:SourceAccount` and `g:SourceUrn` conditions to trust policies.
|
|
31
|
+
|
|
32
|
+
## Network Security
|
|
33
|
+
|
|
34
|
+
- **MUST block public access by default** on OBS buckets.
|
|
35
|
+
- **MUST use security groups** (not iptables) for ECS/CCE access control.
|
|
36
|
+
- **SHOULD use VPC endpoints** for internal service access (no public internet).
|
|
37
|
+
- **MUST enable HTTPS-only** on OBS buckets and API endpoints.
|
|
38
|
+
|
|
39
|
+
## Observability
|
|
40
|
+
|
|
41
|
+
- **SHOULD enable CTS audit logging** on all accounts.
|
|
42
|
+
- **SHOULD configure Cloud Eye alarms** for production workloads (CPU > 80%, memory > 85%, disk > 90%).
|
|
43
|
+
- **SHOULD enable OBS access logging** for audit compliance.
|
|
44
|
+
|
|
45
|
+
## Cost Awareness
|
|
46
|
+
|
|
47
|
+
- **Before creating resources**, inform the user of estimated cost.
|
|
48
|
+
- **SHOULD suggest billing budgets** for production accounts.
|
|
49
|
+
- **SHOULD recommend CBR backup** for stateful resources (RDS, ECS system disks).
|
|
50
|
+
|
|
51
|
+
## Naming Conventions
|
|
52
|
+
|
|
53
|
+
- Resource names: lowercase, hyphens, max 64 chars
|
|
54
|
+
- Tags: include Environment (dev/staging/prod), Owner, Project
|
|
55
|
+
- OBS bucket names: globally unique, lowercase, no underscores
|
|
56
|
+
|
|
57
|
+
## MCP Usage Rules
|
|
58
|
+
|
|
59
|
+
- **Prefer MCP for ALL Huawei Cloud operations** — sandbox, audit, enterprise controls.
|
|
60
|
+
- **When MCP is unavailable:** Fall back to `hcloud` CLI. State clearly: "MCP unavailable, using local hcloud CLI."
|
|
61
|
+
- **`huaweicloud_run_approved_command`:** Only use when the exact planned command has been shown and the user explicitly approved that exact command.
|
|
62
|
+
- **`huaweicloud_run_script`:** Use for deterministic calculations (cost math, data processing). Never do arithmetic in LLM reasoning.
|
|
63
|
+
|
|
64
|
+
## Skill Discovery Priority
|
|
65
|
+
|
|
66
|
+
1. **`huaweicloud-core`** — always check first for routing
|
|
67
|
+
2. **Service-specific skills** — load after routing via `huaweicloud_retrieve_skill`
|
|
68
|
+
3. **Knowledge cards** (`references/`) — quick facts without full skill load
|
|
69
|
+
|
|
70
|
+
## Red Flags — STOP and Re-evaluate
|
|
71
|
+
|
|
72
|
+
- Creating resources without user confirmation
|
|
73
|
+
- Using `hcloud csms download-secret` or `hcloud kms decrypt` directly
|
|
74
|
+
- Guessing API parameters without searching docs
|
|
75
|
+
- Creating IAM users or long-term AK/SK
|
|
76
|
+
- Opening OBS buckets to public access
|
|
77
|
+
- Skipping CTS/Cloud Eye on production workloads
|
|
78
|
+
- Echoing AK/SK or secret values in conversation
|