huaweicloud-devkit 1.0.2-next.12 → 1.0.2-next.17

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 CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  Help AI coding agents use Huawei Cloud safely and accurately — a single integration that gives agents cloud knowledge, CLI tooling, and safety guardrails.
10
10
 
11
- Supports OpenCode, Codex, CodeArts Agent, WorkBuddy, DeepSeek Harness (DSH), and OfficeAce.
11
+ Supports OpenCode, CodeArts Agent, WorkBuddy, DeepSeek Harness (DSH), and OfficeAce.
12
12
 
13
13
  ## Prerequisites
14
14
 
@@ -33,23 +33,6 @@ npx --yes huaweicloud-devkit update --target opencode
33
33
  npx --yes huaweicloud-devkit uninstall --target opencode
34
34
  ```
35
35
 
36
- ### Codex
37
-
38
- ```bash
39
- npx --yes huaweicloud-devkit install --target codex
40
- ```
41
-
42
- > The Codex CLI must be installed first.
43
-
44
- ```bash
45
- npx --yes huaweicloud-devkit doctor --target codex
46
- npx --yes huaweicloud-devkit status --target codex
47
- npx --yes huaweicloud-devkit update --target codex
48
- npx --yes huaweicloud-devkit uninstall --target codex
49
- ```
50
-
51
- > **Codex Desktop** (Windows): use `--target codex-desktop` with the same commands.
52
-
53
36
  ### CodeArts Agent
54
37
 
55
38
  ```bash
@@ -65,7 +48,7 @@ npx --yes huaweicloud-devkit update --target codearts
65
48
  npx --yes huaweicloud-devkit uninstall --target codearts
66
49
  ```
67
50
 
68
- > **Sandbox mode**: CodeArts defaults to sandbox mode which blocks KooCLI. `install-hcloud` detects this and shows how to resolve it — install KooCLI outside the sandbox terminal, or disable sandbox mode in CodeArts settings (Settings → PermissionsBash mode).
51
+ > **Sandbox mode**: CodeArts defaults to sandbox mode which blocks KooCLI. `install-hcloud` detects this and shows how to resolve it — install KooCLI outside the sandbox terminal, or disable sandbox mode in CodeArts settings (Settings → ChatsAgents Terminal Command Running Mode → Auto Running).
69
52
 
70
53
  ### WorkBuddy
71
54
 
@@ -116,26 +99,22 @@ npx --yes huaweicloud-devkit uninstall --target officeace
116
99
 
117
100
  ### Other Agents
118
101
 
119
- For agents that support the Model Context Protocol (MCP), add the MCP server manually:
102
+ Any agent that supports MCP can use the standard config:
120
103
 
121
104
  ```json
122
105
  {
123
106
  "mcpServers": {
124
107
  "huaweicloud-devkit": {
125
- "command": "node",
126
- "args": ["<path>/plugins/huaweicloud-core/src/mcp-server.mjs"]
108
+ "command": "npx",
109
+ "args": ["-y", "-p", "huaweicloud-devkit@next", "huaweicloud-devkit-mcp"]
127
110
  }
128
111
  }
129
112
  }
130
113
  ```
131
114
 
132
- Then install the skills:
133
-
134
- ```bash
135
- npx --yes huaweicloud-devkit install
136
- ```
115
+ No installation required — `npx` handles everything.
137
116
 
138
- > For unified credentials across KooCLI and OBS, run `npx huaweicloud-devkit auth init`.
117
+ > Set `HW_ACCESS_KEY`/`HW_SECRET_KEY` in the MCP config `env` field for project-level credentials.
139
118
 
140
119
  ### Install KooCLI
141
120
 
@@ -171,6 +150,7 @@ npx --yes huaweicloud-devkit update --target all
171
150
  - **Safety-first execution** — all write operations require explicit user approval; credentials and secrets are automatically redacted from output
172
151
  - **Pre-execution risk checks** — public exposure, credential leaks, and destructive operations are caught before they run
173
152
  - **Regional awareness** — auto-discovers available regions and checks service availability before creating resources
153
+ - **Sandbox (DevStation)** — temporary cloud runtime for web app deployment with instant public URL preview
174
154
 
175
155
  ## Supported Services
176
156
 
package/README.zh-CN.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  帮助 AI 编码助手安全、准确地使用华为云——一站式集成云知识、CLI 工具和安全护栏。
10
10
 
11
- 支持 OpenCode、Codex、码道(CodeArts Agent)、WorkBuddy、DeepSeek Harness(DSH)、OfficeAce。
11
+ 支持 OpenCode、码道(CodeArts Agent)、WorkBuddy、DeepSeek Harness(DSH)、OfficeAce。
12
12
 
13
13
  ## 前置条件
14
14
 
@@ -33,23 +33,6 @@ npx --yes huaweicloud-devkit update --target opencode
33
33
  npx --yes huaweicloud-devkit uninstall --target opencode
34
34
  ```
35
35
 
36
- ### Codex
37
-
38
- ```bash
39
- npx --yes huaweicloud-devkit install --target codex
40
- ```
41
-
42
- > 需要先安装 Codex CLI。
43
-
44
- ```bash
45
- npx --yes huaweicloud-devkit doctor --target codex
46
- npx --yes huaweicloud-devkit status --target codex
47
- npx --yes huaweicloud-devkit update --target codex
48
- npx --yes huaweicloud-devkit uninstall --target codex
49
- ```
50
-
51
- > **Codex Desktop**(Windows):将 `--target codex` 换成 `--target codex-desktop`,命令相同。
52
-
53
36
  ### CodeArts Agent(码道)
54
37
 
55
38
  ```bash
@@ -65,7 +48,7 @@ npx --yes huaweicloud-devkit update --target codearts
65
48
  npx --yes huaweicloud-devkit uninstall --target codearts
66
49
  ```
67
50
 
68
- > **沙箱模式**:码道默认沙箱模式会阻止 KooCLI 运行。`install-hcloud` 自动检测并给出指引——请在码道外终端安装使用 KooCLI,或在码道设置中关闭沙箱模式(设置 → 权限Bash 模式)。
51
+ > **沙箱模式**:码道默认沙箱模式会阻止 KooCLI 运行。`install-hcloud` 自动检测并给出指引——请在码道外终端安装使用 KooCLI,或在码道设置中关闭沙箱模式(设置 → 对话流智能体 终端命令运行模式 → 自动运行)。
69
52
 
70
53
  ### WorkBuddy
71
54
 
@@ -116,26 +99,22 @@ npx --yes huaweicloud-devkit uninstall --target officeace
116
99
 
117
100
  ### 其他 Agent
118
101
 
119
- 支持 MCP 协议的 Agent,手动添加 MCP Server 配置:
102
+ 任何支持 MCP 协议的 Agent,直接使用标准 MCP 配置:
120
103
 
121
104
  ```json
122
105
  {
123
106
  "mcpServers": {
124
107
  "huaweicloud-devkit": {
125
- "command": "node",
126
- "args": ["<路径>/plugins/huaweicloud-core/src/mcp-server.mjs"]
108
+ "command": "npx",
109
+ "args": ["-y", "-p", "huaweicloud-devkit@next", "huaweicloud-devkit-mcp"]
127
110
  }
128
111
  }
129
112
  }
130
113
  ```
131
114
 
132
- 然后安装 skills:
133
-
134
- ```bash
135
- npx --yes huaweicloud-devkit install
136
- ```
115
+ 无需预安装 — `npx` 自动处理一切。
137
116
 
138
- > 执行 `npx huaweicloud-devkit auth init` 完成 KooCLI、OBS 的统一认证。
117
+ > 项目级 AK/SK 可通过 MCP 配置的 `env` 字段设置 `HW_ACCESS_KEY`/`HW_SECRET_KEY`。
139
118
 
140
119
  ### 安装 KooCLI
141
120
 
@@ -171,6 +150,7 @@ npx --yes huaweicloud-devkit update --target all
171
150
  - **安全优先执行** — 所有写操作需用户明确批准;凭证和密钥自动脱敏
172
151
  - **执行前风险检查** — 公网暴露、凭证泄露、破坏性操作在执行前即被拦截
173
152
  - **区域感知** — 自动发现可用区域,创建资源前检查服务可用性
153
+ - **沙箱(DevStation)** — 临时云端运行环境,部署 Web 应用并即刻获得公网预览地址
174
154
 
175
155
  ## 支持的服务
176
156
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.0.2-next.12",
3
+ "version": "1.0.2-next.17",
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": [
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "repository": {
17
17
  "type": "git",
18
- "url": "https://github.com/huaweicloud/HuaweiCloud-Devkit.git"
18
+ "url": "https://github.com/huaweicloud/huaweicloud-devkit.git"
19
19
  },
20
20
  "scripts": {
21
21
  "test": "node --test",
@@ -29,6 +29,7 @@
29
29
  "postinstall": "node -e \"console.log('\\nHuaweiCloud DevKit installed. Run: npx huaweicloud-devkit install\\n')\""
30
30
  },
31
31
  "dependencies": {
32
+ "better-sqlite3": "^13.0.3",
32
33
  "undici": "^8.10.0"
33
34
  },
34
35
  "devDependencies": {
@@ -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.0.2-next.12",
20
+ "version": "1.0.2-next.17",
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-core",
3
- "version": "1.0.2-next.12",
3
+ "version": "1.0.2-next.17",
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",
@@ -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.0.2-next.12",
20
+ "version": "1.0.2-next.17",
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-core",
3
- "version": "1.0.2-next.12",
3
+ "version": "1.0.2-next.17",
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",
@@ -18,15 +18,38 @@ from pathlib import Path
18
18
 
19
19
  DENY_PREFIX = "Huawei Cloud safety hook blocked this action: "
20
20
  RULES_PATH = Path(__file__).resolve().parents[1] / "safety" / "rules" / "cloud-risk-rules.json"
21
+ POLICY_PATH = Path(__file__).resolve().parents[1] / "safety" / "policy.json"
21
22
 
22
- CONFIG_FILE_RE = re.compile(r"(\.hcloud|\.huaweicloud|hcloud[/\\](config|credentials)|huaweicloud[/\\](config|credentials))", re.I)
23
- ENV_DUMP_RE = re.compile(r"(env|printenv|Get-ChildItem\s+Env:|gci\s+Env:|dir\s+Env:).*(HUAWEICLOUD|HWC_|HCLOUD|OS_)", re.I)
24
- SECRET_READ_RE = re.compile(r"(ShowSecretVersion|GetSecretValue|secret_string|secret_binary)", re.I)
25
- HCLOUD_RE = re.compile(r"(^|\s)hcloud(\.exe)?\s+", re.I)
26
- WRITE_OPERATION_RE = re.compile(
27
- r"\b(BatchCreate|BatchDelete|Create|Delete|Update|Modify|Resize|Reboot|Stop|Start|Restart|Authorize|Revoke|Add|Remove|Associate|Disassociate|Attach|Detach|Enable|Disable)\w*",
28
- re.I,
23
+ CONFIG_FILE_RE = None
24
+ SECRET_READ_RE = None
25
+ WRITE_OPERATION_RE = None
26
+
27
+
28
+ def load_policy():
29
+ global CONFIG_FILE_RE, SECRET_READ_RE, WRITE_OPERATION_RE
30
+ try:
31
+ with POLICY_PATH.open("r", encoding="utf-8") as file_obj:
32
+ policy = json.load(file_obj)
33
+ cred_patterns = policy.get("credentialFilePatterns", [])
34
+ if cred_patterns:
35
+ CONFIG_FILE_RE = re.compile("|".join(cred_patterns), re.I)
36
+ blocked_secrets = policy.get("blockedSecretOperations", [])
37
+ if blocked_secrets:
38
+ SECRET_READ_RE = re.compile("|".join(re.escape(op) for op in blocked_secrets), re.I)
39
+ write_prefixes = policy.get("writeOperationPrefixes", [])
40
+ if write_prefixes:
41
+ WRITE_OPERATION_RE = re.compile(r"\b(" + "|".join(write_prefixes) + r")\w*", re.I)
42
+ except Exception:
43
+ pass
44
+
45
+
46
+ load_policy()
47
+
48
+ ENV_DUMP_RE = re.compile(
49
+ r"(env|printenv|Get-ChildItem\s+Env:|gci\s+Env:|dir\s+Env:).*(HUAWEICLOUD|HWC_|HCLOUD|OS_)", re.I,
29
50
  )
51
+ HCLOUD_RE = re.compile(r"(^|\s)hcloud(\.exe)?\s+", re.I)
52
+ READ_OPERATION_RE = re.compile(r"\b(List|Show|Get|Describe|NovaList|NovaShow)\w*", re.I)
30
53
  READ_OPERATION_RE = re.compile(r"\b(List|Show|Get|Describe|NovaList|NovaShow)\w*", re.I)
31
54
 
32
55
 
@@ -117,11 +140,11 @@ def main():
117
140
  tool_name = data.get("tool_name", "")
118
141
  text = command_text(data.get("tool_input", {}))
119
142
 
120
- if CONFIG_FILE_RE.search(text):
143
+ if CONFIG_FILE_RE and CONFIG_FILE_RE.search(text):
121
144
  deny("reading Huawei Cloud credential/profile files can expose AK/SK or tokens. Use redacted toolkit tools.")
122
145
  if ENV_DUMP_RE.search(text):
123
146
  deny("dumping cloud credential environment variables is not allowed.")
124
- if SECRET_READ_RE.search(text):
147
+ if SECRET_READ_RE and SECRET_READ_RE.search(text):
125
148
  deny("direct secret value retrieval would put plaintext secrets into the agent context.")
126
149
  denied_rule = first_denied_command_rule(text)
127
150
  if denied_rule:
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "huaweicloud-devkit",
3
+ "displayName": "HuaweiCloud DevKit",
4
+ "version": "1.0.2-next.16",
5
+ "family": "bundle-plugin",
6
+ "bundleFormat": "codex",
7
+ "description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
8
+ "author": {
9
+ "name": "HuaweiCloud Mate",
10
+ "url": "https://github.com/huaweicloud"
11
+ },
12
+ "homepage": "https://github.com/huaweicloud/huaweicloud-devkit",
13
+ "repository": "https://github.com/huaweicloud/huaweicloud-devkit",
14
+ "license": "Apache-2.0",
15
+ "keywords": ["huaweicloud", "huawei-cloud", "koocli", "hcloud", "agent", "mcp", "api", "sdk", "skills", "cloud"],
16
+ "category": "Cloud",
17
+ "capabilities": ["Read", "Interactive"],
18
+ "brandColor": "#C7000B"
19
+ }
@@ -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`
@@ -100,6 +100,7 @@ When the intent is to **deploy, host, or preview a web app or static website** (
100
100
 
101
101
  - Use huaweicloud_search_docs to discover skills covering a topic
102
102
  - Use huaweicloud_retrieve_skill to load a full SKILL.md after routing
103
+ - Use huaweicloud_auth_init to switch credentials at runtime when different accounts co-exist in the same session
103
104
  - If no built-in skill matches, consult huaweicloud-capability-discovery for external marketplace options
104
105
  - Use huaweicloud_list_regions before creating regional resources
105
106
  - Use huaweicloud_get_regional_availability when unsure about service-region pairs
@@ -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',
@@ -113,12 +114,31 @@ function officeaceCapabilitiesDir() {
113
114
  return dotDir;
114
115
  }
115
116
 
117
+ function officeaceSqlitePath() {
118
+ const capDir = officeaceCapabilitiesDir();
119
+ return join(resolve(capDir, '..'), 'data', 'mcp-connectors.sqlite');
120
+ }
121
+
116
122
  function officeaceRegistered() {
123
+ let hasMcp = false;
124
+ const dbPath = officeaceSqlitePath();
125
+ if (existsSync(dbPath)) {
126
+ const nodeMajor = Number(process.versions.node.split('.')[0]);
127
+ if (nodeMajor >= 22) {
128
+ try {
129
+ const { DatabaseSync } = createRequire(import.meta.url)('node:sqlite');
130
+ const db = new DatabaseSync(dbPath, { readonly: true });
131
+ const row = db.prepare("SELECT enabled FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").get();
132
+ db.close();
133
+ hasMcp = Boolean(row?.enabled);
134
+ } catch {}
135
+ }
136
+ }
137
+
117
138
  const capFile = join(officeaceCapabilitiesDir(), 'capabilities.json');
118
139
  const cfg = readJsonSafe(capFile);
119
- if (!cfg?.capabilities) return false;
120
- const hasMcp = cfg.capabilities.some((c) => c.id === 'huaweicloud-devkit' && c.type === 'mcp');
121
- const hasSkills = cfg.capabilities.some((c) => c.id === 'huaweicloud-core' && c.type === 'skill');
140
+ const hasSkills = cfg?.capabilities?.some((c) => c.id === 'huaweicloud-core' && c.type === 'skill') ?? false;
141
+
122
142
  return hasMcp || hasSkills;
123
143
  }
124
144
 
@@ -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,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
3
2
  import { stdin, stdout } from 'node:process';
4
- import { rmSync, existsSync } from 'node:fs';
5
- import { resolve, dirname } from 'node:path';
3
+ import { rmSync, existsSync, readFileSync } from 'node:fs';
4
+ import { resolve, dirname, join } from 'node:path';
6
5
  import { fileURLToPath } from 'node:url';
7
6
  import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
8
7
 
@@ -27,6 +26,14 @@ try {
27
26
  if (existsSync(marker)) rmSync(marker, { force: true });
28
27
  } catch {}
29
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
+ try {
34
+ pkgVersion = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')).version;
35
+ } catch {}
36
+
30
37
  let buffer = Buffer.alloc(0);
31
38
  let useContentLengthFraming = true;
32
39
 
@@ -104,7 +111,7 @@ async function dispatch(method, params) {
104
111
  },
105
112
  serverInfo: {
106
113
  name: 'huaweicloud-devkit',
107
- version: '0.1.0',
114
+ version: pkgVersion,
108
115
  },
109
116
  };
110
117
  }
@@ -1,5 +1,5 @@
1
1
  import crypto from 'node:crypto';
2
- import { resolveCredentials } from '../auth/credentials.mjs';
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 = resolveCredentials();
80
+ const credentials = resolveCredentialsWithRuntime();
81
81
  return { ak: credentials.ak, sk: credentials.sk, securitytoken: credentials.securityToken };
82
82
  }
83
83
 
@@ -13,6 +13,8 @@ import { fileURLToPath } from 'node:url';
13
13
  import { homedir, platform } from 'node:os';
14
14
  import { createInterface } from 'node:readline';
15
15
  import { spawnSync } from 'node:child_process';
16
+ import { randomUUID } from 'node:crypto';
17
+ import Database from 'better-sqlite3';
16
18
  import { getAuthStatus, syncAuth } from './auth/service.mjs';
17
19
  import { SUPPORTED_AGENT_TARGETS } from './auth/agent-registration.mjs';
18
20
  import {
@@ -150,69 +152,128 @@ function officeacePluginsDir() {
150
152
  return join(officeaceCapabilitiesDir(), 'huaweicloud-plugins');
151
153
  }
152
154
 
153
- function readCapabilitiesJson() {
154
- const capFile = officeaceCapabilitiesFile();
155
- if (!existsSync(capFile)) return { capabilities: [] };
155
+ function officeaceSqlitePath() {
156
+ const capDir = officeaceCapabilitiesDir();
157
+ return join(resolve(capDir, '..'), 'data', 'mcp-connectors.sqlite');
158
+ }
159
+
160
+ function openOfficeaceDb() {
161
+ return new Database(officeaceSqlitePath());
162
+ }
163
+
164
+ function officeaceGetOwnerUserId() {
165
+ if (!existsSync(officeaceSqlitePath())) return null;
156
166
  try {
157
- return JSON.parse(readFileSync(capFile, 'utf8'));
167
+ const db = openOfficeaceDb();
168
+ const row = db.prepare('SELECT owner_user_id FROM mcp_connectors WHERE owner_user_id IS NOT NULL LIMIT 1').get();
169
+ db.close();
170
+ return row?.owner_user_id || null;
158
171
  } catch {
159
- return { capabilities: [] };
172
+ return null;
160
173
  }
161
174
  }
162
175
 
163
- function writeCapabilitiesJson(config) {
164
- mkdirSync(officeaceCapabilitiesDir(), { recursive: true });
165
- writeFileSync(officeaceCapabilitiesFile(), JSON.stringify(config, null, 2));
166
- }
176
+ function ensureOfficeaceMcpInSqlite() {
177
+ const dbPath = officeaceSqlitePath();
178
+ if (!existsSync(dbPath)) {
179
+ console.log(` \x1b[31mOfficeAce database not found: ${dbPath}\x1b[0m`);
180
+ console.log(` \x1b[33mPlease ensure OfficeAce is installed and has been launched at least once.\x1b[0m`);
181
+ return false;
182
+ }
167
183
 
168
- function ensureOfficeaceCapabilities() {
169
- const capFile = officeaceCapabilitiesFile();
170
184
  const mcpPath = join(officeacePluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
171
- const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
185
+ const env = [{ key: 'HUAWEICLOUD_AGENT_TOOLKIT_MODE', value: 'local', sensitive: false }];
172
186
  const hcloudBin = findHcloudBin();
173
- if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
187
+ if (hcloudBin) env.push({ key: 'HCLOUD_BIN', value: hcloudBin.replace(/\\/g, '/'), sensitive: false });
174
188
 
175
- const config = readCapabilitiesJson();
176
- let changed = false;
189
+ const now = Date.now();
190
+ let db;
191
+ try {
192
+ db = openOfficeaceDb();
177
193
 
178
- // Ensure MCP server entry
179
- const mcpEntry = {
180
- id: 'huaweicloud-devkit',
181
- type: 'mcp',
182
- enabled: true,
183
- source: 'custom',
184
- mcpServer: {
185
- command: 'node',
186
- args: [mcpPath],
187
- env,
188
- },
189
- };
190
- const existingMcpIdx = config.capabilities.findIndex((c) => c.id === 'huaweicloud-devkit' && c.type === 'mcp');
191
- if (existingMcpIdx >= 0) {
192
- const existing = config.capabilities[existingMcpIdx];
193
- if (
194
- existing.mcpServer?.command === 'node' &&
195
- Array.isArray(existing.mcpServer?.args) &&
196
- existing.mcpServer.args[0] === mcpPath
197
- ) {
198
- console.log(` MCP config unchanged: ${capFile}`);
194
+ const existing = db
195
+ .prepare("SELECT id, command, args_json FROM mcp_connectors WHERE name = 'huaweicloud-devkit'")
196
+ .get();
197
+
198
+ const argsJson = JSON.stringify([mcpPath]);
199
+ const envJson = JSON.stringify(env);
200
+
201
+ if (existing) {
202
+ if (existing.command === 'node' && existing.args_json === argsJson) {
203
+ console.log(` MCP config unchanged: ${dbPath}`);
204
+ db.close();
205
+ return true;
206
+ }
207
+ db.prepare(
208
+ 'UPDATE mcp_connectors SET command = ?, args_json = ?, env_json = ?, updated_at = ?, status = ?, enabled = 1 WHERE id = ?',
209
+ ).run('node', argsJson, envJson, now, 'disconnected', existing.id);
210
+ console.log(` MCP config updated: ${dbPath}`);
199
211
  } else {
200
- config.capabilities[existingMcpIdx] = mcpEntry;
201
- changed = true;
212
+ const ownerUserId = officeaceGetOwnerUserId();
213
+ if (!ownerUserId) {
214
+ console.log(` \x1b[31mCannot determine owner_user_id from database\x1b[0m`);
215
+ db.close();
216
+ return false;
217
+ }
218
+ db.prepare(
219
+ `INSERT INTO mcp_connectors (id, owner_user_id, type, name, normalized_name, transport, timeout_ms, command, args_json, env_json, enabled, status, created_at, updated_at, version, seeded)
220
+ VALUES (?, ?, 'custom', 'huaweicloud-devkit', 'huaweicloud-devkit', 'stdio', 60000, 'node', ?, ?, 1, 'disconnected', ?, ?, 1, 0)`,
221
+ ).run(randomUUID(), ownerUserId, argsJson, envJson, now, now);
222
+ console.log(` MCP config created: ${dbPath}`);
202
223
  }
203
- } else {
204
- config.capabilities.push(mcpEntry);
205
- changed = true;
224
+ db.close();
225
+ return true;
226
+ } catch (err) {
227
+ if (db) {
228
+ try {
229
+ db.close();
230
+ } catch {}
231
+ }
232
+ console.log(` \x1b[31mFailed to write MCP config: ${err.message}\x1b[0m`);
233
+ return false;
206
234
  }
235
+ }
207
236
 
208
- if (changed) {
209
- writeCapabilitiesJson(config);
210
- console.log(` MCP config updated: ${capFile}`);
237
+ function removeOfficeaceMcpFromSqlite() {
238
+ const dbPath = officeaceSqlitePath();
239
+ if (!existsSync(dbPath)) return;
240
+ let db;
241
+ try {
242
+ db = openOfficeaceDb();
243
+ db.prepare(
244
+ "DELETE FROM mcp_connector_tools WHERE connector_id IN (SELECT id FROM mcp_connectors WHERE name = 'huaweicloud-devkit')",
245
+ ).run();
246
+ const result2 = db.prepare("DELETE FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").run();
247
+ if (result2.changes > 0) {
248
+ console.log(` MCP config removed: ${dbPath}`);
249
+ }
250
+ db.close();
251
+ } catch (err) {
252
+ if (db) {
253
+ try {
254
+ db.close();
255
+ } catch {}
256
+ }
257
+ console.log(` \x1b[31mFailed to remove MCP config: ${err.message}\x1b[0m`);
211
258
  }
212
- return changed;
213
259
  }
214
260
 
215
- function removeOfficeaceCapabilities() {
261
+ function readCapabilitiesJson() {
262
+ const capFile = officeaceCapabilitiesFile();
263
+ if (!existsSync(capFile)) return { capabilities: [] };
264
+ try {
265
+ return JSON.parse(readFileSync(capFile, 'utf8'));
266
+ } catch {
267
+ return { capabilities: [] };
268
+ }
269
+ }
270
+
271
+ function writeCapabilitiesJson(config) {
272
+ mkdirSync(officeaceCapabilitiesDir(), { recursive: true });
273
+ writeFileSync(officeaceCapabilitiesFile(), JSON.stringify(config, null, 2));
274
+ }
275
+
276
+ function removeOfficeaceSkillCapabilities() {
216
277
  const capFile = officeaceCapabilitiesFile();
217
278
  if (!existsSync(capFile)) return;
218
279
  let config;
@@ -226,14 +287,13 @@ function removeOfficeaceCapabilities() {
226
287
  config.capabilities = config.capabilities.filter(
227
288
  (c) =>
228
289
  !(
229
- (c.id === 'huaweicloud-devkit' && c.type === 'mcp') ||
230
290
  (c.id === 'huaweicloud-core' && c.type === 'skill') ||
231
291
  (c.source === 'custom' && c.id && c.id.startsWith('huawei'))
232
292
  ),
233
293
  );
234
294
  if (config.capabilities.length !== origLen) {
235
295
  writeCapabilitiesJson(config);
236
- console.log(` Capabilities cleaned: ${capFile}`);
296
+ console.log(` Skill capabilities cleaned: ${capFile}`);
237
297
  }
238
298
  }
239
299
 
@@ -317,7 +377,7 @@ function printSandboxWarning(reason) {
317
377
  console.log(`\x1b[31m 请任选其一继续:\x1b[0m`);
318
378
  console.log(`\x1b[31m A. 在码道外的终端安装并使用 KooCLI (推荐):`);
319
379
  console.log(`\x1b[31m https://support.huaweicloud.com/qs-hcli/hcli_02_003.html`);
320
- console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 → 权限Bash 模式)`);
380
+ console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 → 对话流智能体 终端命令运行模式 → 自动运行)`);
321
381
  console.log(`\x1b[31m 关闭沙箱后重新运行: npx huaweicloud-devkit install-hcloud\x1b[0m`);
322
382
  }
323
383
 
@@ -1367,7 +1427,7 @@ async function installOfficeAce() {
1367
1427
  copyDir(safetyDir, join(pluginDest, 'safety'));
1368
1428
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1369
1429
 
1370
- ensureOfficeaceCapabilities();
1430
+ ensureOfficeaceMcpInSqlite();
1371
1431
  registerOfficeaceSkillEntries();
1372
1432
  }
1373
1433
 
@@ -1384,7 +1444,7 @@ async function updateOfficeAce() {
1384
1444
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1385
1445
  copyDir(safetyDir, join(pluginDest, 'safety'));
1386
1446
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1387
- ensureOfficeaceCapabilities();
1447
+ ensureOfficeaceMcpInSqlite();
1388
1448
  registerOfficeaceSkillEntries();
1389
1449
  mkdirSync(pluginDest, { recursive: true });
1390
1450
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
@@ -1412,7 +1472,8 @@ function uninstallOfficeAce() {
1412
1472
  if (removeIfExists(officeacePluginsDir())) {
1413
1473
  console.log(' Removed MCP server and safety policy');
1414
1474
  }
1415
- removeOfficeaceCapabilities();
1475
+ removeOfficeaceSkillCapabilities();
1476
+ removeOfficeaceMcpFromSqlite();
1416
1477
  }
1417
1478
 
1418
1479
  function officeaceStatus() {
@@ -1433,15 +1494,23 @@ function officeaceStatus() {
1433
1494
  console.log(
1434
1495
  ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1435
1496
  );
1436
- const capFile = officeaceCapabilitiesFile();
1437
- if (existsSync(capFile)) {
1497
+ const dbPath = officeaceSqlitePath();
1498
+ if (existsSync(dbPath)) {
1438
1499
  try {
1439
- const config = JSON.parse(readFileSync(capFile, 'utf8'));
1440
- const hasMcp = (config.capabilities || []).some((c) => c.id === 'huaweicloud-devkit' && c.type === 'mcp');
1441
- console.log(` MCP config: ${hasMcp ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1500
+ const db = openOfficeaceDb();
1501
+ const row = db.prepare("SELECT enabled, status FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").get();
1502
+ db.close();
1503
+ if (row) {
1504
+ const statusIcon = row.status === 'connected' ? '\x1b[32m' : '\x1b[33m';
1505
+ console.log(` MCP config: ${statusIcon}${row.status}\x1b[0m (enabled: ${row.enabled ? 'yes' : 'no'})`);
1506
+ } else {
1507
+ console.log(` MCP config: \x1b[31mNot configured\x1b[0m`);
1508
+ }
1442
1509
  } catch {
1443
1510
  console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
1444
1511
  }
1512
+ } else {
1513
+ console.log(` MCP config: \x1b[31mDatabase not found\x1b[0m`);
1445
1514
  }
1446
1515
  }
1447
1516
 
@@ -16,7 +16,12 @@ import {
16
16
  } from './sandbox/session-manager.mjs';
17
17
  import { hdkitCheckUser, hdkitSignAgreement, hdkitConnect, hdkitCredentials } from './sandbox/hdkitservice-api.mjs';
18
18
  import { getAuthStatus, syncAuth } from './auth/service.mjs';
19
- import { readGlobalCredentials, writeObsConfig as writeObsConfigFile } from './auth/credentials.mjs';
19
+ import {
20
+ readGlobalCredentials,
21
+ writeObsConfig as writeObsConfigFile,
22
+ setRuntimeCredentials,
23
+ clearRuntimeCredentials,
24
+ } from './auth/credentials.mjs';
20
25
 
21
26
  const __dirname = dirname(fileURLToPath(import.meta.url));
22
27
  const SKILLS_ROOT_DEV = join(__dirname, '..', 'skills');
@@ -404,6 +409,20 @@ export const TOOL_DEFINITIONS = [
404
409
  },
405
410
  },
406
411
  },
412
+ {
413
+ name: 'huaweicloud_auth_init',
414
+ description:
415
+ 'Set or clear runtime Huawei Cloud credentials (AK/SK) for this MCP session. Runtime credentials take highest priority over environment variables and config files for all subsequent API calls. Use when switching accounts within the same Agent session — call with AK/SK to switch, or with clear=true to fall back to env/file credentials.',
416
+ inputSchema: {
417
+ type: 'object',
418
+ properties: {
419
+ ak: { type: 'string', description: 'Huawei Cloud Access Key (required unless clear=true)' },
420
+ sk: { type: 'string', description: 'Huawei Cloud Secret Key (required unless clear=true)' },
421
+ region: { type: 'string', description: 'Default region (optional)' },
422
+ clear: { type: 'boolean', description: 'Set to true to clear runtime credentials and revert to env/file' },
423
+ },
424
+ },
425
+ },
407
426
  {
408
427
  name: 'huaweicloud_sandbox_exec_with_session',
409
428
  description:
@@ -570,6 +589,16 @@ export async function callTool(name, args = {}) {
570
589
  return getAuthStatus(args.target || 'all');
571
590
  case 'huaweicloud_auth_sync':
572
591
  return syncAuth(args.target || 'all');
592
+ case 'huaweicloud_auth_init':
593
+ if (args.clear) {
594
+ clearRuntimeCredentials();
595
+ return { status: 'cleared', message: 'Runtime credentials cleared. Fallback to env/file.' };
596
+ }
597
+ if (!args.ak || !args.sk) {
598
+ throw new Error('ak and sk are required. Set clear=true to clear runtime credentials.');
599
+ }
600
+ setRuntimeCredentials(args.ak, args.sk, undefined, args.region);
601
+ return { status: 'ok', message: 'Runtime credentials set for this MCP session.' };
573
602
  case 'huaweicloud_sandbox_exec_with_session': {
574
603
  const sandboxWsId2 = args.workspace_id || DEFAULT_WORKSPACE_ID;
575
604
  const sandboxUser2 = args.username || 'root';
@@ -796,7 +825,7 @@ async function setupObsConfigFromHcloud(profile) {
796
825
  const SERVICE_EXAMPLES = {
797
826
  ECS: { list: 'ECS ListServersDetails', create: 'ECS CreateServers', show: 'IMS GlanceShowImage' },
798
827
  VPC: { list: 'VPC ListVpcs', create: 'VPC CreateVpc', show: 'VPC ShowVpc' },
799
- FunctionGraph: {
828
+ FUNCTIONGRAPH: {
800
829
  list: 'FunctionGraph ListFunctions',
801
830
  create: 'FunctionGraph CreateFunction',
802
831
  show: 'FunctionGraph ShowFunctionConfig',
@@ -805,7 +834,7 @@ const SERVICE_EXAMPLES = {
805
834
  OBS: { list: 'OBS ls', create: 'OBS mb obs://<bucket>', show: 'OBS stat obs://<bucket>/<key>' },
806
835
  RDS: { list: 'RDS ListInstances', create: 'RDS CreateInstance', show: 'RDS ShowInstance' },
807
836
  CES: { list: 'CES ListAlarms', create: 'CES CreateAlarm', show: 'CES ListMetrics' },
808
- GaussDB: { list: 'GaussDB ListInstances', create: 'GaussDB CreateInstance', show: 'GaussDB ShowInstance' },
837
+ GAUSSDB: { list: 'GaussDB ListInstances', create: 'GaussDB CreateInstance', show: 'GaussDB ShowInstance' },
809
838
  DDS: { list: 'DDS ListInstances', create: 'DDS CreateInstance', show: 'DDS ShowInstance' },
810
839
  DCS: { list: 'DCS ListInstances', create: 'DCS CreateInstance', show: 'DCS ShowInstance' },
811
840
  };