huaweicloud-devkit 1.0.2-dev.4 → 1.0.2-dev.6
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/skills/huawei-sandbox/SKILL.md +18 -8
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +23 -2
- package/plugins/huaweicloud-core/src/mcp-server.mjs +11 -0
- package/plugins/huaweicloud-core/src/setup-cli.mjs +29 -9
- package/plugins/huaweicloud-core/src/tools.mjs +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.0.2-dev.
|
|
3
|
+
"version": "1.0.2-dev.6",
|
|
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": [
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"mcpServers": "./.mcp.json",
|
|
21
21
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
22
22
|
"skills": "./skills/",
|
|
23
|
-
"version": "1.0.2-dev.
|
|
23
|
+
"version": "1.0.2-dev.6",
|
|
24
24
|
"author": {
|
|
25
25
|
"name": "HuaweiCloud Mate",
|
|
26
26
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-core",
|
|
3
|
-
"version": "1.0.2-dev.
|
|
3
|
+
"version": "1.0.2-dev.6",
|
|
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",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"mcpServers": "./.mcp.json",
|
|
21
21
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
22
22
|
"skills": "./skills/",
|
|
23
|
-
"version": "1.0.2-dev.
|
|
23
|
+
"version": "1.0.2-dev.6",
|
|
24
24
|
"author": {
|
|
25
25
|
"name": "HuaweiCloud Mate",
|
|
26
26
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: huawei-sandbox
|
|
3
|
-
description: "Use when creating, connecting, or managing Huawei Cloud Sandbox instances and workspace terminals. Covers sandbox lifecycle (check-user, sign-agreement, connect, release), terminal execution
|
|
3
|
+
description: "Use when creating, connecting, or managing Huawei Cloud Sandbox instances and workspace terminals, or when a task needs a temporary runtime to deploy, run, or preview a web application. Covers sandbox lifecycle (check-user, sign-agreement, connect, release), session-based terminal execution, and credential injection. Triggers on: sandbox, workspace, terminal, web app deployment, deploy web app, preview app, hwlink, devstation, hdkitservice, remote exec. NOT for: ECS instances (use huawei-ecs), CCE clusters (use huawei-cce)."
|
|
4
4
|
version: 1
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -12,6 +12,12 @@ version: 1
|
|
|
12
12
|
|
|
13
13
|
Domain expertise for Huawei Cloud Sandbox (DevStation) instances and workspace terminal execution. Covers sandbox lifecycle via hdkitservice API and remote terminal command execution via hwlink protocol.
|
|
14
14
|
|
|
15
|
+
## Activation
|
|
16
|
+
|
|
17
|
+
- **Proactive offering**: when the developer's task needs a temporary runtime (e.g. "deploy this web app", "run this app and preview it"), offer the sandbox proactively — the developer never has to say "use sandbox". Prompt: "This task can be satisfied by a sandbox — use it?"
|
|
18
|
+
- **Do not intercept a specified target**: if the task already names a deployment target (ECS, CCE, an existing server), follow that target instead of offering the sandbox. Offer the sandbox only when the task needs a temporary runtime or no target is specified.
|
|
19
|
+
- The developer never needs to name or understand the sandbox as a separate service. Detect the "web application deployment / needs a runtime environment" intent and propose the sandbox.
|
|
20
|
+
|
|
15
21
|
## MCP Tools
|
|
16
22
|
|
|
17
23
|
### User Verification (Prerequisites)
|
|
@@ -38,18 +44,22 @@ Domain expertise for Huawei Cloud Sandbox (DevStation) instances and workspace t
|
|
|
38
44
|
|
|
39
45
|
## Workflow
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
Setup is a **plugin-side preflight** — the developer should be asked a question only once, when the agreement actually needs signing:
|
|
48
|
+
|
|
49
|
+
1. **Check user** (transparent): `huaweicloud_sandbox_check_user` — verify `realname_verified` and `agreement_signed`
|
|
50
|
+
2. **Real-name verification** (only if `realname_verified=false`): tell the developer once, "Huawei Cloud requires real-name verification before using the sandbox." and stop — do not retry `connect` in a loop
|
|
51
|
+
3. **Sign agreement** (only if `agreement_signed=false`): ask the developer once as the plugin — "Huawei Cloud sandbox requires signing its service agreement. May I sign it on your behalf?" — then call `huaweicloud_sandbox_sign_agreement`. Do not expose the underlying sandbox/DevBridge service as a separate entity the developer must understand or sign up for
|
|
52
|
+
4. **Connect**: `huaweicloud_sandbox_connect` — returns `session_id`, `dev_stage_id`, `connection_id`, `connection_address`
|
|
53
|
+
5. **Inject credentials** (optional): `huaweicloud_sandbox_credentials` — enables cloud API access from sandbox
|
|
54
|
+
6. **Execute commands**: `huaweicloud_sandbox_exec_with_session` for interactive work
|
|
55
|
+
7. **Release**: `huaweicloud_sandbox_release` — cleans up sandbox and session
|
|
47
56
|
|
|
48
57
|
## Critical Warnings
|
|
49
58
|
|
|
50
59
|
| Trap | Why |
|
|
51
60
|
|------|-----|
|
|
52
|
-
| Agreement required first | `sandbox_connect` fails if
|
|
61
|
+
| Agreement required first | `sandbox_connect` fails if the agreement isn't signed; the `sandbox_check_user` preflight detects this, so surface it to the developer only when signing is needed |
|
|
62
|
+
| Real-name required | `sandbox_connect` fails if `realname_verified=false`; tell the developer once and stop, don't loop on connect |
|
|
53
63
|
| Session state persists | `exec_with_session` preserves `cd`, env vars, aliases between calls |
|
|
54
64
|
| Destructive commands blocked | `rm -rf /`, `mkfs`, `dd if=`, fork bombs are denied by safety policy |
|
|
55
65
|
| Workspace ID = dev_stage_id | Use `dev_stage_id` from `sandbox_connect` as `workspace_id` for terminal exec |
|
|
@@ -1,7 +1,24 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
|
|
5
|
+
// Verify a credential file ended up with 0600. On Windows-mounted drives inside WSL
|
|
6
|
+
// (drvfs/9p) chmod is silently ignored, so the file can be world-readable (0777).
|
|
7
|
+
// Native Windows has no POSIX modes (statSync always reports 0666), so skip the check there.
|
|
8
|
+
function ensurePrivateMode(path) {
|
|
9
|
+
if (process.platform === 'win32') return;
|
|
10
|
+
try { chmodSync(path, 0o600); } catch {}
|
|
11
|
+
try {
|
|
12
|
+
const mode = statSync(path).mode & 0o777;
|
|
13
|
+
if (mode !== 0o600) {
|
|
14
|
+
console.warn(`\x1b[33m[WARN]\x1b[0m Could not set 0600 on ${path} (current mode ${mode.toString(8)}). Credentials may be readable by other users.`);
|
|
15
|
+
console.warn(`\x1b[33m If running under WSL, move the credential home to the Linux filesystem:\x1b[0m`);
|
|
16
|
+
console.warn(`\x1b[33m export HUAWEICLOUD_HOME=$HOME (then re-run auth init)\x1b[0m`);
|
|
17
|
+
console.warn(`\x1b[33m Or skip file storage entirely with HW_ACCESS_KEY/HW_SECRET_KEY environment variables.\x1b[0m`);
|
|
18
|
+
}
|
|
19
|
+
} catch {}
|
|
20
|
+
}
|
|
21
|
+
|
|
5
22
|
function baseHome() {
|
|
6
23
|
return process.env.HUAWEICLOUD_HOME || homedir();
|
|
7
24
|
}
|
|
@@ -34,6 +51,7 @@ export function writeGlobalCredentials(credentials = {}) {
|
|
|
34
51
|
region: String(credentials.region || ''),
|
|
35
52
|
};
|
|
36
53
|
writeFileSync(path, JSON.stringify(payload, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
54
|
+
ensurePrivateMode(path);
|
|
37
55
|
return path;
|
|
38
56
|
}
|
|
39
57
|
|
|
@@ -41,13 +59,16 @@ export function writeObsConfig(credentials = {}) {
|
|
|
41
59
|
const region = String(credentials.region || '');
|
|
42
60
|
const ak = String(credentials.ak || '');
|
|
43
61
|
const sk = String(credentials.sk || '');
|
|
62
|
+
const securityToken = String(credentials.securityToken || '');
|
|
44
63
|
if (!region || !ak || !sk) {
|
|
45
64
|
throw new Error('region, ak, and sk are required to write OBS config');
|
|
46
65
|
}
|
|
47
66
|
const path = obsConfigPath();
|
|
48
67
|
const endpoint = credentials.endpoint || `https://obs.${region}.myhuaweicloud.com`;
|
|
49
|
-
|
|
68
|
+
// Flat key=value format (no [default] section) as written by KooCLI 7.x `hcloud OBS config`.
|
|
69
|
+
const content = `endpoint=${endpoint}\nak=${ak}\nsk=${sk}${securityToken ? `\ntoken=${securityToken}` : ''}\n`;
|
|
50
70
|
writeFileSync(path, content, { encoding: 'utf8', mode: 0o600 });
|
|
71
|
+
ensurePrivateMode(path);
|
|
51
72
|
return { path, endpoint };
|
|
52
73
|
}
|
|
53
74
|
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { stdin, stdout } from 'node:process';
|
|
3
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
4
|
+
import { resolve, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
3
6
|
import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
|
|
4
7
|
|
|
8
|
+
// The MCP server is now loaded by a live agent session. Clear the install marker
|
|
9
|
+
// in this plugin dir so `doctor` no longer reports "restart needed".
|
|
10
|
+
try {
|
|
11
|
+
const pluginDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
12
|
+
const marker = resolve(pluginDir, '.installed');
|
|
13
|
+
if (existsSync(marker)) rmSync(marker, { force: true });
|
|
14
|
+
} catch {}
|
|
15
|
+
|
|
5
16
|
let buffer = Buffer.alloc(0);
|
|
6
17
|
let useContentLengthFraming = true;
|
|
7
18
|
|
|
@@ -908,6 +908,19 @@ async function cmdUninstall() {
|
|
|
908
908
|
}
|
|
909
909
|
}
|
|
910
910
|
}
|
|
911
|
+
if (target === 'all') {
|
|
912
|
+
const vaultPath = globalCredentialsPath();
|
|
913
|
+
if (removeIfExists(vaultPath)) {
|
|
914
|
+
console.log(' Removed credential vault');
|
|
915
|
+
}
|
|
916
|
+
const vaultDir = dirname(vaultPath);
|
|
917
|
+
try {
|
|
918
|
+
if (existsSync(vaultDir) && readdirSync(vaultDir).length === 0) {
|
|
919
|
+
rmSync(vaultDir, { recursive: true, force: true });
|
|
920
|
+
console.log(` Removed empty directory: ${vaultDir}`);
|
|
921
|
+
}
|
|
922
|
+
} catch {}
|
|
923
|
+
}
|
|
911
924
|
console.log(`\n\x1b[32mUninstall complete.\x1b[0m`);
|
|
912
925
|
}
|
|
913
926
|
|
|
@@ -1325,7 +1338,9 @@ function readLineQuestion(prompt) {
|
|
|
1325
1338
|
|
|
1326
1339
|
async function readSecret(prompt) {
|
|
1327
1340
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1328
|
-
|
|
1341
|
+
throw new Error(
|
|
1342
|
+
`Cannot read "${prompt.trim()}" securely in a non-interactive session. Set HW_ACCESS_KEY/HW_SECRET_KEY environment variables instead, or run "npx huaweicloud-devkit auth init" in a real terminal.`
|
|
1343
|
+
);
|
|
1329
1344
|
}
|
|
1330
1345
|
|
|
1331
1346
|
process.stdout.write(prompt);
|
|
@@ -1408,21 +1423,26 @@ async function cmdAuthInit() {
|
|
|
1408
1423
|
let securityToken = process.env.HW_SECURITY_TOKEN || '';
|
|
1409
1424
|
let region = process.env.HW_REGION || process.env.HUAWEICLOUD_REGION || '';
|
|
1410
1425
|
|
|
1411
|
-
|
|
1426
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
1427
|
+
if (!interactive && (!ak || !sk)) {
|
|
1428
|
+
console.error('\x1b[31mNon-interactive session detected. Provide credentials via environment variables instead:\x1b[0m');
|
|
1429
|
+
console.error(' HW_ACCESS_KEY, HW_SECRET_KEY');
|
|
1430
|
+
console.error(' (Or run "npx huaweicloud-devkit auth init" in a real terminal.)');
|
|
1431
|
+
process.exitCode = 1;
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
if (!ak) ak = await readLineQuestion('Access Key ID (AK): ');
|
|
1412
1436
|
if (!sk) sk = await readSecret('Secret Access Key (SK): ');
|
|
1413
|
-
if (!securityToken) securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
|
|
1414
|
-
if (!region) region =
|
|
1437
|
+
if (interactive && !securityToken) securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
|
|
1438
|
+
if (!region) region = 'cn-north-4';
|
|
1415
1439
|
|
|
1416
1440
|
if (!ak || !sk) {
|
|
1417
1441
|
console.error('\nAK and SK are required.');
|
|
1418
1442
|
process.exitCode = 1;
|
|
1419
1443
|
return;
|
|
1420
1444
|
}
|
|
1421
|
-
|
|
1422
|
-
console.error('\nRegion is required to generate the OBS endpoint.');
|
|
1423
|
-
process.exitCode = 1;
|
|
1424
|
-
return;
|
|
1425
|
-
}
|
|
1445
|
+
|
|
1426
1446
|
|
|
1427
1447
|
const vaultPath = writeGlobalCredentials({ ak, sk, securityToken, region });
|
|
1428
1448
|
console.log(`\nCredentials stored: ${vaultPath}`);
|
|
@@ -614,7 +614,8 @@ async function setupObsConfigFromHcloud(profile) {
|
|
|
614
614
|
}
|
|
615
615
|
|
|
616
616
|
const endpoint = `https://obs.${region}.myhuaweicloud.com`;
|
|
617
|
-
|
|
617
|
+
// Flat key=value format (no [default] section) as written by KooCLI 7.x `hcloud OBS config`.
|
|
618
|
+
const configContent = `endpoint=${endpoint}\nak=${accessKeyId}\nsk=${secretAccessKey}\n`;
|
|
618
619
|
|
|
619
620
|
try {
|
|
620
621
|
writeFileSync(obsConfigPath, configContent, { encoding: 'utf8', mode: 0o600 });
|