huaweicloud-devkit 1.1.7 → 1.1.8-next.1
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 +9 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +15 -2
- package/plugins/huaweicloud-core/src/auth/service.mjs +36 -0
- package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +23 -5
- package/plugins/huaweicloud-core/src/detect-framework.mjs +41 -5
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +78 -8
- package/plugins/huaweicloud-core/src/hcloud-probe.mjs +10 -2
- package/plugins/huaweicloud-core/src/mcp-server.mjs +9 -0
- package/plugins/huaweicloud-core/src/preflight.mjs +125 -0
- 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 +214 -27
- package/plugins/huaweicloud-core/src/search-market.mjs +40 -1
- package/plugins/huaweicloud-core/src/setup-cli.mjs +57 -17
- package/plugins/huaweicloud-core/src/tools.mjs +307 -20
- package/rules/huawei-agent-rules.mdc +78 -0
|
@@ -8,7 +8,7 @@ import { createHmac, createHash } from 'node:crypto';
|
|
|
8
8
|
|
|
9
9
|
import { evaluateArtifacts, evaluateCommandRisk, evaluateDeployPlan } from './risk-rule-engine.mjs';
|
|
10
10
|
import { classifyTextCommand, redactSecrets } from './safety-policy.mjs';
|
|
11
|
-
import { planHcloudCommand, runHcloud, consumeApprovalToken, hashArgs } from './hcloud-cli.mjs';
|
|
11
|
+
import { planHcloudCommand, runHcloud, consumeApprovalToken, inspectApprovalToken, hashArgs } from './hcloud-cli.mjs';
|
|
12
12
|
import { searchMarketplace } from './search-market.mjs';
|
|
13
13
|
import { getServiceIcon } from './icon-library.mjs';
|
|
14
14
|
import { detectFramework } from './detect-framework.mjs';
|
|
@@ -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:
|
|
@@ -989,6 +1014,11 @@ async function transferGitRepo(args, devStageId, connectResult) {
|
|
|
989
1014
|
}
|
|
990
1015
|
|
|
991
1016
|
const pendingConfirms = new Map();
|
|
1017
|
+
// Tombstones for confirm tokens already consumed, so a repeat
|
|
1018
|
+
// huaweicloud_auth_confirm call can be classified as already_processed
|
|
1019
|
+
// instead of not_found (D4-24, #745). Kept in-memory only: confirm tokens
|
|
1020
|
+
// are session-scoped and never persisted across processes.
|
|
1021
|
+
const consumedConfirms = new Map();
|
|
992
1022
|
|
|
993
1023
|
function readImportFile() {
|
|
994
1024
|
const path = join(dirname(globalCredentialsPath()), 'creds-import.json');
|
|
@@ -1256,9 +1286,18 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1256
1286
|
return persisted;
|
|
1257
1287
|
}
|
|
1258
1288
|
case 'huaweicloud_auth_confirm': {
|
|
1289
|
+
// D4-24 precise JSON contract (#745): a missing or already-consumed
|
|
1290
|
+
// confirm token returns a structured result instead of throwing a
|
|
1291
|
+
// generic Error string, mirroring runApprovedCommand's contract.
|
|
1259
1292
|
const pending = pendingConfirms.get(args.token);
|
|
1260
|
-
if (!pending)
|
|
1293
|
+
if (!pending) {
|
|
1294
|
+
if (args.token && consumedConfirms.has(args.token)) {
|
|
1295
|
+
return { status: 'ok', outcome: 'already_processed' };
|
|
1296
|
+
}
|
|
1297
|
+
return { status: 'rejected', code: 'CONFIRM_TOKEN_NOT_FOUND' };
|
|
1298
|
+
}
|
|
1261
1299
|
pendingConfirms.delete(args.token);
|
|
1300
|
+
consumedConfirms.set(args.token, { consumedAt: Date.now() });
|
|
1262
1301
|
if (args.decision === 's1') {
|
|
1263
1302
|
return { status: 'ok', outcome: 'aborted', message: '保持 S1 现有账号,未覆盖。' };
|
|
1264
1303
|
}
|
|
@@ -1404,6 +1443,23 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1404
1443
|
sandboxTimeout8,
|
|
1405
1444
|
);
|
|
1406
1445
|
}
|
|
1446
|
+
case 'huaweicloud_sandbox_expose_tunnel': {
|
|
1447
|
+
if (!args.port) {
|
|
1448
|
+
throw new Error(
|
|
1449
|
+
'port is required. Use the ACTUAL "port" value returned by huaweicloud_sandbox_deploy_nginx (it may have been auto-incremented on a port conflict).',
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
const sandboxWsId9 = args.workspace_id || getCurrentWorkspaceId();
|
|
1453
|
+
if (!sandboxWsId9) {
|
|
1454
|
+
throw new Error(
|
|
1455
|
+
'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
|
|
1456
|
+
'or set HW_WORKSPACE_ID environment variable before starting the agent.',
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
const sandboxUser9 = args.username || 'root';
|
|
1460
|
+
const sandboxTimeout9 = args.timeout_ms || 90000;
|
|
1461
|
+
return await exposeTunnel(sandboxWsId9, { port: args.port }, sandboxUser9, sandboxTimeout9);
|
|
1462
|
+
}
|
|
1407
1463
|
case 'huaweicloud_sandbox_check_user':
|
|
1408
1464
|
return await hdkitCheckUser();
|
|
1409
1465
|
case 'huaweicloud_sandbox_sign_agreement':
|
|
@@ -1750,11 +1806,80 @@ const SERVICE_EXAMPLES = {
|
|
|
1750
1806
|
DCS: { list: 'DCS ListInstances', create: 'DCS CreateInstance', show: 'DCS ShowInstance' },
|
|
1751
1807
|
};
|
|
1752
1808
|
|
|
1809
|
+
// DMS and DEW are Huawei Cloud aggregate service names that do not exist as
|
|
1810
|
+
// single KooCLI service identifiers. Each maps to real KooCLI sub-services.
|
|
1811
|
+
const AGGREGATE_SERVICE_MAP = {
|
|
1812
|
+
DMS: ['Kafka', 'RabbitMQ', 'RocketMQ'],
|
|
1813
|
+
DEW: ['KMS', 'CSMS'],
|
|
1814
|
+
};
|
|
1815
|
+
|
|
1816
|
+
// Identity-class notes per service. BSS leads: its operations are split between
|
|
1817
|
+
// Customer-level APIs (work with a normal IAM user) and Partner-level APIs
|
|
1818
|
+
// (only valid for a partner/dealer identity). Calling a Partner-level API with a
|
|
1819
|
+
// customer credential returns APIGW.0301 even when AK/SK are perfectly valid —
|
|
1820
|
+
// the common false-positive diagnosis is "cli-domain-id missing".
|
|
1821
|
+
const SERVICE_IDENTITY_NOTES = {
|
|
1822
|
+
BSS: [
|
|
1823
|
+
{
|
|
1824
|
+
identity: 'Customer-level (normal IAM user)',
|
|
1825
|
+
apis: [
|
|
1826
|
+
'ShowCustomerAccountBalances',
|
|
1827
|
+
'ListCustomerBillsFeeRecords',
|
|
1828
|
+
'ListCustomerCouponChangeRecords',
|
|
1829
|
+
'ListResourceUsage',
|
|
1830
|
+
'ListCosts',
|
|
1831
|
+
],
|
|
1832
|
+
note: 'Work with a normal IAM user that has BSS Administrator or Finance role.',
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
identity: 'Partner-level (partner/dealer only)',
|
|
1836
|
+
apis: ['ListQuotaCoupons', 'ListSubCustomerCoupons', 'ListSubCustomerBillDetail'],
|
|
1837
|
+
note: 'APIGW.0301 here means the caller identity is not a partner — NOT bad AK/SK or a missing cli-domain-id.',
|
|
1838
|
+
},
|
|
1839
|
+
],
|
|
1840
|
+
};
|
|
1841
|
+
|
|
1753
1842
|
async function listOperations(service, options = {}) {
|
|
1754
1843
|
const serviceName = String(service || '').trim();
|
|
1755
1844
|
if (!/^[A-Za-z][A-Za-z0-9-]{1,63}$/.test(serviceName)) {
|
|
1756
1845
|
throw new Error('service must be a KooCLI service name such as ECS, VPC, IMS, OBS, RDS, or CDN.');
|
|
1757
1846
|
}
|
|
1847
|
+
const upperName = serviceName.toUpperCase();
|
|
1848
|
+
const subServices = AGGREGATE_SERVICE_MAP[upperName];
|
|
1849
|
+
if (subServices && subServices.length) {
|
|
1850
|
+
const results = await Promise.all(
|
|
1851
|
+
subServices.map(async (sub) => {
|
|
1852
|
+
const r = await runHcloud([sub, '--help'], {
|
|
1853
|
+
timeoutMs: options.timeoutMs,
|
|
1854
|
+
maxRetries: 0,
|
|
1855
|
+
});
|
|
1856
|
+
let result = r;
|
|
1857
|
+
if (!result.ok) {
|
|
1858
|
+
result = await runHcloud([sub, 'help'], {
|
|
1859
|
+
timeoutMs: options.timeoutMs,
|
|
1860
|
+
maxRetries: 0,
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
return {
|
|
1864
|
+
subService: sub,
|
|
1865
|
+
command: `hcloud ${sub} --help`,
|
|
1866
|
+
examples: SERVICE_EXAMPLES[sub.toUpperCase()] || {
|
|
1867
|
+
note: `No cached examples for ${sub}. Use the help text above to discover available operations.`,
|
|
1868
|
+
},
|
|
1869
|
+
result,
|
|
1870
|
+
};
|
|
1871
|
+
}),
|
|
1872
|
+
);
|
|
1873
|
+
return {
|
|
1874
|
+
service: serviceName,
|
|
1875
|
+
aggregate: true,
|
|
1876
|
+
aggregatedFrom: subServices,
|
|
1877
|
+
selectionRule:
|
|
1878
|
+
'DMS/DEW are aggregate service names. Use each sub-service help text below to select the exact KooCLI operation name.',
|
|
1879
|
+
identityNotes: SERVICE_IDENTITY_NOTES[upperName] || null,
|
|
1880
|
+
subServices: results,
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1758
1883
|
const isObs = /^obs$/i.test(serviceName);
|
|
1759
1884
|
const svc = isObs ? 'obs' : serviceName;
|
|
1760
1885
|
const args = isObs ? ['obs', 'help'] : [svc, '--help'];
|
|
@@ -1771,8 +1896,10 @@ async function listOperations(service, options = {}) {
|
|
|
1771
1896
|
return {
|
|
1772
1897
|
service: serviceName,
|
|
1773
1898
|
command: isObs ? 'hcloud obs help' : `hcloud ${svc} --help`,
|
|
1774
|
-
selectionRule:
|
|
1775
|
-
|
|
1899
|
+
selectionRule:
|
|
1900
|
+
'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.',
|
|
1901
|
+
identityNotes: SERVICE_IDENTITY_NOTES[upperName] || null,
|
|
1902
|
+
examples: SERVICE_EXAMPLES[upperName] || {
|
|
1776
1903
|
note: `No cached examples for ${serviceName}. Use the help text above to discover available operations.`,
|
|
1777
1904
|
},
|
|
1778
1905
|
result,
|
|
@@ -1784,9 +1911,33 @@ async function runApprovedCommand(args = {}) {
|
|
|
1784
1911
|
throw new Error('approvedByUser must be true after explicit user approval for this exact command.');
|
|
1785
1912
|
}
|
|
1786
1913
|
const token = String(args.approvalToken || '');
|
|
1914
|
+
|
|
1915
|
+
// D4-24 precise JSON contract (#745): expired and already-consumed tokens
|
|
1916
|
+
// return a structured result instead of throwing a generic Error string, so
|
|
1917
|
+
// clients can machine-assert the two non-success states.
|
|
1918
|
+
const inspection = inspectApprovalToken(token);
|
|
1919
|
+
if (inspection.state === 'already_consumed') {
|
|
1920
|
+
return { status: 'ok', outcome: 'already_processed' };
|
|
1921
|
+
}
|
|
1922
|
+
if (inspection.state === 'expired') {
|
|
1923
|
+
return { status: 'rejected', code: 'CONFIRM_TOKEN_EXPIRED' };
|
|
1924
|
+
}
|
|
1925
|
+
if (inspection.state === 'not_found') {
|
|
1926
|
+
// A token that was never created (typo / unknown / pruned beyond the
|
|
1927
|
+
// tombstone window) is still a rejection, but carries no expired/already
|
|
1928
|
+
// semantics — surface a distinct code so clients can tell the cases apart.
|
|
1929
|
+
return { status: 'rejected', code: 'CONFIRM_TOKEN_NOT_FOUND' };
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1787
1932
|
const stored = consumeApprovalToken(token);
|
|
1788
1933
|
if (!stored) {
|
|
1789
|
-
|
|
1934
|
+
// Defensive: inspectApprovalToken said 'valid' but consume returned null
|
|
1935
|
+
// (e.g. raced and pruned between the two reads). Re-classify so we never
|
|
1936
|
+
// return a misleading success.
|
|
1937
|
+
const recheck = inspectApprovalToken(token);
|
|
1938
|
+
if (recheck.state === 'expired') return { status: 'rejected', code: 'CONFIRM_TOKEN_EXPIRED' };
|
|
1939
|
+
if (recheck.state === 'already_consumed') return { status: 'ok', outcome: 'already_processed' };
|
|
1940
|
+
return { status: 'rejected', code: 'CONFIRM_TOKEN_NOT_FOUND' };
|
|
1790
1941
|
}
|
|
1791
1942
|
const providedArgs = Array.isArray(args.args) ? args.args.map(String) : [];
|
|
1792
1943
|
if (hashArgs(providedArgs) !== stored.argsHash) {
|
|
@@ -1816,22 +1967,79 @@ function serviceCatalog(intent = '') {
|
|
|
1816
1967
|
const it = String(intent).toLowerCase();
|
|
1817
1968
|
const routeMap = [
|
|
1818
1969
|
{
|
|
1819
|
-
keywords: [
|
|
1970
|
+
keywords: [
|
|
1971
|
+
'ecs',
|
|
1972
|
+
'server',
|
|
1973
|
+
'vm',
|
|
1974
|
+
'instance',
|
|
1975
|
+
'compute',
|
|
1976
|
+
'flavor',
|
|
1977
|
+
'image',
|
|
1978
|
+
'弹性云服务器',
|
|
1979
|
+
'云服务器',
|
|
1980
|
+
'服务器',
|
|
1981
|
+
'虚拟机',
|
|
1982
|
+
'镜像',
|
|
1983
|
+
],
|
|
1820
1984
|
skills: ['huawei-ecs'],
|
|
1821
1985
|
services: ['ECS'],
|
|
1822
1986
|
},
|
|
1823
1987
|
{
|
|
1824
|
-
keywords: [
|
|
1988
|
+
keywords: [
|
|
1989
|
+
'vpc',
|
|
1990
|
+
'subnet',
|
|
1991
|
+
'network',
|
|
1992
|
+
'security group',
|
|
1993
|
+
'eip',
|
|
1994
|
+
'nat',
|
|
1995
|
+
'vpn',
|
|
1996
|
+
'bandwidth',
|
|
1997
|
+
'虚拟私有云',
|
|
1998
|
+
'子网',
|
|
1999
|
+
'网络',
|
|
2000
|
+
'安全组',
|
|
2001
|
+
'弹性公网IP',
|
|
2002
|
+
'NAT网关',
|
|
2003
|
+
'VPN网关',
|
|
2004
|
+
'带宽',
|
|
2005
|
+
],
|
|
1825
2006
|
skills: ['huawei-vpc'],
|
|
1826
2007
|
services: ['VPC', 'EIP'],
|
|
1827
2008
|
},
|
|
1828
2009
|
{
|
|
1829
|
-
keywords: [
|
|
2010
|
+
keywords: [
|
|
2011
|
+
'obs',
|
|
2012
|
+
'bucket',
|
|
2013
|
+
'storage',
|
|
2014
|
+
'object',
|
|
2015
|
+
'static website',
|
|
2016
|
+
'static site',
|
|
2017
|
+
'hosting',
|
|
2018
|
+
'对象存储',
|
|
2019
|
+
'存储桶',
|
|
2020
|
+
'桶',
|
|
2021
|
+
'存储',
|
|
2022
|
+
'对象',
|
|
2023
|
+
'静态网站',
|
|
2024
|
+
'静态托管',
|
|
2025
|
+
],
|
|
1830
2026
|
skills: ['huawei-obs'],
|
|
1831
2027
|
services: ['OBS'],
|
|
1832
2028
|
},
|
|
1833
2029
|
{
|
|
1834
|
-
keywords: [
|
|
2030
|
+
keywords: [
|
|
2031
|
+
'functiongraph',
|
|
2032
|
+
'serverless',
|
|
2033
|
+
'function',
|
|
2034
|
+
'lambda',
|
|
2035
|
+
'trigger',
|
|
2036
|
+
'faas',
|
|
2037
|
+
'函数工作流',
|
|
2038
|
+
'函数',
|
|
2039
|
+
'触发器',
|
|
2040
|
+
'无服务器',
|
|
2041
|
+
'函数计算',
|
|
2042
|
+
],
|
|
1835
2043
|
skills: ['huawei-functiongraph'],
|
|
1836
2044
|
services: ['FunctionGraph'],
|
|
1837
2045
|
},
|
|
@@ -1840,20 +2048,60 @@ function serviceCatalog(intent = '') {
|
|
|
1840
2048
|
skills: ['huawei-cce'],
|
|
1841
2049
|
services: ['CCE', 'SWR'],
|
|
1842
2050
|
},
|
|
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
2051
|
{
|
|
1846
|
-
keywords: ['
|
|
2052
|
+
keywords: ['apig', 'api gateway', 'publish', 'throttle', 'API网关', '限流'],
|
|
2053
|
+
skills: ['huawei-apig'],
|
|
2054
|
+
services: ['APIG'],
|
|
2055
|
+
},
|
|
2056
|
+
{
|
|
2057
|
+
keywords: ['rds', 'mysql', 'postgresql', 'database', 'db', '关系型数据库', '数据库'],
|
|
2058
|
+
skills: ['huawei-rds'],
|
|
2059
|
+
services: ['RDS'],
|
|
2060
|
+
},
|
|
2061
|
+
{
|
|
2062
|
+
keywords: ['gaussdb', 'distributed', 'sharding', 'opengauss', '高斯数据库', '分布式数据库'],
|
|
1847
2063
|
skills: ['huawei-gaussdb'],
|
|
1848
2064
|
services: ['GaussDB'],
|
|
1849
2065
|
},
|
|
1850
2066
|
{
|
|
1851
|
-
keywords: [
|
|
2067
|
+
keywords: [
|
|
2068
|
+
'iam',
|
|
2069
|
+
'permission',
|
|
2070
|
+
'policy',
|
|
2071
|
+
'role',
|
|
2072
|
+
'user',
|
|
2073
|
+
'ak/sk',
|
|
2074
|
+
'access key',
|
|
2075
|
+
'agency',
|
|
2076
|
+
'身份认证',
|
|
2077
|
+
'身份管理',
|
|
2078
|
+
'权限',
|
|
2079
|
+
'策略',
|
|
2080
|
+
'角色',
|
|
2081
|
+
'用户',
|
|
2082
|
+
'访问密钥',
|
|
2083
|
+
'委托',
|
|
2084
|
+
],
|
|
1852
2085
|
skills: ['huawei-iam'],
|
|
1853
2086
|
services: ['IAM'],
|
|
1854
2087
|
},
|
|
1855
2088
|
{
|
|
1856
|
-
keywords: [
|
|
2089
|
+
keywords: [
|
|
2090
|
+
'dew',
|
|
2091
|
+
'secret',
|
|
2092
|
+
'kms',
|
|
2093
|
+
'encrypt',
|
|
2094
|
+
'decrypt',
|
|
2095
|
+
'certificate',
|
|
2096
|
+
'csms',
|
|
2097
|
+
'数据加密服务',
|
|
2098
|
+
'密钥管理',
|
|
2099
|
+
'密钥',
|
|
2100
|
+
'加密',
|
|
2101
|
+
'解密',
|
|
2102
|
+
'证书',
|
|
2103
|
+
'凭据管理',
|
|
2104
|
+
],
|
|
1857
2105
|
skills: ['huawei-dew'],
|
|
1858
2106
|
services: ['CSMS', 'KMS'],
|
|
1859
2107
|
},
|
|
@@ -1863,7 +2111,7 @@ function serviceCatalog(intent = '') {
|
|
|
1863
2111
|
services: ['ModelArts'],
|
|
1864
2112
|
},
|
|
1865
2113
|
{
|
|
1866
|
-
keywords: ['billing', 'cost', 'bill', 'budget', 'expense', 'bss'],
|
|
2114
|
+
keywords: ['billing', 'cost', 'bill', 'budget', 'expense', 'bss', '计费', '费用', '账单', '预算', '开销'],
|
|
1867
2115
|
skills: ['huawei-billing'],
|
|
1868
2116
|
services: ['BSS'],
|
|
1869
2117
|
},
|
|
@@ -1878,12 +2126,16 @@ function serviceCatalog(intent = '') {
|
|
|
1878
2126
|
services: ['SMN', 'DMS'],
|
|
1879
2127
|
},
|
|
1880
2128
|
{
|
|
1881
|
-
keywords: ['ces', 'monitor', 'alarm', 'metric', 'dashboard', 'cloud eye'],
|
|
2129
|
+
keywords: ['ces', 'monitor', 'alarm', 'metric', 'dashboard', 'cloud eye', '监控', '告警', '云监控'],
|
|
1882
2130
|
skills: ['huawei-cloud-eye'],
|
|
1883
2131
|
services: ['CES'],
|
|
1884
2132
|
},
|
|
1885
|
-
{ keywords: ['cts', 'audit', 'trace', 'tracker'], skills: ['huawei-cts'], services: ['CTS'] },
|
|
1886
|
-
{
|
|
2133
|
+
{ keywords: ['cts', 'audit', 'trace', 'tracker', '审计', '追踪'], skills: ['huawei-cts'], services: ['CTS'] },
|
|
2134
|
+
{
|
|
2135
|
+
keywords: ['cbr', 'backup', 'restore', 'vault', 'snapshot', '备份', '恢复', '快照', '存储库'],
|
|
2136
|
+
skills: ['huawei-cbr'],
|
|
2137
|
+
services: ['CBR'],
|
|
2138
|
+
},
|
|
1887
2139
|
{
|
|
1888
2140
|
keywords: ['deployment', 'deploy', 'ci/cd', 'pipeline', 'release'],
|
|
1889
2141
|
skills: ['huawei-deployment'],
|
|
@@ -1909,10 +2161,26 @@ function serviceCatalog(intent = '') {
|
|
|
1909
2161
|
services: ['Sandbox', 'DevStation'],
|
|
1910
2162
|
},
|
|
1911
2163
|
{
|
|
1912
|
-
keywords: [
|
|
2164
|
+
keywords: [
|
|
2165
|
+
'dds',
|
|
2166
|
+
'dcs',
|
|
2167
|
+
'mongodb',
|
|
2168
|
+
'redis',
|
|
2169
|
+
'memcached',
|
|
2170
|
+
'cache',
|
|
2171
|
+
'document db',
|
|
2172
|
+
'文档数据库',
|
|
2173
|
+
'缓存',
|
|
2174
|
+
'内存数据库',
|
|
2175
|
+
],
|
|
1913
2176
|
skills: ['huawei-dds-dcs'],
|
|
1914
2177
|
services: ['DDS', 'DCS'],
|
|
1915
2178
|
},
|
|
2179
|
+
{
|
|
2180
|
+
keywords: ['elb', 'load balancer', '负载均衡', '弹性负载均衡', '监听器', '证书'],
|
|
2181
|
+
skills: ['huawei-vpc'],
|
|
2182
|
+
services: ['ELB'],
|
|
2183
|
+
},
|
|
1916
2184
|
{
|
|
1917
2185
|
keywords: ['voucher', 'coupon', 'incentive', 'credit', '领券', '代金券', '优惠券', '激励金', '领取'],
|
|
1918
2186
|
skills: ['huawei-voucher'],
|
|
@@ -1923,7 +2191,9 @@ function serviceCatalog(intent = '') {
|
|
|
1923
2191
|
const tokens = new Set(it.split(/[\s,./-]+/).filter((t) => t.length > 0));
|
|
1924
2192
|
const cjk = /[\u4e00-\u9fff]/;
|
|
1925
2193
|
for (const route of routeMap) {
|
|
1926
|
-
if (
|
|
2194
|
+
if (
|
|
2195
|
+
route.keywords.some((kw) => (kw.includes(' ') || cjk.test(kw) ? it.includes(kw.toLowerCase()) : tokens.has(kw)))
|
|
2196
|
+
) {
|
|
1927
2197
|
matched.push(route);
|
|
1928
2198
|
}
|
|
1929
2199
|
}
|
|
@@ -2016,6 +2286,23 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
|
|
|
2016
2286
|
}
|
|
2017
2287
|
}
|
|
2018
2288
|
|
|
2289
|
+
// G: when the active credential set is a temporary STS token that has already
|
|
2290
|
+
// expired, surface that as the primary cause before generic auth guidance.
|
|
2291
|
+
let activeCreds;
|
|
2292
|
+
try {
|
|
2293
|
+
activeCreds = resolveCredentialsWithRuntime({ allowMissing: true });
|
|
2294
|
+
} catch {
|
|
2295
|
+
activeCreds = null;
|
|
2296
|
+
}
|
|
2297
|
+
if (activeCreds?.ak && activeCreds?.sk && activeCreds.securityToken) {
|
|
2298
|
+
const expiry = parseStsExpiry({ securityToken: activeCreds.securityToken });
|
|
2299
|
+
if (expiry !== null && expiry <= Date.now()) {
|
|
2300
|
+
suggestions.push(
|
|
2301
|
+
'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).',
|
|
2302
|
+
);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2019
2306
|
if (/auth|token|credential|ak|sk|401|403|unauthorized|forbidden|Incorrect IAM/i.test(combined)) {
|
|
2020
2307
|
if (svc === 'obs') {
|
|
2021
2308
|
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
|