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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json 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.13",
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": [
@@ -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.13",
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.13",
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.13",
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.13",
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,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,28 @@ 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
+ try {
127
+ const { DatabaseSync } = createRequire(import.meta.url)('node:sqlite');
128
+ const db = new DatabaseSync(dbPath, { readonly: true });
129
+ const row = db.prepare("SELECT enabled FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").get();
130
+ db.close();
131
+ hasMcp = Boolean(row?.enabled);
132
+ } catch {}
133
+ }
134
+
117
135
  const capFile = join(officeaceCapabilitiesDir(), 'capabilities.json');
118
136
  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');
137
+ const hasSkills = cfg?.capabilities?.some((c) => c.id === 'huaweicloud-core' && c.type === 'skill') ?? false;
138
+
122
139
  return hasMcp || hasSkills;
123
140
  }
124
141
 
@@ -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
 
@@ -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