huaweicloud-devkit 1.1.0 → 1.1.1-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.zh-CN.md +1 -0
- 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-ecs/SKILL.md +8 -7
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +1 -1
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +2 -1
- package/plugins/huaweicloud-core/src/tools.mjs +86 -4
package/README.zh-CN.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://github.com/huaweicloud/huaweicloud-devkit/discussions)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/huaweicloud/huaweicloud-devkit)
|
|
6
7
|
|
|
7
8
|
**中文 | [English](README.md)**
|
|
8
9
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.0",
|
|
3
|
+
"version": "1.1.1-next.0",
|
|
4
4
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -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.0",
|
|
20
|
+
"version": "1.1.1-next.0",
|
|
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.0",
|
|
23
|
+
"version": "1.1.1-next.0",
|
|
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.0",
|
|
20
|
+
"version": "1.1.1-next.0",
|
|
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.0",
|
|
3
|
+
"version": "1.1.1-next.0",
|
|
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.0",
|
|
3
|
+
"version": "1.1.1-next.0",
|
|
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.0",
|
|
5
|
+
"version": "1.1.1-next.0",
|
|
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.",
|
|
@@ -28,13 +28,14 @@ If these do not exist, load the `huawei-vpc` skill first and create them before
|
|
|
28
28
|
|
|
29
29
|
## Critical Warnings
|
|
30
30
|
|
|
31
|
-
| Trap
|
|
32
|
-
|
|
|
33
|
-
| Flavor not in region
|
|
34
|
-
| Security group denies all
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
31
|
+
| Trap | Why |
|
|
32
|
+
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
33
|
+
| Flavor not in region | Not all flavors available everywhere. Check with ECS ListFlavors first |
|
|
34
|
+
| Security group denies all | New SGs deny ALL inbound. Must explicitly add rules |
|
|
35
|
+
| Reusing existing security group | Existing SGs may have 0.0.0.0/0 rules. Before referencing, run hcloud VPC ListSecurityGroupRules --security_group_id=<id> to audit inbound rules |
|
|
36
|
+
| EIP bills when idle | Unattached EIP still incurs charges |
|
|
37
|
+
| Stopped instance still bills | Pay-per-use instances bill when stopped (unless shutdown-no-billing flavor) |
|
|
38
|
+
| Disk survives instance delete | Deleting instance does NOT delete system disk by default |
|
|
38
39
|
|
|
39
40
|
## Flavor Selection Guide
|
|
40
41
|
|
|
@@ -95,7 +95,7 @@ Setup is a **plugin-side preflight** — the developer should be asked a questio
|
|
|
95
95
|
2. **Real-name verification only** (`HDKIT_NOT_REALNAME`): tell the developer once, "Huawei Cloud requires real-name verification before using the sandbox — please complete it in the Huawei Cloud console (实名认证)." and stop — do not retry `connect` in a loop
|
|
96
96
|
3. **Sign agreement only** (`HDKIT_NOT_AGREEMENT`): **STOP and do NOT sign on your own.** Ask the developer: "Huawei Cloud sandbox requires signing the latest developer service agreement. May I sign it for you?" Then **wait for the developer to explicitly agree** (e.g. "签署" / "确认" / "sign it"). Only after explicit consent call `huaweicloud_sandbox_sign_agreement` and return its result (`signed`/`signedCount`) to the developer. **Never sign a legal agreement on the developer's behalf without their explicit, unambiguous consent.** Do not expose the underlying sandbox/DevBridge service as a separate entity the developer must understand or sign up for
|
|
97
97
|
4. **Both missing** (`HDKIT_NOT_REALNAME_AND_AGREEMENT`): present **both** requirements together in one message — the real-name verification steps (console, step 2) **and** the agreement-signing request (step 3, wait for explicit consent) — so the developer can complete both at once
|
|
98
|
-
5. **Connect**: `huaweicloud_sandbox_connect` — returns `session_id`, `dev_stage_id`, `connection_id`, `connection_address`. The `source` parameter identifies the calling agent (valid values: `CLI`, `WEB`, `VSCODE`, `WEBVNC`, `WEBPTY`, `WEBIDE`, `CURSOR`, etc. — case-sensitive, all uppercase).
|
|
98
|
+
5. **Connect**: `huaweicloud_sandbox_connect` — returns `session_id`, `dev_stage_id`, `connection_id`, `connection_address`. The `source` parameter identifies the calling agent (valid values: `CLI`, `WEB`, `VSCODE`, `WEBVNC`, `WEBPTY`, `WEBIDE`, `CURSOR`, etc. — case-sensitive, all uppercase). When the `git` parameter is provided (with `repo_url`, `target_path`, optional `repo_branch`), code is automatically transferred to the sandbox: local `git clone` followed by `uploadProject` (falls back to direct sandbox clone when local git is unavailable). Check `_repoStatus` in the response: `uploaded_from_local`, `cloned_in_sandbox`, or `already_exists` (reconnect).
|
|
99
99
|
6. **Cleanup previous deployments** (after first connect to a sandbox): nginx configs, DevBridge tunnels, and stale web processes from previous deployments can cause port conflicts and quota errors. Run cleanup immediately after connect:
|
|
100
100
|
|
|
101
101
|
```bash
|
|
@@ -827,7 +827,8 @@ fi`;
|
|
|
827
827
|
`chmod -R o+rX "$REAL_PROJECT" 2>/dev/null || true`,
|
|
828
828
|
`find "$REAL_PROJECT" -type d -exec chmod o+x {} \\; 2>/dev/null || true`,
|
|
829
829
|
`find "$REAL_PROJECT" -type f -path "*/node_modules/.bin/*" -exec chmod +x {} \\; 2>/dev/null || true`,
|
|
830
|
-
`if pgrep -x nginx > /dev/null 2>&1; then sudo
|
|
830
|
+
`if pgrep -x nginx > /dev/null 2>&1; then sudo killall -9 nginx 2>/dev/null; sleep 1; fi
|
|
831
|
+
sudo nginx`,
|
|
831
832
|
].join('\n');
|
|
832
833
|
|
|
833
834
|
const result = await execOneShot(workspaceId, cmd, username, timeoutMs);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, existsSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { join, dirname } from 'node:path';
|
|
1
|
+
import { readFileSync, readdirSync, existsSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join, dirname, basename } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { homedir } from 'node:os';
|
|
5
|
-
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { homedir, tmpdir } from 'node:os';
|
|
5
|
+
import { spawnSync, execFile } from 'node:child_process';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
6
7
|
|
|
7
8
|
import { evaluateArtifacts, evaluateCommandRisk, evaluateDeployPlan } from './risk-rule-engine.mjs';
|
|
8
9
|
import { classifyTextCommand, redactSecrets } from './safety-policy.mjs';
|
|
@@ -759,6 +760,85 @@ export const TOOL_DEFINITIONS = [
|
|
|
759
760
|
},
|
|
760
761
|
];
|
|
761
762
|
|
|
763
|
+
const execFilePromise = promisify(execFile);
|
|
764
|
+
|
|
765
|
+
async function isGitAvailable() {
|
|
766
|
+
try {
|
|
767
|
+
await execFilePromise('git', ['--version'], { timeout: 5000 });
|
|
768
|
+
return true;
|
|
769
|
+
} catch {
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
async function gitCloneToLocal(repoUrl, branch, targetBasename) {
|
|
775
|
+
const tempRoot = join(tmpdir(), `hw-sandbox-git-${Date.now()}`);
|
|
776
|
+
const cloneDir = join(tempRoot, targetBasename);
|
|
777
|
+
mkdirSync(tempRoot, { recursive: true });
|
|
778
|
+
const args = ['clone', '--depth', '1'];
|
|
779
|
+
if (branch) args.push('-b', branch);
|
|
780
|
+
args.push(repoUrl, cloneDir);
|
|
781
|
+
await execFilePromise('git', args, { timeout: 120000 });
|
|
782
|
+
return { cloneDir, tempRoot };
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
async function transferGitRepo(args, devStageId, connectResult) {
|
|
786
|
+
const git = args.git;
|
|
787
|
+
if (!git?.repo_url || !git?.target_path) return;
|
|
788
|
+
|
|
789
|
+
const { repo_url, repo_branch, target_path } = git;
|
|
790
|
+
|
|
791
|
+
try {
|
|
792
|
+
const existsCheck = await execOneShot(
|
|
793
|
+
devStageId,
|
|
794
|
+
`test -d "${target_path}/.git" && echo "EXISTS" || echo "NOT_EXISTS"`,
|
|
795
|
+
'root',
|
|
796
|
+
10000,
|
|
797
|
+
);
|
|
798
|
+
if (String(existsCheck.stdout || '').includes('EXISTS')) {
|
|
799
|
+
connectResult._repoStatus = 'already_exists';
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
} catch {
|
|
803
|
+
/* 检查失败继续 */
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const hasGit = await isGitAvailable();
|
|
807
|
+
if (hasGit) {
|
|
808
|
+
let tempRoot = null;
|
|
809
|
+
try {
|
|
810
|
+
const targetName = basename(target_path);
|
|
811
|
+
const { cloneDir, tempRoot: root } = await gitCloneToLocal(repo_url, repo_branch, targetName);
|
|
812
|
+
tempRoot = root;
|
|
813
|
+
await uploadProjectWithSession(devStageId, cloneDir, dirname(target_path), 'root', 300000, {
|
|
814
|
+
extract: true,
|
|
815
|
+
exclude: ['.git', 'node_modules'],
|
|
816
|
+
});
|
|
817
|
+
connectResult._repoStatus = 'uploaded_from_local';
|
|
818
|
+
return;
|
|
819
|
+
} catch {
|
|
820
|
+
/* 本地方式失败,进入兜底 */
|
|
821
|
+
} finally {
|
|
822
|
+
if (tempRoot) {
|
|
823
|
+
try {
|
|
824
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
825
|
+
} catch {
|
|
826
|
+
/* 清理失败忽略 */
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const branchFlag = repo_branch ? `-b ${repo_branch}` : '';
|
|
833
|
+
await execOneShot(
|
|
834
|
+
devStageId,
|
|
835
|
+
`mkdir -p $(dirname "${target_path}") && git clone --depth 1 ${branchFlag} "${repo_url}" "${target_path}"`,
|
|
836
|
+
'root',
|
|
837
|
+
120000,
|
|
838
|
+
);
|
|
839
|
+
connectResult._repoStatus = 'cloned_in_sandbox';
|
|
840
|
+
}
|
|
841
|
+
|
|
762
842
|
export async function callTool(name, args = {}) {
|
|
763
843
|
switch (name) {
|
|
764
844
|
case 'huaweicloud_check_cli':
|
|
@@ -974,6 +1054,7 @@ export async function callTool(name, args = {}) {
|
|
|
974
1054
|
try {
|
|
975
1055
|
await execOneShot(devStageId, 'devbridge delete-all 2>/dev/null || true', 'root', 15000);
|
|
976
1056
|
} catch {}
|
|
1057
|
+
await transferGitRepo(args, devStageId, connectResult);
|
|
977
1058
|
}
|
|
978
1059
|
return connectResult;
|
|
979
1060
|
}
|
|
@@ -999,6 +1080,7 @@ export async function callTool(name, args = {}) {
|
|
|
999
1080
|
'root',
|
|
1000
1081
|
15000,
|
|
1001
1082
|
);
|
|
1083
|
+
await execWithSession(sandboxWsIdCred, `source ${credsFile} && echo "CREDS_SOURCED"`, 'root', 15000);
|
|
1002
1084
|
} catch {}
|
|
1003
1085
|
}
|
|
1004
1086
|
return credResult;
|