huaweicloud-devkit 1.1.8-next.2 → 1.1.8
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 +48 -0
- package/README.zh-CN.md +48 -0
- package/integrations/opencode/hooks/skill-tracker.js +83 -10
- package/integrations/opencode/opencode.json +6 -5
- 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-voucher/SKILL.md +39 -7
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +2 -1
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +2 -1
- package/plugins/huaweicloud-core/src/mcp-config-merge.mjs +82 -18
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +14 -0
- package/plugins/huaweicloud-core/src/setup-cli.mjs +108 -25
- package/plugins/huaweicloud-core/src/tools.mjs +17 -0
package/README.md
CHANGED
|
@@ -344,6 +344,54 @@ Configures HTTP/HTTPS proxy for connections to Huawei Cloud services (e.g. enter
|
|
|
344
344
|
|
|
345
345
|
Environment variables `HTTPS_PROXY`, `HTTP_PROXY`, and `NO_PROXY` take precedence over the file configuration — use `proxy show` to inspect the effective settings.
|
|
346
346
|
|
|
347
|
+
## Telemetry
|
|
348
|
+
|
|
349
|
+
HuaweiCloud DevKit collects **anonymous** telemetry to understand usage patterns and improve the plugin. Telemetry is **enabled by default**.
|
|
350
|
+
|
|
351
|
+
### What We Collect
|
|
352
|
+
|
|
353
|
+
- **Installation ID** — a SHA-256 hash derived from a machine fingerprint (hostname + MAC address + OS type + home directory). The raw values are never sent or stored.
|
|
354
|
+
- **User hash** — a server-generated user identifier hash (produced by the Huawei Cloud backend `check-user` / `generatorUserIDHash` API). It is cached locally and sent with every event; it is not derived from any local PII.
|
|
355
|
+
- **Plugin version** — the installed `huaweicloud-devkit` version.
|
|
356
|
+
- **Agent harness** — which AI coding agent is running (e.g. `opencode`, `codex`, `codearts`).
|
|
357
|
+
- **Agent version** — the agent's version string.
|
|
358
|
+
- **OS type and version** — e.g. `Linux`, `Darwin`, `Windows_NT`.
|
|
359
|
+
- **Capability** — how the event was triggered: `mcp` (MCP tool invocation) or `cli` (CLI command), derived from the event key.
|
|
360
|
+
- **Event type** — what happened, e.g. `plugin:install`, `plugin:first_use`, `dau:active_today` (daily active), `tool:<tool_name>`, `skill:retrieve`, `sandbox:connect`.
|
|
361
|
+
- **Event value** — a short sanitized string (max 255 chars, newlines/tabs stripped). For tool events this is typically `1`; for skill retrieval it is the skill name.
|
|
362
|
+
|
|
363
|
+
### What We Do **Not** Collect
|
|
364
|
+
|
|
365
|
+
- No AK/SK, access keys, or any credentials
|
|
366
|
+
- No user input, prompts, or code content
|
|
367
|
+
- No IP addresses
|
|
368
|
+
- No personally identifiable information (PII)
|
|
369
|
+
- No file paths or project names
|
|
370
|
+
|
|
371
|
+
### How Data Is Transmitted
|
|
372
|
+
|
|
373
|
+
Events are queued in memory and sent in batches (up to 100 events per batch) via HTTP POST to Huawei Cloud's telemetry endpoint every 60 seconds. Transmission respects your proxy configuration. Failed requests are retried up to 3 times, then dropped.
|
|
374
|
+
|
|
375
|
+
### Disable Telemetry (Opt-Out)
|
|
376
|
+
|
|
377
|
+
Set the environment variable:
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
export HUAWEICLOUD_DEVKIT_TELEMETRY=off
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
When set to `off`, no telemetry events are collected or transmitted.
|
|
384
|
+
|
|
385
|
+
### Debug Mode
|
|
386
|
+
|
|
387
|
+
To log telemetry activity for troubleshooting:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
export HUAWEICLOUD_DEVKIT_DEBUG=true
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Debug logs are written to the plugin's `telemetry/telemetry-debug.log` file.
|
|
394
|
+
|
|
347
395
|
## What It Does
|
|
348
396
|
|
|
349
397
|
- **Guided cloud operations** — agents get step-by-step guidance for 20+ commonly used Huawei Cloud services (ECS, OBS, VPC, RDS, GaussDB, FunctionGraph, APIG, CCE, and more)
|
package/README.zh-CN.md
CHANGED
|
@@ -338,6 +338,54 @@ npx --yes huaweicloud-devkit proxy clear # 删除代理配置
|
|
|
338
338
|
|
|
339
339
|
环境变量 `HTTPS_PROXY`、`HTTP_PROXY`、`NO_PROXY` 优先级高于文件配置——可使用 `proxy show` 查看最终生效的设置。
|
|
340
340
|
|
|
341
|
+
## 遥测
|
|
342
|
+
|
|
343
|
+
HuaweiCloud DevKit 收集**匿名**遥测数据,以了解使用模式并改进插件。遥测**默认开启**。
|
|
344
|
+
|
|
345
|
+
### 收集哪些数据
|
|
346
|
+
|
|
347
|
+
- **安装 ID** — 基于机器指纹(主机名 + MAC 地址 + OS 类型 + home 目录)生成的 SHA-256 哈希。原始值不会被发送或存储。
|
|
348
|
+
- **用户哈希** — 服务端生成的用户标识哈希(由华为云后端 `check-user` / `generatorUserIDHash` API 生成)。本地缓存后随每次事件发送,非本地 PII 派生。
|
|
349
|
+
- **插件版本** — 已安装的 `huaweicloud-devkit` 版本。
|
|
350
|
+
- **Agent harness** — 正在运行的 AI 编码助手(如 `opencode`、`codex`、`codearts`)。
|
|
351
|
+
- **Agent 版本** — Agent 版本号。
|
|
352
|
+
- **OS 类型和版本** — 如 `Linux`、`Darwin`、`Windows_NT`。
|
|
353
|
+
- **Capability** — 事件触发方式:`mcp`(MCP 工具调用)或 `cli`(CLI 命令),由事件 key 派生。
|
|
354
|
+
- **事件类型** — 发生了什么,如 `plugin:install`(安装)、`plugin:first_use`(首次使用)、`dau:active_today`(日活)、`tool:<工具名>`(工具调用)、`skill:retrieve`(技能加载)、`sandbox:connect`(沙箱连接)。
|
|
355
|
+
- **事件值** — 简短的脱敏字符串(最长 255 字符,换行符/制表符已清除)。工具事件通常为 `1`;技能加载事件为技能名称。
|
|
356
|
+
|
|
357
|
+
### **不**收集哪些数据
|
|
358
|
+
|
|
359
|
+
- 不收集 AK/SK、访问密钥或任何凭据
|
|
360
|
+
- 不收集用户输入、提示词或代码内容
|
|
361
|
+
- 不收集 IP 地址
|
|
362
|
+
- 不收集个人可识别信息(PII)
|
|
363
|
+
- 不收集文件路径或项目名称
|
|
364
|
+
|
|
365
|
+
### 数据如何传输
|
|
366
|
+
|
|
367
|
+
事件先在内存中排队,然后每 60 秒批量(每批最多 100 条)通过 HTTP POST 发送到华为云遥测端点。传输过程遵循你的代理配置。发送失败最多重试 3 次后丢弃。
|
|
368
|
+
|
|
369
|
+
### 禁用遥测(Opt-Out)
|
|
370
|
+
|
|
371
|
+
设置环境变量:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
export HUAWEICLOUD_DEVKIT_TELEMETRY=off
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
设置为 `off` 后,不会收集或传输任何遥测事件。
|
|
378
|
+
|
|
379
|
+
### 调试模式
|
|
380
|
+
|
|
381
|
+
如需记录遥测活动以供排查:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
export HUAWEICLOUD_DEVKIT_DEBUG=true
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
调试日志写入插件的 `telemetry/telemetry-debug.log` 文件。
|
|
388
|
+
|
|
341
389
|
## 功能特性
|
|
342
390
|
|
|
343
391
|
- **引导式云操作** — Agent 获得 20+ 常用华为云服务的分步操作指引(ECS、OBS、VPC、RDS、GaussDB、FunctionGraph、APIG、CCE 等)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { appendFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
4
5
|
|
|
5
6
|
const DEBUG = process.env.HUAWEICLOUD_DEVKIT_DEBUG === 'true';
|
|
6
7
|
|
|
@@ -98,19 +99,91 @@ function getHooks() {
|
|
|
98
99
|
};
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
// ──
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
102
|
+
// ── V2 setup: register hooks on their owning domains via the context. ──────
|
|
103
|
+
// `tool.execute.before` → ctx.tool.hook("execute.before", ...) whose single
|
|
104
|
+
// event carries `.tool` (name) and `.input` (args), replacing V1's (input, output).
|
|
105
|
+
// `event` → ctx.event.subscribe(AsyncIterable of { type, properties }).
|
|
106
|
+
async function setupV2(ctx) {
|
|
107
|
+
await ctx.tool.hook('execute.before', (event) => {
|
|
108
|
+
try {
|
|
109
|
+
debugLog(`HOOK tool.execute.before tool=${event?.tool}`);
|
|
110
|
+
if (event?.tool === 'skill') {
|
|
111
|
+
const name = event?.input?.name ?? event?.args?.name;
|
|
112
|
+
debugLog(`SKILL name=${name}`);
|
|
113
|
+
if (isHuaweiCloudSkill(name)) {
|
|
114
|
+
writeEvent('skill:retrieve', name);
|
|
115
|
+
debugLog(`SKILL TRACKED: ${name}`);
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (event?.tool === 'bash') {
|
|
120
|
+
const cmd = event?.input?.command ?? event?.args?.command ?? '';
|
|
121
|
+
if (!cmd) return;
|
|
122
|
+
const result = classifyHcloud(cmd);
|
|
123
|
+
if (result) writeEvent(result.key, result.value, { capability: 'cli' });
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
debugLog(`HOOK ERROR: ${error?.message || error}`);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
105
129
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
130
|
+
const controller = new AbortController();
|
|
131
|
+
void (async () => {
|
|
132
|
+
try {
|
|
133
|
+
for await (const event of ctx.event.subscribe({ signal: controller.signal })) {
|
|
134
|
+
if (event?.type === 'message.part.updated') {
|
|
135
|
+
const text = event?.properties?.part?.text;
|
|
136
|
+
if (typeof text === 'string') {
|
|
137
|
+
const m = text.match(/Base directory for this skill:\s*.*?skills[/\\]([a-z0-9-]+)/i);
|
|
138
|
+
if (m && isHuaweiCloudSkill(m[1])) writeEvent('skill:retrieve', m[1]);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
debugLog(`EVENT ERROR: ${error?.message || error}`);
|
|
144
|
+
}
|
|
145
|
+
})();
|
|
146
|
+
|
|
147
|
+
return () => controller.abort();
|
|
109
148
|
}
|
|
110
149
|
|
|
111
|
-
|
|
112
|
-
|
|
150
|
+
// ── Dual export: one default object serves OpenCode V1 and V2 ───────────────
|
|
151
|
+
// V1 loader reads `server()` (returns legacy string-keyed hooks).
|
|
152
|
+
// V2 loader reads `id` + `setup(ctx)` (registers hooks on the context domains).
|
|
153
|
+
// `Plugin.define` is a type/runtime helper injected lazily so a V1 host that
|
|
154
|
+
// lacks `@opencode/plugin` still loads (fallback to an identity define).
|
|
155
|
+
let Plugin = { define: (def) => def };
|
|
156
|
+
try {
|
|
157
|
+
const require = createRequire(import.meta.url);
|
|
158
|
+
for (const pkg of ['@opencode/plugin', '@opencode-ai/plugin']) {
|
|
159
|
+
try {
|
|
160
|
+
const mod = require(pkg);
|
|
161
|
+
if (mod?.Plugin?.define) {
|
|
162
|
+
Plugin = mod.Plugin;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
if (typeof mod?.define === 'function') {
|
|
166
|
+
Plugin = mod;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
// not installed in this host; keep the identity define
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
// createRequire unavailable; keep the identity define
|
|
175
|
+
}
|
|
113
176
|
|
|
114
|
-
export default
|
|
177
|
+
export default {
|
|
178
|
+
...Plugin.define({
|
|
179
|
+
id: 'huaweicloud-skill-tracker',
|
|
180
|
+
async setup(ctx) {
|
|
181
|
+
return setupV2(ctx);
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
async server() {
|
|
185
|
+
return getHooks();
|
|
186
|
+
},
|
|
187
|
+
};
|
|
115
188
|
|
|
116
189
|
debugLog('=== PLUGIN INIT DONE ===');
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://opencode.ai/config.json",
|
|
3
|
-
"_note": "Merge this into ~/.config/opencode/opencode.jsonc. Replace the path with the absolute path to your mcp-server.mjs installation.",
|
|
3
|
+
"_note": "Merge this into ~/.config/opencode/opencode.jsonc (OpenCode 2.x). Replace the path with the absolute path to your mcp-server.mjs installation. For OpenCode 1.x, place the same entry directly under \"mcp\" (without the \"servers\" level) and add \"enabled\": true.",
|
|
4
4
|
"mcp": {
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
"servers": {
|
|
6
|
+
"huaweicloud-devkit": {
|
|
7
|
+
"type": "local",
|
|
8
|
+
"command": ["node", "<绝对路径>/plugins/huaweicloud-core/src/mcp-server.mjs"]
|
|
9
|
+
}
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
}
|
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.8
|
|
4
|
+
"version": "1.1.8",
|
|
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.8
|
|
20
|
+
"version": "1.1.8",
|
|
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.8
|
|
23
|
+
"version": "1.1.8",
|
|
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.8
|
|
20
|
+
"version": "1.1.8",
|
|
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.8
|
|
3
|
+
"version": "1.1.8",
|
|
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.8
|
|
3
|
+
"version": "1.1.8",
|
|
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.8
|
|
5
|
+
"version": "1.1.8",
|
|
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.",
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: huawei-voucher
|
|
3
|
-
description: '
|
|
4
|
-
version:
|
|
3
|
+
description: '查询和领取华为云激励代金券(一人一次);领取华为开发者大赛·小红书skill创新赛专属代金券(需已完成赛事报名)。激励代金券:当用户首次使用、开始会话、询问插件能力,或提到"领券/代金券/优惠券/激励金/领取/福利"等话题时,先调 voucher_status 查领取状态:未领取(claimed=false)则主动提示可领取一张代金券并询问是否领取;已领取(claimed=true)或状态不明确则静默不打扰。赛事代金券:仅在用户明确要求领取大赛代金券时调用 competition_voucher_claim,不要主动提示。Triggers: 首次使用, 会话开始, voucher, coupon, incentive, 领券, 代金券, 优惠券, 激励金, 领取, 福利, 华为开发者大赛, 小红书, skill创新赛, 赛事代金券, 大赛代金券, 比赛领券. NOT for: 账单/费用查询(用 huawei-billing)。'
|
|
4
|
+
version: 2
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# 华为云代金券
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
覆盖两类活动:**激励代金券**(通用,一人一次)与**赛事代金券**(华为开发者大赛·小红书skill创新赛,需已完成赛事报名)。两者相互独立,注意区分调用对应工具。
|
|
10
|
+
|
|
11
|
+
## 激励代金券(一人一次)
|
|
12
|
+
|
|
13
|
+
### 主动提示准则(核心)
|
|
10
14
|
|
|
11
15
|
此能力仅在用户尚未领取时才有价值,需避免已领取用户被反复打扰。以下场景默认执行"先查后提示":会话开始、能力导读、首次对话、提到福利领取类话题。
|
|
12
16
|
|
|
@@ -48,12 +52,40 @@ version: 1
|
|
|
48
52
|
| `发券失败: <原文>` | 其他错误 | 原样转达,不展开 |
|
|
49
53
|
| `code=HDKIT_CRED_*` / 含「未配置 / not configured / AK/SK 无效 / 限额」 | 凭证未配置、无效或达限额 | 引导用户检查并配置 AK/SK |
|
|
50
54
|
|
|
55
|
+
## 赛事代金券(华为开发者大赛·小红书skill创新赛)
|
|
56
|
+
|
|
57
|
+
### 1. 适用场景与调用时机
|
|
58
|
+
|
|
59
|
+
用户参加华为开发者大赛·小红书skill创新赛,说出类似"帮我领取华为开发者大赛·小红书skill创新赛华为云代金券"、"领取小红书skill创新赛的券"时,调用 `huaweicloud_competition_voucher_claim`。
|
|
60
|
+
|
|
61
|
+
**与激励代金券的关键区别**:赛事代金券**不做主动提示**——不要在会话开始、能力导读等场景主动推荐;仅当用户明确提及该赛事并要求领取时才调用。
|
|
62
|
+
|
|
63
|
+
### 2. 调用
|
|
64
|
+
|
|
65
|
+
调用 `huaweicloud_competition_voucher_claim`(生产环境无需 `domain_id`,后端从 IAM 自动解析账号;测试环境需传 `domain_id`)。
|
|
66
|
+
|
|
67
|
+
后端自动完成:赛事报名校验 → 是否已领取校验 → 发券。
|
|
68
|
+
|
|
69
|
+
### 3. 结果解读(错误码 UX)
|
|
70
|
+
|
|
71
|
+
| 返回 message / code 特征 | 含义 | 对用户表述 |
|
|
72
|
+
| ----------------------------------------------------------------------- | --------------------------- | --------------------------------- |
|
|
73
|
+
| `领取成功` | 发券成功 | 告知券已到账(含 voucherId/金额) |
|
|
74
|
+
| `code=HDKIT_NOT_SIGNED_UP`(您尚未报名…) | 未报名该赛事 | 告知需先完成赛事报名后再来领取 |
|
|
75
|
+
| `已领取过` / `已领取` | 该账号已领过赛事券 | 告知已领取,不打扰 |
|
|
76
|
+
| `code=HDKIT_COMPETITION_NOT_FOUND`(赛事不存在或已下线) | 赛事配置异常 | 告知赛事暂不可用,稍后再试 |
|
|
77
|
+
| `本月代金券总额度已用完,所有账号均无法领取` | 全局额度用尽(HD.60630042) | 告知下月再试 |
|
|
78
|
+
| `请先完成实名认证` | 账号未实名(HD.60630022) | 引导去控制台实名认证 |
|
|
79
|
+
| `code=HDKIT_CRED_*` / 含「未配置 / not configured / AK/SK 无效 / 限额」 | 凭证未配置、无效或达限额 | 引导用户检查并配置 AK/SK |
|
|
80
|
+
| 其他(如 `赛事报名查询失败`、`发券失败: <原文>`) | 服务端错误 | 原样转达,建议稍后重试 |
|
|
81
|
+
|
|
51
82
|
## 环境注意
|
|
52
83
|
|
|
53
|
-
- 生产环境:`voucher_status` / `voucher_claim` 无需传 `domain_id`,后端从 IAM 自动解析账号。
|
|
84
|
+
- 生产环境:`voucher_status` / `voucher_claim` / `competition_voucher_claim` 无需传 `domain_id`,后端从 IAM 自动解析账号。
|
|
54
85
|
- 测试环境:需传 `domain_id`(华为云账号 ID);未传时返回 `测试环境需提供 domain_id`。
|
|
55
86
|
|
|
56
87
|
## 注意事项
|
|
57
88
|
|
|
58
|
-
-
|
|
59
|
-
-
|
|
89
|
+
- 激励代金券:每个账号(domain_id)仅可领取一次,重复调用返回已领取。
|
|
90
|
+
- 赛事代金券与激励代金券是**两个独立活动**,领取互不影响;用户问普通代金券时不要调赛事工具,反之亦然。
|
|
91
|
+
- 领取/报名结果以服务端返回为准,不要凭本地认知猜测。
|
|
@@ -52,7 +52,7 @@ Do not rely on training data for facts. Huawei Cloud services, pricing, quotas,
|
|
|
52
52
|
| troubleshoot | Troubleshooting Routing | error, bug, failed, AccessDenied, quota | Handoff to huaweicloud-troubleshooting |
|
|
53
53
|
| api-sdk | API/SDK Routing | API call, SDK, REST, integration, code example | Handoff to huaweicloud-api-and-sdk |
|
|
54
54
|
| report-issue | Issue Reporting | wrong service, you picked wrong, incorrect | Read references/report-issue.md |
|
|
55
|
-
| voucher | Voucher Routing | 领券, 代金券, 优惠券, 激励金, 领取, voucher, coupon, incentive
|
|
55
|
+
| voucher | Voucher Routing | 领券, 代金券, 优惠券, 激励金, 领取, voucher, coupon, incentive, 华为开发者大赛, 小红书, skill创新赛, 大赛代金券, 赛事代金券, 比赛领券 | Handoff to huawei-voucher |
|
|
56
56
|
|
|
57
57
|
## Service Map
|
|
58
58
|
|
|
@@ -81,6 +81,7 @@ Do not rely on training data for facts. Huawei Cloud services, pricing, quotas,
|
|
|
81
81
|
| Temporary runtime / web app preview | Sandbox (DevStation) | huawei-sandbox |
|
|
82
82
|
| Getting started | Account setup | huaweicloud-cli-and-auth |
|
|
83
83
|
| Incentive voucher | Voucher | huawei-voucher |
|
|
84
|
+
| Competition voucher (小红书skill创新赛) | Voucher | huawei-voucher |
|
|
84
85
|
|
|
85
86
|
**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 (runtime lifetime not exposed via API); credential expiry see `expiresAt` from `connect`/`credentials`, zero billed resources); route to `huawei-functiongraph` / `huawei-ecs` (or `huawei-cce`) for production / long-term / custom-domain / high-availability hosting. When the chosen target requires **multiple purchased resources** (e.g. VPC + subnet + ECS + EIP + RDS, or OBS + CDN + DNS) or the user asks for an architecture/cost proposal first, hand off to `huawei-iac` for orchestrated provisioning with a mandatory cost gate.
|
|
86
87
|
|
|
@@ -40,7 +40,8 @@ function readJsonSafe(path) {
|
|
|
40
40
|
function opencodeRegistered() {
|
|
41
41
|
const path = opencodeConfigFile();
|
|
42
42
|
const cfg = readJsonSafe(path);
|
|
43
|
-
|
|
43
|
+
// V2 native (`mcp.servers.*`) or V1 legacy (`mcp.*`) form.
|
|
44
|
+
return Boolean(cfg?.mcp?.['huaweicloud-devkit'] || cfg?.mcp?.servers?.['huaweicloud-devkit']);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
function codexDesktopRegistered() {
|
|
@@ -32,28 +32,76 @@ function withDefaultTimeout(entry, defaultTimeout) {
|
|
|
32
32
|
return entry;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// OpenCode style entry: { type, command: ['node', mcpPath, ...userArgs], ... }
|
|
36
|
-
|
|
35
|
+
// OpenCode style entry: { type, command: ['node', mcpPath, ...userArgs], ... }.
|
|
36
|
+
// era 'v1' writes the legacy shape (enabled + flat timeout); era 'v2' writes the
|
|
37
|
+
// native V2 shape (no flat `enabled`/`timeout` — V2 uses `disabled` and a nested
|
|
38
|
+
// timeout object, so both are left to OpenCode defaults unless the user set them).
|
|
39
|
+
export function mergeCommandStyle(existing, { mcpPath, defaultTimeout = 300000, era = 'v1' } = {}) {
|
|
40
|
+
const v2 = era === 'v2';
|
|
41
|
+
const freshDefault = () =>
|
|
42
|
+
v2
|
|
43
|
+
? { type: 'local', command: ['node', mcpPath] }
|
|
44
|
+
: withDefaultTimeout({ type: 'local', command: ['node', mcpPath], enabled: true }, defaultTimeout);
|
|
45
|
+
|
|
37
46
|
if (!isPlainObject(existing)) {
|
|
38
|
-
return {
|
|
39
|
-
entry: withDefaultTimeout({ type: 'local', command: ['node', mcpPath], enabled: true }, defaultTimeout),
|
|
40
|
-
changed: true,
|
|
41
|
-
};
|
|
47
|
+
return { entry: freshDefault(), changed: true };
|
|
42
48
|
}
|
|
43
49
|
const commandIsNodeStyle = Array.isArray(existing.command) && existing.command[0] === 'node';
|
|
44
50
|
if (!commandIsNodeStyle) {
|
|
45
51
|
// Foreign wrapper entry: keep current overwrite behavior.
|
|
46
|
-
return {
|
|
47
|
-
entry: withDefaultTimeout({ type: 'local', command: ['node', mcpPath], enabled: true }, defaultTimeout),
|
|
48
|
-
changed: true,
|
|
49
|
-
};
|
|
52
|
+
return { entry: freshDefault(), changed: true };
|
|
50
53
|
}
|
|
51
54
|
const merged = { ...existing, type: 'local', command: ['node', mcpPath, ...existing.command.slice(2)] };
|
|
52
|
-
if (
|
|
53
|
-
|
|
55
|
+
if (v2) {
|
|
56
|
+
// Native V2 shape: drop V1-only flat `enabled`/`timeout`; `disabled`,
|
|
57
|
+
// `environment`, and any nested timeout object are preserved via the spread.
|
|
58
|
+
delete merged.enabled;
|
|
59
|
+
if (typeof merged.timeout !== 'object' || merged.timeout === null || Array.isArray(merged.timeout)) {
|
|
60
|
+
delete merged.timeout;
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
if (merged.timeout === undefined && defaultTimeout) merged.timeout = defaultTimeout;
|
|
64
|
+
if (merged.enabled === undefined) merged.enabled = true;
|
|
65
|
+
}
|
|
54
66
|
return { entry: merged, changed: !sameJson(merged, existing) };
|
|
55
67
|
}
|
|
56
68
|
|
|
69
|
+
// Classify an existing OpenCode config by which MCP form the DevKit entry uses:
|
|
70
|
+
// `mcp.servers.*` (native V2) vs a flat `mcp.<name>` (legacy V1). Returns 'unknown'
|
|
71
|
+
// when the config has no MCP section or no recognizable server entry.
|
|
72
|
+
export function detectOpenCodeEra(config) {
|
|
73
|
+
if (!isPlainObject(config) || !isPlainObject(config.mcp)) return 'unknown';
|
|
74
|
+
if (isPlainObject(config.mcp.servers)) return 'v2';
|
|
75
|
+
if (Object.keys(config.mcp).some((key) => key !== 'servers' && isPlainObject(config.mcp[key]))) return 'v1';
|
|
76
|
+
return 'unknown';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Place (or merge) the DevKit entry into an OpenCode config at the correct level
|
|
80
|
+
// for the target era. Returns the new config plus whether an entry pre-existed and
|
|
81
|
+
// whether anything changed, so callers can skip writes and restore backup deltas.
|
|
82
|
+
export function mergeOpenCodeEntry(config, { mcpPath, era = 'v1' } = {}) {
|
|
83
|
+
const base = isPlainObject(config) ? config : {};
|
|
84
|
+
const key = 'huaweicloud-devkit';
|
|
85
|
+
const next = { ...base, mcp: { ...(isPlainObject(base.mcp) ? base.mcp : {}) } };
|
|
86
|
+
|
|
87
|
+
if (era === 'v2') {
|
|
88
|
+
const servers = isPlainObject(next.mcp.servers) ? { ...next.mcp.servers } : {};
|
|
89
|
+
// An entry may live in the native `servers` map or the legacy flat map; merge
|
|
90
|
+
// from whichever is present so user fields survive an era switch.
|
|
91
|
+
const existing = servers[key] ?? next.mcp[key];
|
|
92
|
+
const { entry } = mergeCommandStyle(existing, { mcpPath, era: 'v2' });
|
|
93
|
+
servers[key] = entry;
|
|
94
|
+
if (isPlainObject(next.mcp[key])) delete next.mcp[key];
|
|
95
|
+
next.mcp.servers = servers;
|
|
96
|
+
return { config: next, entry, changed: !existing || !sameJson(entry, existing), existing: Boolean(existing) };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const existing = next.mcp[key];
|
|
100
|
+
const { entry, changed } = mergeCommandStyle(existing, { mcpPath, era: 'v1' });
|
|
101
|
+
next.mcp[key] = entry;
|
|
102
|
+
return { config: next, entry, changed: !existing || changed, existing: Boolean(existing) };
|
|
103
|
+
}
|
|
104
|
+
|
|
57
105
|
// WorkBuddy/AtomCode/CodeArts style entry: { command: 'node', args: [mcpPath, ...userArgs], env, ... }
|
|
58
106
|
export function mergeArgsStyle(existing, { mcpPath, env = {}, defaultTimeout = 300000 } = {}) {
|
|
59
107
|
if (!isPlainObject(existing)) {
|
|
@@ -105,17 +153,19 @@ export function extractUserDelta(entry, style) {
|
|
|
105
153
|
} else {
|
|
106
154
|
return null;
|
|
107
155
|
}
|
|
108
|
-
|
|
109
|
-
|
|
156
|
+
const env = isPlainObject(entry.env) ? entry.env : isPlainObject(entry.environment) ? entry.environment : null;
|
|
157
|
+
if (env) {
|
|
158
|
+
const userEnv = Object.fromEntries(Object.entries(env).filter(([key]) => !REQUIRED_ENV_KEYS.has(key)));
|
|
110
159
|
if (Object.keys(userEnv).length > 0) delta.env = userEnv;
|
|
111
160
|
}
|
|
112
161
|
if (entry.timeout !== undefined && entry.timeout !== 300000) delta.timeout = entry.timeout;
|
|
113
|
-
|
|
162
|
+
// Canonical "disabled" marker: V1 uses `enabled:false`, V2 uses `disabled:true`.
|
|
163
|
+
if (entry.enabled === false || entry.disabled === true) delta.enabled = false;
|
|
114
164
|
return Object.keys(delta).length > 0 ? delta : null;
|
|
115
165
|
}
|
|
116
166
|
|
|
117
167
|
// Apply a previously saved delta onto a freshly written default entry.
|
|
118
|
-
export function applyUserDelta(entry, delta, style) {
|
|
168
|
+
export function applyUserDelta(entry, delta, style, era = 'v1') {
|
|
119
169
|
if (!isPlainObject(entry) || !isPlainObject(delta)) return entry;
|
|
120
170
|
const merged = { ...entry };
|
|
121
171
|
if (style === 'command' && Array.isArray(delta.commandExtra)) {
|
|
@@ -124,10 +174,24 @@ export function applyUserDelta(entry, delta, style) {
|
|
|
124
174
|
merged.args = [...entry.args, ...delta.argsExtra];
|
|
125
175
|
}
|
|
126
176
|
if (isPlainObject(delta.env)) {
|
|
127
|
-
|
|
177
|
+
// V2 native entries use `environment`; V1 (and args-style agents) use `env`.
|
|
178
|
+
if (style === 'command' && era === 'v2') {
|
|
179
|
+
merged.environment = { ...(isPlainObject(merged.environment) ? merged.environment : {}), ...delta.env };
|
|
180
|
+
delete merged.env;
|
|
181
|
+
} else {
|
|
182
|
+
merged.env = { ...(isPlainObject(merged.env) ? merged.env : {}), ...delta.env };
|
|
183
|
+
}
|
|
128
184
|
}
|
|
129
185
|
if (delta.timeout !== undefined) merged.timeout = delta.timeout;
|
|
130
|
-
if (delta.enabled === false)
|
|
186
|
+
if (delta.enabled === false) {
|
|
187
|
+
// Canonical disabled marker → era-appropriate field.
|
|
188
|
+
if (style === 'command' && era === 'v2') {
|
|
189
|
+
merged.disabled = true;
|
|
190
|
+
delete merged.enabled;
|
|
191
|
+
} else {
|
|
192
|
+
merged.enabled = false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
131
195
|
return merged;
|
|
132
196
|
}
|
|
133
197
|
|
|
@@ -147,3 +147,17 @@ export async function hdkitVoucherClaim(domainId) {
|
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
+
|
|
151
|
+
export async function hdkitCompetitionVoucherClaim(domainId) {
|
|
152
|
+
try {
|
|
153
|
+
const body = domainId ? { domain_id: domainId } : {};
|
|
154
|
+
return await hdkitRequest('POST', 'competition/voucher/claim', body);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return {
|
|
157
|
+
claimed: false,
|
|
158
|
+
message: error?.message || 'Competition voucher service unavailable, please try again later',
|
|
159
|
+
code: error?.code,
|
|
160
|
+
...(error?.remediation ? { remediation: error.remediation } : {}),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -43,6 +43,8 @@ import {
|
|
|
43
43
|
extractUserDelta,
|
|
44
44
|
applyUserDelta,
|
|
45
45
|
inheritPeerUserEnv,
|
|
46
|
+
detectOpenCodeEra,
|
|
47
|
+
mergeOpenCodeEntry,
|
|
46
48
|
} from './mcp-config-merge.mjs';
|
|
47
49
|
import { readAgentDelta, saveAgentDelta, takeAgentDelta, purgeBackup } from './mcp-config-backup.mjs';
|
|
48
50
|
import { isUsableOfficeaceRoot, readOfficeaceRootMarker, writeOfficeaceRootMarker } from './officeace-paths.mjs';
|
|
@@ -646,39 +648,103 @@ function removeIfExists(p) {
|
|
|
646
648
|
return false;
|
|
647
649
|
}
|
|
648
650
|
|
|
651
|
+
// ── OpenCode era detection ─────────────────────────────────────────────────
|
|
652
|
+
// OpenCode 2.x reads MCP servers from `mcp.servers.<name>` (native); OpenCode
|
|
653
|
+
// 0.x/1.x read from a flat `mcp.<name>`. We resolve the target form in this order:
|
|
654
|
+
// 1. `--opencode-mcp-era v1|v2` explicit override
|
|
655
|
+
// 2. `opencode --version` major (2 → v2, 0/1 → v1)
|
|
656
|
+
// 3. the existing config's shape (`mcp.servers` → v2, flat `mcp.*` → v1)
|
|
657
|
+
// 4. default `v2` (native, future-proof)
|
|
658
|
+
function detectOpenCodeMajor() {
|
|
659
|
+
const attempts = process.platform === 'win32' ? [false, true] : [false]; // .cmd shims need a shell on Windows
|
|
660
|
+
for (const shell of attempts) {
|
|
661
|
+
try {
|
|
662
|
+
const r = spawnSync('opencode', ['--version'], { shell, windowsHide: true, stdio: 'pipe', timeout: 10000 });
|
|
663
|
+
const out = `${r.stdout || ''}${r.stderr || ''}`.trim();
|
|
664
|
+
const m = out.match(/v?(\d+)\.\d+/);
|
|
665
|
+
if (m) return Number(m[1]);
|
|
666
|
+
} catch {
|
|
667
|
+
// ignore and try the next attempt
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function parseOpenCodeEraFlag() {
|
|
674
|
+
const idx = process.argv.indexOf('--opencode-mcp-era');
|
|
675
|
+
if (idx < 0) return null;
|
|
676
|
+
const raw = (process.argv[idx + 1] || '').toLowerCase();
|
|
677
|
+
if (raw === 'v1' || raw === 'v2') return raw;
|
|
678
|
+
console.error(`Invalid --opencode-mcp-era value "${process.argv[idx + 1] || ''}" (expected v1|v2).`);
|
|
679
|
+
process.exit(1);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
let resolvedOpenCodeEra = null;
|
|
683
|
+
function resolveOpenCodeEra() {
|
|
684
|
+
if (resolvedOpenCodeEra) return resolvedOpenCodeEra;
|
|
685
|
+
|
|
686
|
+
const flagged = parseOpenCodeEraFlag();
|
|
687
|
+
if (flagged) return (resolvedOpenCodeEra = flagged);
|
|
688
|
+
|
|
689
|
+
const major = detectOpenCodeMajor();
|
|
690
|
+
if (major === 2) return (resolvedOpenCodeEra = 'v2');
|
|
691
|
+
if (major === 0 || major === 1) return (resolvedOpenCodeEra = 'v1');
|
|
692
|
+
|
|
693
|
+
const configPath = opencodeConfigFile();
|
|
694
|
+
if (existsSync(configPath)) {
|
|
695
|
+
try {
|
|
696
|
+
const era = detectOpenCodeEra(JSON.parse(readFileSync(configPath, 'utf8')));
|
|
697
|
+
if (era !== 'unknown') return (resolvedOpenCodeEra = era);
|
|
698
|
+
} catch {
|
|
699
|
+
// fall through to default
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
return (resolvedOpenCodeEra = 'v2');
|
|
704
|
+
}
|
|
705
|
+
|
|
649
706
|
function updateOpenCodeConfig(pluginDir) {
|
|
650
707
|
const configPath = opencodeConfigFile();
|
|
651
708
|
const mcpPath = join(pluginDir, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
709
|
+
const era = resolveOpenCodeEra();
|
|
710
|
+
const formLabel = era === 'v2' ? 'mcp.servers.huaweicloud-devkit' : 'mcp.huaweicloud-devkit';
|
|
652
711
|
let config = {};
|
|
653
712
|
if (existsSync(configPath)) {
|
|
654
713
|
try {
|
|
655
714
|
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
656
715
|
} catch {
|
|
657
716
|
console.log(
|
|
658
|
-
` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath} (jsonc comments?). Skipping MCP config write; ensure "
|
|
717
|
+
` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath} (jsonc comments?). Skipping MCP config write; ensure "${formLabel}" points to ${mcpPath}.`,
|
|
659
718
|
);
|
|
660
719
|
return;
|
|
661
720
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const { config: next, existing, changed } = mergeOpenCodeEntry(config, { mcpPath, era });
|
|
724
|
+
|
|
725
|
+
// Restore user fields saved by a previous uninstall (issue #615), on fresh install only.
|
|
726
|
+
if (!existing) {
|
|
727
|
+
const delta = takeAgentDelta('opencode');
|
|
728
|
+
if (delta) {
|
|
729
|
+
const key = 'huaweicloud-devkit';
|
|
730
|
+
const entry = era === 'v2' ? next.mcp.servers[key] : next.mcp[key];
|
|
731
|
+
const restored = applyUserDelta(entry, delta, 'command', era);
|
|
732
|
+
if (era === 'v2') next.mcp.servers[key] = restored;
|
|
733
|
+
else next.mcp[key] = restored;
|
|
673
734
|
}
|
|
674
735
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
736
|
+
|
|
737
|
+
if (existing && !changed) {
|
|
738
|
+
console.log(` OpenCode MCP config unchanged: ${configPath}`);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
writeMcpSettingsFile(configPath, next);
|
|
743
|
+
console.log(
|
|
744
|
+
existing
|
|
745
|
+
? ` OpenCode MCP config merged (user fields preserved, ${era}): ${configPath}`
|
|
746
|
+
: ` OpenCode MCP config updated (${era}): ${configPath}`,
|
|
747
|
+
);
|
|
682
748
|
}
|
|
683
749
|
|
|
684
750
|
// Read-merge-write for pluginDir/.mcp.json (OpenClaw, Codex Desktop).
|
|
@@ -731,12 +797,22 @@ function removeOpenCodeConfig() {
|
|
|
731
797
|
} catch {
|
|
732
798
|
return;
|
|
733
799
|
}
|
|
734
|
-
|
|
800
|
+
const key = 'huaweicloud-devkit';
|
|
801
|
+
const flatEntry = config.mcp?.[key];
|
|
802
|
+
const serversEntry = config.mcp?.servers?.[key];
|
|
803
|
+
const entry = serversEntry || flatEntry;
|
|
804
|
+
if (!entry) return;
|
|
735
805
|
// Back up user-customized fields before removal so a later reinstall can restore them (issue #615).
|
|
736
|
-
const delta = extractUserDelta(
|
|
806
|
+
const delta = extractUserDelta(entry, 'command');
|
|
737
807
|
if (delta) saveAgentDelta('opencode', delta);
|
|
738
|
-
|
|
739
|
-
|
|
808
|
+
if (config.mcp) {
|
|
809
|
+
delete config.mcp[key];
|
|
810
|
+
if (config.mcp.servers) {
|
|
811
|
+
delete config.mcp.servers[key];
|
|
812
|
+
if (Object.keys(config.mcp.servers).length === 0) delete config.mcp.servers;
|
|
813
|
+
}
|
|
814
|
+
if (Object.keys(config.mcp).length === 0) delete config.mcp;
|
|
815
|
+
}
|
|
740
816
|
writeMcpSettingsFile(configPath, config);
|
|
741
817
|
console.log(` OpenCode MCP config cleaned: ${configPath}`);
|
|
742
818
|
}
|
|
@@ -3180,7 +3256,11 @@ function opencodeStatus() {
|
|
|
3180
3256
|
try {
|
|
3181
3257
|
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
3182
3258
|
console.log(
|
|
3183
|
-
` MCP config: ${
|
|
3259
|
+
` MCP config: ${
|
|
3260
|
+
config.mcp?.['huaweicloud-devkit'] || config.mcp?.servers?.['huaweicloud-devkit']
|
|
3261
|
+
? '\x1b[32mConfigured\x1b[0m'
|
|
3262
|
+
: '\x1b[31mNot configured\x1b[0m'
|
|
3263
|
+
}`,
|
|
3184
3264
|
);
|
|
3185
3265
|
} catch {
|
|
3186
3266
|
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
@@ -4017,7 +4097,7 @@ async function cmdDoctor() {
|
|
|
4017
4097
|
if (existsSync(opencodeCfg)) {
|
|
4018
4098
|
try {
|
|
4019
4099
|
const cfg = JSON.parse(readFileSync(opencodeCfg, 'utf8'));
|
|
4020
|
-
if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) {
|
|
4100
|
+
if (cfg.mcp && (cfg.mcp['huaweicloud-devkit'] || cfg.mcp.servers?.['huaweicloud-devkit'])) {
|
|
4021
4101
|
mcpConfigured = true;
|
|
4022
4102
|
mcpCfgTarget = 'OpenCode';
|
|
4023
4103
|
}
|
|
@@ -5188,6 +5268,9 @@ async function main() {
|
|
|
5188
5268
|
' --target Target agent: opencode (default), codex, codearts, codearts-work, workbuddy, dsh, officeace, hermes, openclaw, atomcode, all',
|
|
5189
5269
|
);
|
|
5190
5270
|
console.log(' --version Print CLI version and installed plugin version per agent');
|
|
5271
|
+
console.log(
|
|
5272
|
+
' --opencode-mcp-era v1|v2 Force OpenCode MCP config form (v1 legacy flat, v2 native mcp.servers)',
|
|
5273
|
+
);
|
|
5191
5274
|
console.log(' --clean-kocli (with: uninstall --target all) also remove KooCLI');
|
|
5192
5275
|
console.log(' --clean-obs (with: uninstall --target all) also remove OBS config');
|
|
5193
5276
|
console.log(' --clean-global (with: uninstall --target all) also remove KooCLI + OBS config');
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
hdkitCredentials,
|
|
32
32
|
hdkitVoucherStatus,
|
|
33
33
|
hdkitVoucherClaim,
|
|
34
|
+
hdkitCompetitionVoucherClaim,
|
|
34
35
|
hdkitGenerateUserHash,
|
|
35
36
|
} from './sandbox/hdkitservice-api.mjs';
|
|
36
37
|
import { getCredentials } from './sandbox/hwlink-api.mjs';
|
|
@@ -856,6 +857,20 @@ export const TOOL_DEFINITIONS = [
|
|
|
856
857
|
},
|
|
857
858
|
},
|
|
858
859
|
},
|
|
860
|
+
{
|
|
861
|
+
name: 'huaweicloud_competition_voucher_claim',
|
|
862
|
+
description:
|
|
863
|
+
'领取华为开发者大赛·小红书skill创新赛专属华为云代金券。前置条件:用户已完成该赛事报名(后端自动校验,未报名返回 code=HDKIT_NOT_SIGNED_UP)。仅当用户明确要求领取该赛事代金券时调用,不要主动提示。重复领取会返回已领取。',
|
|
864
|
+
inputSchema: {
|
|
865
|
+
type: 'object',
|
|
866
|
+
properties: {
|
|
867
|
+
domain_id: {
|
|
868
|
+
type: 'string',
|
|
869
|
+
description: 'Optional. Leave empty in production — account is resolved from IAM automatically.',
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
},
|
|
873
|
+
},
|
|
859
874
|
{
|
|
860
875
|
name: 'huaweicloud_check_update',
|
|
861
876
|
description:
|
|
@@ -1570,6 +1585,8 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1570
1585
|
return await hdkitVoucherStatus(args.domain_id);
|
|
1571
1586
|
case 'huaweicloud_voucher_claim':
|
|
1572
1587
|
return await hdkitVoucherClaim(args.domain_id);
|
|
1588
|
+
case 'huaweicloud_competition_voucher_claim':
|
|
1589
|
+
return await hdkitCompetitionVoucherClaim(args.domain_id);
|
|
1573
1590
|
case 'huaweicloud_check_update':
|
|
1574
1591
|
return await handleCheckUpdate(args, { sessionId: opts?.sessionId, doQuery: opts?.doQuery });
|
|
1575
1592
|
case 'huaweicloud_upgrade':
|