huaweicloud-devkit 1.1.4-next.4 → 1.1.4-next.5
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/package.json +1 -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/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +2 -2
- package/plugins/huaweicloud-core/skills/huawei-obs/references/static-website.md +3 -7
- package/plugins/huaweicloud-core/src/tools.mjs +148 -4
- package/plugins/huaweicloud-core/src/update-check.mjs +7 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"mcpName": "io.github.huaweicloud/huaweicloud-devkit",
|
|
4
|
-
"version": "1.1.4-next.
|
|
4
|
+
"version": "1.1.4-next.5",
|
|
5
5
|
"kooCliVersion": "7.2.12",
|
|
6
6
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
7
7
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"mcpServers": "./.mcp.json",
|
|
18
18
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"version": "1.1.4-next.
|
|
20
|
+
"version": "1.1.4-next.5",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"mcpServers": "./.mcp.json",
|
|
21
21
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
22
22
|
"skills": "./skills/",
|
|
23
|
-
"version": "1.1.4-next.
|
|
23
|
+
"version": "1.1.4-next.5",
|
|
24
24
|
"author": {
|
|
25
25
|
"name": "HuaweiCloud Mate",
|
|
26
26
|
"url": "https://github.com/huaweicloud"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"mcpServers": "./.mcp.json",
|
|
18
18
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"version": "1.1.4-next.
|
|
20
|
+
"version": "1.1.4-next.5",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.4-next.
|
|
3
|
+
"version": "1.1.4-next.5",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.4-next.
|
|
3
|
+
"version": "1.1.4-next.5",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"id": "huaweicloud-devkit",
|
|
4
4
|
"displayName": "HuaweiCloud DevKit",
|
|
5
|
-
"version": "1.1.4-next.
|
|
5
|
+
"version": "1.1.4-next.5",
|
|
6
6
|
"family": "bundle-plugin",
|
|
7
7
|
"bundleFormat": "codex",
|
|
8
8
|
"description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
|
|
@@ -41,7 +41,7 @@ Domain expertise for Huawei Cloud Object Storage Service (OBS). Covers bucket/ob
|
|
|
41
41
|
| Three-layer permissions | IAM > Bucket Policy > ACL. Most restrictive wins |
|
|
42
42
|
| Versioning is irreversible | Once enabled, cannot be disabled, only suspended |
|
|
43
43
|
| OBS uses AK/SK directly | NOT IAM tokens. Auth errors mean check AK/SK validity |
|
|
44
|
-
| Static website via CLI missing | KooCLI OBS lacks website config. Use
|
|
44
|
+
| Static website via CLI missing | KooCLI OBS lacks website config. Use `huaweicloud_obs_set_website_config` MCP tool (handles AWS4 signing internally) or console |
|
|
45
45
|
| **OBS needs separate cred config** | `hcloud configure` is NOT enough for OBS. Before any OBS operation, call `huaweicloud_setup_obs_config` to sync credentials from hcloud profile. |
|
|
46
46
|
| **obsutil interactive prompts** | `cp`/`rm` without `-f` causes "Please input (y/n)" → Agent hangs (TIMEOUT). Always use `-f` for non-interactive. |
|
|
47
47
|
| **Directory upload adds prefix** | `cp <dir>/ obs://<bucket>/ -r` puts files under `bucket/<dir>/...`. Use `-flat` for root-level files (static sites). Preview with `-dryRun` first. |
|
|
@@ -83,7 +83,7 @@ hcloud OBS config -e=<endpoint> -i=<AK> -k=<SK> -t=token
|
|
|
83
83
|
See `references/static-website.md` for the full end-to-end workflow:
|
|
84
84
|
Build → Create bucket → Upload → Set bucket ACL → Set object ACL → Configure website (REST API/console)
|
|
85
85
|
|
|
86
|
-
> KooCLI OBS does NOT support `SetBucketWebsite`.
|
|
86
|
+
> KooCLI OBS does NOT support `SetBucketWebsite`. Use the `huaweicloud_obs_set_website_config` MCP tool (handles AWS4 signing internally, no manual signature needed) or the Huawei Cloud console.
|
|
87
87
|
|
|
88
88
|
## Single-File Quick Share
|
|
89
89
|
|
|
@@ -31,13 +31,9 @@ hcloud OBS chattri obs://my-static-site -acl=public-read
|
|
|
31
31
|
hcloud OBS chattri obs://my-static-site/ -r -f -acl=public-read
|
|
32
32
|
|
|
33
33
|
# 5. Configure static website hosting
|
|
34
|
-
# KooCLI OBS does NOT support this. Use
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# Host: my-static-site.obs.cn-north-4.myhuaweicloud.com
|
|
38
|
-
# Body: {"IndexDocument": {"Suffix": "index.html"}, "ErrorDocument": {"Key": "error.html"}}
|
|
39
|
-
# (requires AK/SK signature)
|
|
40
|
-
# Option B — Huawei Cloud Console:
|
|
34
|
+
# KooCLI OBS does NOT support this. Use the MCP tool (handles AWS4 signing internally):
|
|
35
|
+
# action=set, bucket=<bucket>, region=<region>, indexDocument=index.html, errorDocument=404.html
|
|
36
|
+
# Or use Huawei Cloud Console:
|
|
41
37
|
# Console → OBS → Bucket → Basic Settings → Static Website Hosting
|
|
42
38
|
|
|
43
39
|
# 6. Verify
|
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { homedir, tmpdir } from 'node:os';
|
|
5
5
|
import { spawnSync, execFile } from 'node:child_process';
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
|
+
import { createHmac, createHash } from 'node:crypto';
|
|
7
8
|
|
|
8
9
|
import { evaluateArtifacts, evaluateCommandRisk, evaluateDeployPlan } from './risk-rule-engine.mjs';
|
|
9
10
|
import { classifyTextCommand, redactSecrets } from './safety-policy.mjs';
|
|
@@ -48,6 +49,7 @@ import {
|
|
|
48
49
|
resolveCredentialsWithRuntime,
|
|
49
50
|
} from './auth/credentials.mjs';
|
|
50
51
|
import { trackToolInvoke, trackSkillRetrieve, clearUserHash } from './telemetry/telemetry.mjs';
|
|
52
|
+
import { fetchWithProxy } from './proxy/proxy-agent.mjs';
|
|
51
53
|
import { fingerprint, runHcloudConfigure, resolveManagedProfile } from './auth/reconcile.mjs';
|
|
52
54
|
import {
|
|
53
55
|
getCachedUpdateInfo,
|
|
@@ -846,6 +848,32 @@ export const TOOL_DEFINITIONS = [
|
|
|
846
848
|
},
|
|
847
849
|
},
|
|
848
850
|
},
|
|
851
|
+
{
|
|
852
|
+
name: 'huaweicloud_obs_set_website_config',
|
|
853
|
+
description:
|
|
854
|
+
'配置 OBS 桶的静态网站托管。KooCLI OBS 不支持 SetBucketWebsite API,此工具内部实现 AWS4 签名调用 OBS REST API,屏蔽签名细节。支持 set(配置)、get(查询)、delete(删除)三种操作。操作前需确保桶已创建且已设置 public-read ACL。',
|
|
855
|
+
inputSchema: {
|
|
856
|
+
type: 'object',
|
|
857
|
+
properties: {
|
|
858
|
+
action: {
|
|
859
|
+
type: 'string',
|
|
860
|
+
enum: ['set', 'get', 'delete'],
|
|
861
|
+
description: '操作类型:set=配置静态网站托管,get=查询当前配置,delete=删除配置',
|
|
862
|
+
},
|
|
863
|
+
bucket: { type: 'string', description: 'OBS 桶名称' },
|
|
864
|
+
region: { type: 'string', description: 'OBS 桶所在区域,如 cn-north-4' },
|
|
865
|
+
indexDocument: {
|
|
866
|
+
type: 'string',
|
|
867
|
+
description: '首页文件名(action=set 时必填),如 index.html',
|
|
868
|
+
},
|
|
869
|
+
errorDocument: {
|
|
870
|
+
type: 'string',
|
|
871
|
+
description: '错误页面文件名(action=set 时可选),如 404.html 或 error.html',
|
|
872
|
+
},
|
|
873
|
+
},
|
|
874
|
+
required: ['action', 'bucket', 'region'],
|
|
875
|
+
},
|
|
876
|
+
},
|
|
849
877
|
];
|
|
850
878
|
|
|
851
879
|
function toolInvokeValue(name, args) {
|
|
@@ -1448,19 +1476,26 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1448
1476
|
case 'huaweicloud_voucher_claim':
|
|
1449
1477
|
return await hdkitVoucherClaim(args.domain_id);
|
|
1450
1478
|
case 'huaweicloud_check_update':
|
|
1451
|
-
return await handleCheckUpdate(args, { sessionId: opts?.sessionId });
|
|
1479
|
+
return await handleCheckUpdate(args, { sessionId: opts?.sessionId, doQuery: opts?.doQuery });
|
|
1452
1480
|
case 'huaweicloud_upgrade':
|
|
1453
1481
|
return await handleUpgrade(args, { sessionId: opts?.sessionId });
|
|
1482
|
+
case 'huaweicloud_obs_set_website_config':
|
|
1483
|
+
return await handleObsWebsiteConfig(args);
|
|
1454
1484
|
default:
|
|
1455
1485
|
throw new Error(`Unknown tool: ${name}`);
|
|
1456
1486
|
}
|
|
1457
1487
|
}
|
|
1458
1488
|
|
|
1459
1489
|
async function handleCheckUpdate(args = {}, opts = {}) {
|
|
1460
|
-
const sessionId =
|
|
1490
|
+
const { sessionId = null, doQuery } = opts;
|
|
1461
1491
|
const current = readInstalledVersion() || '0.0.0';
|
|
1462
1492
|
if (args.dismiss === true) {
|
|
1463
|
-
const distTags = await getUpdateDistTags(current, { sessionId });
|
|
1493
|
+
const distTags = await getUpdateDistTags(current, { sessionId, doQuery });
|
|
1494
|
+
if (!distTags) {
|
|
1495
|
+
// 查询失败:不降级为 current 冷却、不写 skip,返回 check_failed。
|
|
1496
|
+
// 不调 invalidateUpdateCache(),以保留 failedAt 的 5 分钟失败节流。
|
|
1497
|
+
return judgeUpdate(current, null, null);
|
|
1498
|
+
}
|
|
1464
1499
|
const target = determineTarget(current, distTags);
|
|
1465
1500
|
const dismissedVersion =
|
|
1466
1501
|
typeof args.dismissVersion === 'string' && args.dismissVersion ? args.dismissVersion : target || current;
|
|
@@ -1468,7 +1503,7 @@ async function handleCheckUpdate(args = {}, opts = {}) {
|
|
|
1468
1503
|
invalidateUpdateCache();
|
|
1469
1504
|
return judgeUpdate(current, distTags, state);
|
|
1470
1505
|
}
|
|
1471
|
-
return getCachedUpdateInfo(current, { sessionId });
|
|
1506
|
+
return getCachedUpdateInfo(current, { sessionId, doQuery });
|
|
1472
1507
|
}
|
|
1473
1508
|
|
|
1474
1509
|
async function handleUpgrade(args = {}, opts = {}) {
|
|
@@ -2255,3 +2290,112 @@ async function getRegionalAvailability(service, region) {
|
|
|
2255
2290
|
export function classifyRawCommand(command) {
|
|
2256
2291
|
return classifyTextCommand(command);
|
|
2257
2292
|
}
|
|
2293
|
+
|
|
2294
|
+
// ── OBS Static Website Hosting (AWS4 signed REST API) ──
|
|
2295
|
+
|
|
2296
|
+
async function handleObsWebsiteConfig(args) {
|
|
2297
|
+
const { action, bucket, region, indexDocument, errorDocument } = args;
|
|
2298
|
+
if (!bucket || !region) {
|
|
2299
|
+
throw new Error('bucket and region are required');
|
|
2300
|
+
}
|
|
2301
|
+
const creds = resolveCredentialsWithRuntime({});
|
|
2302
|
+
if (!creds?.ak || !creds?.sk) {
|
|
2303
|
+
throw new Error('OBS website config requires AK/SK credentials. Run huaweicloud_auth_init first.');
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
const host = `${bucket}.obs.${region}.myhuaweicloud.com`;
|
|
2307
|
+
const endpoint = `https://${host}`;
|
|
2308
|
+
|
|
2309
|
+
if (action === 'get') {
|
|
2310
|
+
const res = await obsSignedRequest('GET', endpoint, '/?website', '', creds, region);
|
|
2311
|
+
return { ok: res.status === 200, status: res.status, body: res.body };
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
if (action === 'delete') {
|
|
2315
|
+
const res = await obsSignedRequest('DELETE', endpoint, '/?website', '', creds, region);
|
|
2316
|
+
return { ok: res.status === 204, status: res.status };
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
if (action === 'set') {
|
|
2320
|
+
if (!indexDocument) {
|
|
2321
|
+
throw new Error('indexDocument is required for action=set');
|
|
2322
|
+
}
|
|
2323
|
+
const xmlParts = ['<WebsiteConfiguration>', ` <IndexDocument><Suffix>${indexDocument}</Suffix></IndexDocument>`];
|
|
2324
|
+
if (errorDocument) {
|
|
2325
|
+
xmlParts.push(` <ErrorDocument><Key>${errorDocument}</Key></ErrorDocument>`);
|
|
2326
|
+
}
|
|
2327
|
+
xmlParts.push('</WebsiteConfiguration>');
|
|
2328
|
+
const body = xmlParts.join('\n');
|
|
2329
|
+
const res = await obsSignedRequest('PUT', endpoint, '/?website', body, creds, region);
|
|
2330
|
+
const websiteUrl = `http://${bucket}.obs-website.${region}.myhuaweicloud.com`;
|
|
2331
|
+
return {
|
|
2332
|
+
ok: res.status === 200,
|
|
2333
|
+
status: res.status,
|
|
2334
|
+
websiteUrl,
|
|
2335
|
+
message:
|
|
2336
|
+
res.status === 200
|
|
2337
|
+
? `Static website hosting configured. Website URL: ${websiteUrl} (may take ~1 min to propagate)`
|
|
2338
|
+
: `Failed to configure website: HTTP ${res.status}`,
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
throw new Error(`Unknown action: ${action}. Use set, get, or delete.`);
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
async function obsSignedRequest(method, endpoint, pathAndQuery, body, creds, region) {
|
|
2346
|
+
const now = new Date();
|
|
2347
|
+
const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
2348
|
+
const amzDate = dateStamp + 'T' + now.toISOString().slice(11, 19).replace(/:/g, '') + 'Z';
|
|
2349
|
+
const payloadHash = createHash('sha256').update(body).digest('hex');
|
|
2350
|
+
|
|
2351
|
+
const url = new URL(endpoint + pathAndQuery);
|
|
2352
|
+
const canonicalUri = '/';
|
|
2353
|
+
const canonicalQueryString = 'website=';
|
|
2354
|
+
const canonicalHeaders = `host:${url.host}\n` + `x-amz-content-sha256:${payloadHash}\n` + `x-amz-date:${amzDate}\n`;
|
|
2355
|
+
const signedHeaders = 'host;x-amz-content-sha256;x-amz-date';
|
|
2356
|
+
const canonicalRequest = [
|
|
2357
|
+
method,
|
|
2358
|
+
canonicalUri,
|
|
2359
|
+
canonicalQueryString,
|
|
2360
|
+
canonicalHeaders,
|
|
2361
|
+
signedHeaders,
|
|
2362
|
+
payloadHash,
|
|
2363
|
+
].join('\n');
|
|
2364
|
+
|
|
2365
|
+
const credentialScope = `${dateStamp}/${region}/s3/aws4_request`;
|
|
2366
|
+
const stringToSign = [
|
|
2367
|
+
'AWS4-HMAC-SHA256',
|
|
2368
|
+
amzDate,
|
|
2369
|
+
credentialScope,
|
|
2370
|
+
createHash('sha256').update(canonicalRequest).digest('hex'),
|
|
2371
|
+
].join('\n');
|
|
2372
|
+
|
|
2373
|
+
const kDate = createHmac('sha256', 'AWS4' + creds.sk)
|
|
2374
|
+
.update(dateStamp)
|
|
2375
|
+
.digest();
|
|
2376
|
+
const kRegion = createHmac('sha256', kDate).update(region).digest();
|
|
2377
|
+
const kService = createHmac('sha256', kRegion).update('s3').digest();
|
|
2378
|
+
const kSigning = createHmac('sha256', kService).update('aws4_request').digest();
|
|
2379
|
+
const signature = createHmac('sha256', kSigning).update(stringToSign).digest('hex');
|
|
2380
|
+
|
|
2381
|
+
const authorization =
|
|
2382
|
+
`AWS4-HMAC-SHA256 Credential=${creds.ak}/${credentialScope}, ` +
|
|
2383
|
+
`SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
2384
|
+
|
|
2385
|
+
const headers = {
|
|
2386
|
+
Host: url.host,
|
|
2387
|
+
'x-amz-content-sha256': payloadHash,
|
|
2388
|
+
'x-amz-date': amzDate,
|
|
2389
|
+
Authorization: authorization,
|
|
2390
|
+
};
|
|
2391
|
+
if (body) headers['Content-Type'] = 'application/xml';
|
|
2392
|
+
if (creds.securityToken) headers['x-amz-security-token'] = creds.securityToken;
|
|
2393
|
+
|
|
2394
|
+
const res = await fetchWithProxy(endpoint + pathAndQuery, {
|
|
2395
|
+
method,
|
|
2396
|
+
headers,
|
|
2397
|
+
body: body || undefined,
|
|
2398
|
+
});
|
|
2399
|
+
const resBody = await res.text();
|
|
2400
|
+
return { status: res.status, body: resBody };
|
|
2401
|
+
}
|
|
@@ -114,6 +114,10 @@ export function judgeUpdate(current, distTags, skipState, now = Date.now()) {
|
|
|
114
114
|
if (!distTags) {
|
|
115
115
|
return buildResult(current, null, { result: 'check_failed', note: '检测失败,不影响使用' });
|
|
116
116
|
}
|
|
117
|
+
// truthy 但无有效版本(如 failure 折叠后的 { latest:null, next:null })同样视为检测失败
|
|
118
|
+
if (!distTags.latest && !distTags.next) {
|
|
119
|
+
return buildResult(current, null, { result: 'check_failed', note: '检测失败,不影响使用' });
|
|
120
|
+
}
|
|
117
121
|
const target = determineTarget(current, distTags);
|
|
118
122
|
if (!target || semverCompare(target, current) <= 0) {
|
|
119
123
|
return buildResult(current, distTags, { result: 'up_to_date', target: target ?? null });
|
|
@@ -348,9 +352,9 @@ export function peekCachedUpdateInfo() {
|
|
|
348
352
|
return lastHint && lastHint.updateAvailable && lastHint.targetVersion ? lastHint : null;
|
|
349
353
|
}
|
|
350
354
|
|
|
351
|
-
export async function getUpdateDistTags(current, { sessionId = null } = {}) {
|
|
352
|
-
const result = await getCachedUpdateInfo(current, { sessionId });
|
|
353
|
-
if (!result) return null;
|
|
355
|
+
export async function getUpdateDistTags(current, { sessionId = null, doQuery } = {}) {
|
|
356
|
+
const result = await getCachedUpdateInfo(current, { sessionId, doQuery });
|
|
357
|
+
if (!result || result.result === 'check_failed') return null;
|
|
354
358
|
return { latest: result.latestStable ?? null, next: result.latestNext ?? null };
|
|
355
359
|
}
|
|
356
360
|
|