huaweicloud-devkit 1.0.2-next.8 → 1.0.2
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/.agents/plugins/marketplace.json +1 -1
- package/LICENSE +201 -201
- package/README.md +71 -52
- package/README.zh-CN.md +71 -50
- package/bin/setup.cjs +0 -0
- package/package.json +9 -5
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +2 -2
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +2 -2
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +2 -2
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
- package/plugins/huaweicloud-core/.mcp.json +1 -2
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +2 -3
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +159 -136
- package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +67 -20
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +11 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +16 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +27 -24
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +12 -0
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +79 -11
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +65 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +31 -4
- package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +6 -1
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +7 -2
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +14 -4
- package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +432 -10
- package/plugins/huaweicloud-core/src/setup-cli.mjs +581 -87
- package/plugins/huaweicloud-core/src/tools.mjs +225 -33
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +18 -4
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +9 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +9 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +9 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +9 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +267 -0
- package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +9 -5
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "huaweicloud-devkit",
|
|
3
|
+
"id": "huaweicloud-devkit",
|
|
4
|
+
"displayName": "HuaweiCloud DevKit",
|
|
5
|
+
"version": "1.0.2",
|
|
6
|
+
"family": "bundle-plugin",
|
|
7
|
+
"bundleFormat": "codex",
|
|
8
|
+
"description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
|
|
9
|
+
"author": {
|
|
10
|
+
"name": "HuaweiCloud Mate",
|
|
11
|
+
"url": "https://github.com/huaweicloud"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/huaweicloud/huaweicloud-devkit",
|
|
14
|
+
"repository": "https://github.com/huaweicloud/huaweicloud-devkit",
|
|
15
|
+
"license": "Apache-2.0",
|
|
16
|
+
"keywords": ["huaweicloud", "huawei-cloud", "koocli", "hcloud", "agent", "mcp", "api", "sdk", "skills", "cloud"],
|
|
17
|
+
"category": "Cloud",
|
|
18
|
+
"capabilities": ["Read", "Interactive"],
|
|
19
|
+
"brandColor": "#C7000B"
|
|
20
|
+
}
|
|
@@ -37,7 +37,7 @@ This skill enables users to:
|
|
|
37
37
|
|
|
38
38
|
## Prerequisites
|
|
39
39
|
|
|
40
|
-
- **Node.js >=
|
|
40
|
+
- **Node.js >= 22** must be installed
|
|
41
41
|
- **Network access** to `gitcode.com` (API v5 for index) and `github.com` / `raw.githubusercontent.com` (for skill details)
|
|
42
42
|
|
|
43
43
|
### Step 0: Check Python Environment
|
|
@@ -40,11 +40,31 @@ Domain expertise for Huawei Cloud Sandbox (DevStation) instances and workspace t
|
|
|
40
40
|
|
|
41
41
|
### Terminal Execution
|
|
42
42
|
|
|
43
|
-
| Tool | Purpose
|
|
44
|
-
| --------------------------------------- |
|
|
45
|
-
| `huaweicloud_sandbox_exec_with_session` | Session-based execution (state persists)
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
43
|
+
| Tool | Purpose |
|
|
44
|
+
| --------------------------------------- | --------------------------------------------------------------------------- |
|
|
45
|
+
| `huaweicloud_sandbox_exec_with_session` | Session-based execution (state persists; best for interactive work) |
|
|
46
|
+
| `huaweicloud_sandbox_exec_one_shot` | One-shot execution (fresh connection; best for long/heavy commands) |
|
|
47
|
+
| `huaweicloud_sandbox_upload_file` | Upload a local file into the sandbox (chunked base64 write + md5 verify) |
|
|
48
|
+
| `huaweicloud_sandbox_upload_project` | Upload a local project directory to sandbox (HTTP tunnel, tar.gz + extract) |
|
|
49
|
+
| `huaweicloud_sandbox_close_session` | Close a persistent terminal session |
|
|
50
|
+
|
|
51
|
+
### Tool Selection Guide
|
|
52
|
+
|
|
53
|
+
| Scenario | Use | Why |
|
|
54
|
+
| ---------------------------------- | ---------------------------------- | ------------------------------------------------------------ |
|
|
55
|
+
| `cd`, env setup, command chains | `exec_with_session` | Needs shared shell state across calls |
|
|
56
|
+
| `npm install`, `apt-get`, builds | `exec_one_shot` | Long-running (>30s), no state needed, more stable |
|
|
57
|
+
| `curl`, health checks, quick tests | Either — `exec_one_shot` preferred | Stateless, fast |
|
|
58
|
+
| Server startup (background) | `exec_with_session` | Need to `nohup ... &` then check output in same session |
|
|
59
|
+
| Deployment scripts | `exec_one_shot+shot` | Long script, fresh connection avoids session timeouts |
|
|
60
|
+
| Single file upload (<1MB) | `upload_file` | Base64 chunked, reliable for small files |
|
|
61
|
+
| Project directory upload (>1MB) | `upload_project` | HTTP tunnel, much faster than base64 for multi-file projects |
|
|
62
|
+
|
|
63
|
+
**Timeout tuning**: default is 120s. For commands expected to run longer (e.g. large builds), pass `timeout_ms` explicitly:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{ "timeout_ms": 300000 }
|
|
67
|
+
```
|
|
48
68
|
|
|
49
69
|
## Workflow
|
|
50
70
|
|
|
@@ -59,20 +79,40 @@ Setup is a **plugin-side preflight** — the developer should be asked a questio
|
|
|
59
79
|
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
|
|
60
80
|
5. **Connect**: `huaweicloud_sandbox_connect` — returns `session_id`, `dev_stage_id`, `connection_id`, `connection_address`
|
|
61
81
|
6. **Inject credentials** (optional): `huaweicloud_sandbox_credentials` — enables cloud API access from sandbox
|
|
62
|
-
7. **Execute commands**: `huaweicloud_sandbox_exec_with_session` for interactive work
|
|
63
|
-
8. **
|
|
82
|
+
7. **Execute commands**: use `huaweicloud_sandbox_exec_with_session` for interactive work (cd, env setup), or `huaweicloud_sandbox_exec_one_shot` for long-running builds/installs. See [Tool Selection Guide](#tool-selection-guide).
|
|
83
|
+
8. **Upload project** (if deploying local code): `huaweicloud_sandbox_upload_project` to transfer a local project directory into `/workspace/<name>` — uses HTTP tunnel, much faster than base64 for multi-file projects. Use `huaweicloud_sandbox_upload_file` for single files.
|
|
84
|
+
9. **Expose the deployed app and return the public URL**: set up a public access link for the app port and give the URL to the developer as the final result (see "Expose the deployed app (public URL)") — this step uses an internal tunnel mechanism that must never be mentioned to the developer
|
|
64
85
|
|
|
65
86
|
## File Transfer (local → sandbox)
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
### upload_project (preferred for directories)
|
|
89
|
+
|
|
90
|
+
`huaweicloud_sandbox_upload_project` packages a local directory as tar.gz, uploads it to the sandbox via HTTP tunnel, and extracts it. This is the fastest way to transfer multi-file projects.
|
|
91
|
+
|
|
92
|
+
**Prerequisites**: sandbox must already be connected (`sandbox_connect` called first). The tool handles everything internally — no manual setup needed.
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"local_dir": "/path/to/local/project",
|
|
97
|
+
"remote_dir": "/workspace",
|
|
98
|
+
"extract": true,
|
|
99
|
+
"exclude": ["node_modules", ".git", "__pycache__"]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- `local_dir` (required): local project directory
|
|
104
|
+
- `remote_dir` (optional, default `/workspace`): parent directory on sandbox
|
|
105
|
+
- `extract` (optional, default `true`): extract tar.gz after upload
|
|
106
|
+
- `exclude` (optional): patterns to exclude from archive
|
|
107
|
+
- Result includes `md5` and `md5Verified` for integrity check
|
|
68
108
|
|
|
69
|
-
###
|
|
109
|
+
### upload_file (for single files)
|
|
70
110
|
|
|
71
|
-
`huaweicloud_sandbox_upload_file` (local_path → remote_path) uploads a local file by base64-encoding it and writing it in small chunks through the terminal session, then decoding and verifying the md5 checksum. Use it for
|
|
111
|
+
`huaweicloud_sandbox_upload_file` (local_path → remote_path) uploads a local file by base64-encoding it and writing it in small chunks through the terminal session, then decoding and verifying the md5 checksum. Use it for individual files, especially small ones (<1MB).
|
|
72
112
|
|
|
73
113
|
### Exec channel size limit
|
|
74
114
|
|
|
75
|
-
`huaweicloud_sandbox_exec_with_session`
|
|
115
|
+
`huaweicloud_sandbox_exec_with_session` and `huaweicloud_sandbox_exec_one_shot` are fragile for long commands and large inline content. Do NOT paste a file's base64 directly into a single command:
|
|
76
116
|
|
|
77
117
|
| Payload | Result |
|
|
78
118
|
| --------------------------- | -------------------------------------- |
|
|
@@ -104,7 +144,11 @@ Expose a deployed web app to a public URL and return that URL to the developer.
|
|
|
104
144
|
**Install** (internal tooling; skip if `devbridge version` already works):
|
|
105
145
|
|
|
106
146
|
```bash
|
|
107
|
-
|
|
147
|
+
if command -v devbridge >/dev/null 2>&1; then
|
|
148
|
+
echo "devbridge already installed"
|
|
149
|
+
else
|
|
150
|
+
curl -fsSL https://res-hd.hc-cdn.cn/sharedata/hdspace/devbridge/install.sh | bash
|
|
151
|
+
fi
|
|
108
152
|
export PATH=$PATH:$HOME/.huawei/bin # installer only writes ~/.bashrc; session shells do not re-source it
|
|
109
153
|
```
|
|
110
154
|
|
|
@@ -143,9 +187,12 @@ sleep 10 && cat /tmp/host.log
|
|
|
143
187
|
| Never install tunnel tooling locally | If the sandbox cannot install it, report a generic error and stop — installing on the developer's machine defeats sandbox deployment |
|
|
144
188
|
| Return the deployment URL | Always hand the public URL from the host log to the developer as the final result |
|
|
145
189
|
| Session state persists | `exec_with_session` preserves `cd`, env vars, aliases between calls |
|
|
190
|
+
| Long commands prefer one-shot | `exec_one_shot` creates a fresh connection per call — more stable for builds, installs, and scripts >30s. See [Tool Selection Guide](#tool-selection-guide). |
|
|
146
191
|
| Destructive commands blocked | `rm -rf /`, `mkfs`, `dd if=`, fork bombs are denied by safety policy |
|
|
147
192
|
| Workspace ID = dev_stage_id | Use `dev_stage_id` from `sandbox_connect` as `workspace_id` for terminal exec |
|
|
148
193
|
| Projects live in `/workspace` | Clone/install project code under `/workspace/<repo-name>` (filesystem-root workspace mount, not `$HOME/workspace`), never in `/tmp` — ephemeral locations lose the project when the sandbox session restarts |
|
|
194
|
+
| Upload project for local code | Use `sandbox_upload_project` to transfer local projects — packages as tar.gz, uploads via HTTP tunnel, extracts on sandbox. Much faster than base64 for multi-file projects |
|
|
195
|
+
| Upload file for single files | Use `sandbox_upload_file` for individual files — base64 chunked, reliable for small files (<1MB) |
|
|
149
196
|
| Node.js >= 22 required | Sandbox terminal uses built-in WebSocket (globalThis.WebSocket); if Node.js is missing, install it from the Huawei Cloud mirror (see "Node.js in the sandbox") |
|
|
150
197
|
|
|
151
198
|
## Node.js in the sandbox
|
|
@@ -163,11 +210,11 @@ node --version
|
|
|
163
210
|
|
|
164
211
|
## Environment Variables
|
|
165
212
|
|
|
166
|
-
| Variable | Required | Description
|
|
167
|
-
| ----------------------- | -------- |
|
|
168
|
-
| `HW_ACCESS_KEY` | Yes | Huawei Cloud AK
|
|
169
|
-
| `HW_SECRET_KEY` | Yes | Huawei Cloud SK
|
|
170
|
-
| `HW_SECURITY_TOKEN` | No | STS security token
|
|
171
|
-
| `HW_WORKSPACE_ID` | No | Default workspace ID
|
|
172
|
-
| `HDKITSERVICE_ENDPOINT` | No | hdkitservice API endpoint |
|
|
173
|
-
| `HWLINK_ENDPOINT` | No | DevStation API endpoint
|
|
213
|
+
| Variable | Required | Description |
|
|
214
|
+
| ----------------------- | -------- | --------------------------------------------------------------- |
|
|
215
|
+
| `HW_ACCESS_KEY` | Yes | Huawei Cloud AK |
|
|
216
|
+
| `HW_SECRET_KEY` | Yes | Huawei Cloud SK |
|
|
217
|
+
| `HW_SECURITY_TOKEN` | No | STS security token |
|
|
218
|
+
| `HW_WORKSPACE_ID` | No | Default workspace ID |
|
|
219
|
+
| `HDKITSERVICE_ENDPOINT` | No | hdkitservice API endpoint (default: devkit.huaweicloud.com) |
|
|
220
|
+
| `HWLINK_ENDPOINT` | No | DevStation API endpoint (default: devstation.myhuaweicloud.com) |
|
|
@@ -22,6 +22,17 @@ Use this skill to turn vague developer intent into a precise Huawei Cloud capabi
|
|
|
22
22
|
9. When no built-in devkit skill matches, browse the community skill marketplace at https://github.com/huaweicloud/huaweicloud-skills. Fetch the index from https://raw.githubusercontent.com/huaweicloud/huaweicloud-skills/master/skills-index/index.json.
|
|
23
23
|
10. When the deliverable is a PPT, architecture diagram (draw.io), or frontend page that needs official Huawei Cloud service logos, use the `huaweicloud_get_service_icon` MCP tool to get logo URLs from the official Icons library instead of guessing or hotlinking unofficial images.
|
|
24
24
|
|
|
25
|
+
## Scenario Routing
|
|
26
|
+
|
|
27
|
+
Match the scenario before picking a service. For "deploy a web app" requests, layer the recommendation instead of defaulting to a production service:
|
|
28
|
+
|
|
29
|
+
| Scenario | Recommended path |
|
|
30
|
+
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
31
|
+
| hello world / prototype / demo / temporary preview (free, quick try) | `huawei-sandbox` first — temporary runtime + public URL, ~8h validity, zero billed resources |
|
|
32
|
+
| production / long-term / custom domain / high availability | `huawei-functiongraph` / `huawei-ecs` (or `huawei-cce` for containers) |
|
|
33
|
+
|
|
34
|
+
Route to the sandbox when the developer signals free/quick preview intent ("免费", "快速", "预览", "hello world", "原型", "演示"); route to production services only when production-grade hosting is explicitly required.
|
|
35
|
+
|
|
25
36
|
## Official Service Logos
|
|
26
37
|
|
|
27
38
|
- Use `huaweicloud_get_service_icon` with the service name, alias, or Chinese name (e.g. `ecs`, `obs`, `modelarts`, `对象存储`). It returns the official CDN logo URL (`logo.source_url`), category, and product page link from https://open.huaweicloud.com/openplatform/icons.html.
|
|
@@ -121,8 +121,24 @@ hcloud <Service> <ListOp> --cli-output=json --cli-query "items[?status=='ACTIVE'
|
|
|
121
121
|
hcloud <Service> <Op> --cli-debug=true
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
## Credential Resolution Priority
|
|
125
|
+
|
|
126
|
+
Credentials are resolved in this order (highest priority first):
|
|
127
|
+
|
|
128
|
+
| Priority | Source | Mechanism | Persistence |
|
|
129
|
+
| -------- | ----------------------- | ---------------------------------------------------------- | ------------------------------- |
|
|
130
|
+
| 1 | Runtime credentials | `huaweicloud_auth_init` tool | Memory (cleared on MCP restart) |
|
|
131
|
+
| 2 | Environment variables | `HW_ACCESS_KEY` / `HW_SECRET_KEY` | MCP process lifetime |
|
|
132
|
+
| 3 | CodeArts project config | `.codeartsdoer/mcp/mcp_settings.json` (project, then user) | File |
|
|
133
|
+
| 4 | Global config file | `~/.config/huaweicloud/credentials.json` | Permanent |
|
|
134
|
+
| 5 | KooCLI profile | `~/.hcloud/config.json` (KooCLI only) | Permanent |
|
|
135
|
+
|
|
136
|
+
When switching accounts within the same Agent session, use `huaweicloud_auth_init` to set runtime credentials. This overrides all other sources for the current MCP process.
|
|
137
|
+
|
|
124
138
|
## Preferred Toolkit Tools
|
|
125
139
|
|
|
140
|
+
- `huaweicloud_auth_init`
|
|
141
|
+
- `huaweicloud_auth_status`
|
|
126
142
|
- `huaweicloud_check_cli`
|
|
127
143
|
- `huaweicloud_show_profile_redacted`
|
|
128
144
|
- `huaweicloud_plan_cli_command`
|
|
@@ -46,30 +46,32 @@ Do not rely on training data for facts. Huawei Cloud services, pricing, quotas,
|
|
|
46
46
|
|
|
47
47
|
## Service Map
|
|
48
48
|
|
|
49
|
-
| Workload
|
|
50
|
-
|
|
|
51
|
-
| Web application hosting
|
|
52
|
-
| Containerized microservices
|
|
53
|
-
| Static file storage / CDN (long-term)
|
|
54
|
-
| Relational database (MySQL/PG)
|
|
55
|
-
| Distributed SQL database
|
|
56
|
-
| Document database (MongoDB API)
|
|
57
|
-
| In-memory cache
|
|
58
|
-
| Serverless functions
|
|
59
|
-
| API management
|
|
60
|
-
| AI model training/inference
|
|
61
|
-
| Message queuing
|
|
62
|
-
| Push notifications
|
|
63
|
-
| Secret management
|
|
64
|
-
| Key management
|
|
65
|
-
| DDoS protection
|
|
66
|
-
| Web firewall
|
|
67
|
-
| Monitoring dashboards
|
|
68
|
-
| Audit trails
|
|
69
|
-
| Backup / disaster recovery
|
|
70
|
-
| CI/CD pipeline
|
|
71
|
-
| Temporary runtime / web app preview
|
|
72
|
-
| Getting started
|
|
49
|
+
| Workload | Primary Service | Skill |
|
|
50
|
+
| ------------------------------------------------ | -------------------- | ------------------------ |
|
|
51
|
+
| Web application hosting (production / long-term) | ECS | huawei-ecs |
|
|
52
|
+
| Containerized microservices | CCE | huawei-cce |
|
|
53
|
+
| Static file storage / CDN (long-term) | OBS | huawei-obs |
|
|
54
|
+
| Relational database (MySQL/PG) | RDS | huawei-rds |
|
|
55
|
+
| Distributed SQL database | GaussDB | huawei-gaussdb |
|
|
56
|
+
| Document database (MongoDB API) | DDS | huawei-dds-dcs |
|
|
57
|
+
| In-memory cache | DCS (Redis) | huawei-dds-dcs |
|
|
58
|
+
| Serverless functions | FunctionGraph | huawei-functiongraph |
|
|
59
|
+
| API management | APIG | huawei-apig |
|
|
60
|
+
| AI model training/inference | ModelArts | huawei-modelarts |
|
|
61
|
+
| Message queuing | DMS (Kafka/RabbitMQ) | huawei-smn-dms |
|
|
62
|
+
| Push notifications | SMN | huawei-smn-dms |
|
|
63
|
+
| Secret management | DEW (CSMS) | huawei-dew |
|
|
64
|
+
| Key management | DEW (KMS) | huawei-dew |
|
|
65
|
+
| DDoS protection | AAD | huawei-waf-aad |
|
|
66
|
+
| Web firewall | WAF | huawei-waf-aad |
|
|
67
|
+
| Monitoring dashboards | Cloud Eye (CES) | huawei-cloud-eye |
|
|
68
|
+
| Audit trails | CTS | huawei-cts |
|
|
69
|
+
| Backup / disaster recovery | CBR | huawei-cbr |
|
|
70
|
+
| CI/CD pipeline | CloudDeploy | huawei-deployment |
|
|
71
|
+
| Temporary runtime / web app preview | Sandbox (DevStation) | huawei-sandbox |
|
|
72
|
+
| Getting started | Account setup | huaweicloud-cli-and-auth |
|
|
73
|
+
|
|
74
|
+
**Web app scenario layering**: for "deploy a web app", prefer `huawei-sandbox` for free/quick try (hello world, prototype, demo, temporary preview — temporary runtime + public URL, ~8h validity, zero billed resources); route to `huawei-functiongraph` / `huawei-ecs` (or `huawei-cce`) for production / long-term / custom-domain / high-availability hosting.
|
|
73
75
|
|
|
74
76
|
## Deployment Target Options
|
|
75
77
|
|
|
@@ -100,6 +102,7 @@ When the intent is to **deploy, host, or preview a web app or static website** (
|
|
|
100
102
|
|
|
101
103
|
- Use huaweicloud_search_docs to discover skills covering a topic
|
|
102
104
|
- Use huaweicloud_retrieve_skill to load a full SKILL.md after routing
|
|
105
|
+
- Use huaweicloud_auth_init to switch credentials at runtime when different accounts co-exist in the same session
|
|
103
106
|
- If no built-in skill matches, consult huaweicloud-capability-discovery for external marketplace options
|
|
104
107
|
- Use huaweicloud_list_regions before creating regional resources
|
|
105
108
|
- Use huaweicloud_get_regional_availability when unsure about service-region pairs
|
|
@@ -28,3 +28,15 @@ Route based on answers:
|
|
|
28
28
|
- SQL + massive scale / distributed -> huawei-gaussdb
|
|
29
29
|
- Document model (MongoDB-compatible) -> huawei-dds-dcs
|
|
30
30
|
- Cache / key-value -> huawei-dds-dcs (DCS)
|
|
31
|
+
|
|
32
|
+
## Example: I need to deploy a web app
|
|
33
|
+
|
|
34
|
+
Ask:
|
|
35
|
+
|
|
36
|
+
- Is this a quick try / prototype / demo / temporary preview (free)?
|
|
37
|
+
- Or production (long-term, custom domain, high availability)?
|
|
38
|
+
|
|
39
|
+
Route by scenario:
|
|
40
|
+
|
|
41
|
+
- Free / quick try / prototype / demo / temporary preview -> huawei-sandbox (temporary runtime + public URL, ~8h validity, zero billed resources)
|
|
42
|
+
- Production / long-term / custom domain / high availability -> huawei-functiongraph / huawei-ecs (or huawei-cce for containers)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
5
6
|
|
|
6
7
|
export const SUPPORTED_AGENT_TARGETS = [
|
|
7
8
|
'opencode',
|
|
@@ -11,6 +12,8 @@ export const SUPPORTED_AGENT_TARGETS = [
|
|
|
11
12
|
'workbuddy',
|
|
12
13
|
'dsh',
|
|
13
14
|
'officeace',
|
|
15
|
+
'hermes',
|
|
16
|
+
'openclaw',
|
|
14
17
|
];
|
|
15
18
|
|
|
16
19
|
function baseHome() {
|
|
@@ -98,30 +101,94 @@ function dshRegistered() {
|
|
|
98
101
|
}
|
|
99
102
|
}
|
|
100
103
|
|
|
104
|
+
function readOfficeaceRegistryInstallDir() {
|
|
105
|
+
if (process.platform !== 'win32') return null;
|
|
106
|
+
try {
|
|
107
|
+
const r = spawnSync('reg', ['query', 'HKCU\\SOFTWARE\\OfficeAce\\OfficeAce', '/v', 'InstallDir'], {
|
|
108
|
+
encoding: 'utf8',
|
|
109
|
+
windowsHide: true,
|
|
110
|
+
timeout: 5000,
|
|
111
|
+
});
|
|
112
|
+
if (r.status === 0) {
|
|
113
|
+
const m = r.stdout.match(/InstallDir\s+REG_SZ\s+(.+)/);
|
|
114
|
+
if (m) return m[1].trim();
|
|
115
|
+
}
|
|
116
|
+
} catch {}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
101
120
|
function officeaceCapabilitiesDir() {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
121
|
+
const configRoot = process.env.OFFICE_CLAW_CONFIG_ROOT;
|
|
122
|
+
if (configRoot && existsSync(join(configRoot, 'capabilities.json'))) return configRoot;
|
|
123
|
+
const regDir = readOfficeaceRegistryInstallDir();
|
|
124
|
+
if (regDir) {
|
|
125
|
+
const dir = join(regDir, '.office-claw');
|
|
126
|
+
if (existsSync(join(dir, 'capabilities.json'))) return dir;
|
|
127
|
+
}
|
|
106
128
|
if (process.platform === 'win32') {
|
|
107
|
-
|
|
129
|
+
const bases = [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files'];
|
|
130
|
+
if (process.env.LOCALAPPDATA) bases.push(join(process.env.LOCALAPPDATA, 'Programs'));
|
|
131
|
+
for (const base of bases) {
|
|
108
132
|
if (!base) continue;
|
|
109
133
|
const dir = join(base, 'OfficeAce', '.office-claw');
|
|
110
134
|
if (existsSync(join(dir, 'capabilities.json'))) return dir;
|
|
111
135
|
}
|
|
112
136
|
}
|
|
113
|
-
return
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function officeaceCapabilitiesDirSafe() {
|
|
141
|
+
return officeaceCapabilitiesDir() || join(baseHome(), '.office-claw');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function officeaceSqlitePath() {
|
|
145
|
+
const capDir = officeaceCapabilitiesDirSafe();
|
|
146
|
+
return join(resolve(capDir, '..'), 'data', 'mcp-connectors.sqlite');
|
|
114
147
|
}
|
|
115
148
|
|
|
116
149
|
function officeaceRegistered() {
|
|
117
|
-
|
|
150
|
+
let hasMcp = false;
|
|
151
|
+
const dbPath = officeaceSqlitePath();
|
|
152
|
+
if (existsSync(dbPath)) {
|
|
153
|
+
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
|
154
|
+
if (nodeMajor >= 22) {
|
|
155
|
+
try {
|
|
156
|
+
const { DatabaseSync } = createRequire(import.meta.url)('node:sqlite');
|
|
157
|
+
const db = new DatabaseSync(dbPath, { readonly: true });
|
|
158
|
+
const row = db.prepare("SELECT enabled FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").get();
|
|
159
|
+
db.close();
|
|
160
|
+
hasMcp = Boolean(row?.enabled);
|
|
161
|
+
} catch {}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const capFile = join(officeaceCapabilitiesDirSafe(), 'capabilities.json');
|
|
118
166
|
const cfg = readJsonSafe(capFile);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const hasSkills = cfg.capabilities.some((c) => c.id === 'huaweicloud-core' && c.type === 'skill');
|
|
167
|
+
const hasSkills = cfg?.capabilities?.some((c) => c.id === 'huaweicloud-core' && c.type === 'skill') ?? false;
|
|
168
|
+
|
|
122
169
|
return hasMcp || hasSkills;
|
|
123
170
|
}
|
|
124
171
|
|
|
172
|
+
function hermesHome() {
|
|
173
|
+
if (process.env.HERMES_HOME) return process.env.HERMES_HOME;
|
|
174
|
+
// Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
|
|
175
|
+
if (process.platform === 'win32' && process.env.LOCALAPPDATA) {
|
|
176
|
+
return join(process.env.LOCALAPPDATA, 'hermes');
|
|
177
|
+
}
|
|
178
|
+
return join(baseHome(), '.hermes');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function hermesRegistered() {
|
|
182
|
+
const configPath = join(hermesHome(), 'config.yaml');
|
|
183
|
+
if (!existsSync(configPath)) return false;
|
|
184
|
+
try {
|
|
185
|
+
const content = readFileSync(configPath, 'utf8');
|
|
186
|
+
return content.includes('mcp_servers:') && content.includes('huaweicloud-devkit');
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
125
192
|
export function getAgentRegistrationStatuses(target = 'all') {
|
|
126
193
|
const requested = target === 'all' ? SUPPORTED_AGENT_TARGETS : [target];
|
|
127
194
|
const result = { target, agents: {} };
|
|
@@ -134,6 +201,7 @@ export function getAgentRegistrationStatuses(target = 'all') {
|
|
|
134
201
|
if (agent === 'workbuddy') configured = workbuddyRegistered();
|
|
135
202
|
if (agent === 'dsh') configured = dshRegistered();
|
|
136
203
|
if (agent === 'officeace') configured = officeaceRegistered();
|
|
204
|
+
if (agent === 'hermes') configured = hermesRegistered();
|
|
137
205
|
result.agents[agent] = { configured };
|
|
138
206
|
}
|
|
139
207
|
return result;
|
|
@@ -84,6 +84,13 @@ export function resolveCredentials(options = {}) {
|
|
|
84
84
|
let securityToken = process.env.HW_SECURITY_TOKEN;
|
|
85
85
|
let region = process.env.HW_REGION || process.env.HUAWEICLOUD_REGION || '';
|
|
86
86
|
|
|
87
|
+
const codeartsCreds = isCodeArtsContext() ? readCodeArtsCredentials() : null;
|
|
88
|
+
if (codeartsCreds) {
|
|
89
|
+
if (!ak && codeartsCreds.ak) ak = codeartsCreds.ak;
|
|
90
|
+
if (!sk && codeartsCreds.sk) sk = codeartsCreds.sk;
|
|
91
|
+
if (!region && codeartsCreds.region) region = codeartsCreds.region;
|
|
92
|
+
}
|
|
93
|
+
|
|
87
94
|
const stored = readGlobalCredentials();
|
|
88
95
|
if (stored) {
|
|
89
96
|
if (!ak && stored.ak) ak = stored.ak;
|
|
@@ -101,3 +108,61 @@ export function resolveCredentials(options = {}) {
|
|
|
101
108
|
|
|
102
109
|
return { ak, sk, securityToken, region };
|
|
103
110
|
}
|
|
111
|
+
|
|
112
|
+
function isCodeArtsContext() {
|
|
113
|
+
return existsSync(join(process.cwd(), '.codeartsdoer')) || existsSync(join(homedir(), '.codeartsdoer'));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readCodeArtsCredentials() {
|
|
117
|
+
const searchPaths = [
|
|
118
|
+
join(process.cwd(), '.codeartsdoer', 'mcp', 'mcp_settings.json'),
|
|
119
|
+
join(homedir(), '.codeartsdoer', 'mcp', 'mcp_settings.json'),
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
for (const path of searchPaths) {
|
|
123
|
+
try {
|
|
124
|
+
if (!existsSync(path)) continue;
|
|
125
|
+
const config = JSON.parse(readFileSync(path, 'utf8'));
|
|
126
|
+
const server = config?.mcpServers?.['huaweicloud-devkit'];
|
|
127
|
+
if (!server?.env) continue;
|
|
128
|
+
|
|
129
|
+
const ak = server.env.HW_ACCESS_KEY;
|
|
130
|
+
const sk = server.env.HW_SECRET_KEY;
|
|
131
|
+
if (ak && sk) {
|
|
132
|
+
return {
|
|
133
|
+
ak,
|
|
134
|
+
sk,
|
|
135
|
+
securityToken: server.env.HW_SECURITY_TOKEN || '',
|
|
136
|
+
region: server.env.HW_REGION || server.env.HUAWEICLOUD_REGION || '',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// mcp_settings.json missing or invalid — skip
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let runtimeCredentials = null;
|
|
148
|
+
|
|
149
|
+
export function setRuntimeCredentials(ak, sk, securityToken, region) {
|
|
150
|
+
runtimeCredentials = { ak, sk, securityToken: securityToken || '', region: region || '' };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function clearRuntimeCredentials() {
|
|
154
|
+
runtimeCredentials = null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function resolveCredentialsWithRuntime(options = {}) {
|
|
158
|
+
if (runtimeCredentials) {
|
|
159
|
+
return {
|
|
160
|
+
ak: runtimeCredentials.ak,
|
|
161
|
+
sk: runtimeCredentials.sk,
|
|
162
|
+
securityToken: runtimeCredentials.securityToken,
|
|
163
|
+
region: runtimeCredentials.region,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return resolveCredentials(options);
|
|
168
|
+
}
|
|
@@ -1,10 +1,23 @@
|
|
|
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';
|
|
3
|
+
import { rmSync, existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { resolve, dirname, join } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
|
|
7
7
|
|
|
8
|
+
try {
|
|
9
|
+
const { readProxyConfig } = await import('./proxy/proxy-config.mjs');
|
|
10
|
+
const proxyConfig = readProxyConfig();
|
|
11
|
+
if (proxyConfig) {
|
|
12
|
+
if (proxyConfig.https_proxy || proxyConfig.HTTPS_PROXY) {
|
|
13
|
+
process.env.HTTPS_PROXY = process.env.HTTPS_PROXY || proxyConfig.https_proxy || proxyConfig.HTTPS_PROXY;
|
|
14
|
+
}
|
|
15
|
+
if (proxyConfig.http_proxy || proxyConfig.HTTP_PROXY) {
|
|
16
|
+
process.env.HTTP_PROXY = process.env.HTTP_PROXY || proxyConfig.http_proxy || proxyConfig.HTTP_PROXY;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
} catch {}
|
|
20
|
+
|
|
8
21
|
// The MCP server is now loaded by a live agent session. Clear the install marker
|
|
9
22
|
// in this plugin dir so `doctor` no longer reports "restart needed".
|
|
10
23
|
try {
|
|
@@ -13,6 +26,20 @@ try {
|
|
|
13
26
|
if (existsSync(marker)) rmSync(marker, { force: true });
|
|
14
27
|
} catch {}
|
|
15
28
|
|
|
29
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const pluginRoot = resolve(__dirname, '..');
|
|
31
|
+
const packageRoot = resolve(pluginRoot, '..', '..');
|
|
32
|
+
let pkgVersion = '0.0.0';
|
|
33
|
+
for (const base of [pluginRoot, packageRoot]) {
|
|
34
|
+
try {
|
|
35
|
+
const version = JSON.parse(readFileSync(join(base, 'package.json'), 'utf8')).version;
|
|
36
|
+
if (version) {
|
|
37
|
+
pkgVersion = version;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
} catch {}
|
|
41
|
+
}
|
|
42
|
+
|
|
16
43
|
let buffer = Buffer.alloc(0);
|
|
17
44
|
let useContentLengthFraming = true;
|
|
18
45
|
|
|
@@ -90,7 +117,7 @@ async function dispatch(method, params) {
|
|
|
90
117
|
},
|
|
91
118
|
serverInfo: {
|
|
92
119
|
name: 'huaweicloud-devkit',
|
|
93
|
-
version:
|
|
120
|
+
version: pkgVersion,
|
|
94
121
|
},
|
|
95
122
|
};
|
|
96
123
|
}
|
|
@@ -4,7 +4,12 @@ let cachedDispatcher = undefined;
|
|
|
4
4
|
let cachedDispatcherProxyUrl = null;
|
|
5
5
|
|
|
6
6
|
async function importUndici() {
|
|
7
|
-
|
|
7
|
+
try {
|
|
8
|
+
/* eslint-disable-next-line n/no-missing-import */
|
|
9
|
+
return await import('node:undici');
|
|
10
|
+
} catch {
|
|
11
|
+
return await import('undici');
|
|
12
|
+
}
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
export async function getProxyDispatcher(targetUrl) {
|
|
@@ -29,8 +29,13 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
|
|
|
29
29
|
body: body ? JSON.stringify(body) : undefined,
|
|
30
30
|
signal: controller.signal,
|
|
31
31
|
};
|
|
32
|
-
if (dispatcher)
|
|
33
|
-
|
|
32
|
+
if (dispatcher) {
|
|
33
|
+
fetchOpts.dispatcher = dispatcher;
|
|
34
|
+
const { fetch: undiciFetch } = await import('undici');
|
|
35
|
+
resp = await undiciFetch(url, fetchOpts);
|
|
36
|
+
} else {
|
|
37
|
+
resp = await fetch(url, fetchOpts);
|
|
38
|
+
}
|
|
34
39
|
} finally {
|
|
35
40
|
clearTimeout(timer);
|
|
36
41
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveCredentialsWithRuntime } from '../auth/credentials.mjs';
|
|
3
3
|
import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
|
|
4
4
|
|
|
5
5
|
const BASE_URL = process.env.HWLINK_ENDPOINT || 'https://devstation.myhuaweicloud.com';
|
|
@@ -77,7 +77,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export function getCredentials() {
|
|
80
|
-
const credentials =
|
|
80
|
+
const credentials = resolveCredentialsWithRuntime();
|
|
81
81
|
return { ak: credentials.ak, sk: credentials.sk, securitytoken: credentials.securityToken };
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -89,7 +89,12 @@ async function apiGet(path, query, ak, sk, securitytoken) {
|
|
|
89
89
|
const url = `${BASE_URL}${fullPath}`;
|
|
90
90
|
const dispatcher = await getProxyDispatcher(url);
|
|
91
91
|
const fetchOpts = { headers };
|
|
92
|
-
if (dispatcher)
|
|
92
|
+
if (dispatcher) {
|
|
93
|
+
fetchOpts.dispatcher = dispatcher;
|
|
94
|
+
const { fetch: undiciFetch } = await import('undici');
|
|
95
|
+
const resp = await undiciFetch(url, fetchOpts);
|
|
96
|
+
return { status: resp.status, data: await resp.json() };
|
|
97
|
+
}
|
|
93
98
|
const resp = await fetch(url, fetchOpts);
|
|
94
99
|
return { status: resp.status, data: await resp.json() };
|
|
95
100
|
}
|
|
@@ -103,7 +108,12 @@ async function apiPost(path, body, ak, sk, securitytoken) {
|
|
|
103
108
|
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
104
109
|
body: JSON.stringify(body),
|
|
105
110
|
};
|
|
106
|
-
if (dispatcher)
|
|
111
|
+
if (dispatcher) {
|
|
112
|
+
fetchOpts.dispatcher = dispatcher;
|
|
113
|
+
const { fetch: undiciFetch } = await import('undici');
|
|
114
|
+
const resp = await undiciFetch(url, fetchOpts);
|
|
115
|
+
return { status: resp.status, data: await resp.json() };
|
|
116
|
+
}
|
|
107
117
|
const resp = await fetch(url, fetchOpts);
|
|
108
118
|
return { status: resp.status, data: await resp.json() };
|
|
109
119
|
}
|