huaweicloud-devkit 1.0.3-next.0 → 1.1.0-next.0

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, CodeArts Agent, WorkBuddy, DeepSeek Harness (DSH), OfficeAce, and OpenClaw.
11
+ Supports OpenCode, CodeArts Agent, WorkBuddy, DeepSeek Harness (DSH), OfficeAce, OpenClaw, and AtomCode.
12
12
 
13
13
  ## Prerequisites
14
14
 
@@ -118,6 +118,21 @@ npx --yes huaweicloud-devkit uninstall --target openclaw
118
118
  rm -rf ~/.npm/_npx/ # Linux/macOS only; Windows path TBD
119
119
  ```
120
120
 
121
+ ### AtomCode
122
+
123
+ ```bash
124
+ npx --yes huaweicloud-devkit install --target atomcode
125
+ ```
126
+
127
+ **Restart the AtomCode session** after installation.
128
+
129
+ ```bash
130
+ npx --yes huaweicloud-devkit doctor --target atomcode
131
+ npx --yes huaweicloud-devkit status --target atomcode
132
+ npx --yes huaweicloud-devkit update --target atomcode
133
+ npx --yes huaweicloud-devkit uninstall --target atomcode
134
+ ```
135
+
121
136
  ### Other Agents
122
137
 
123
138
  Any agent that supports MCP can use the standard config:
package/README.zh-CN.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  帮助 AI 编码助手安全、准确地使用华为云——一站式集成云知识、CLI 工具和安全护栏。
10
10
 
11
- 支持 OpenCode、码道(CodeArts Agent)、WorkBuddy、DeepSeek Harness(DSH)、OfficeAce、OpenClaw。
11
+ 支持 OpenCode、码道(CodeArts Agent)、WorkBuddy、DeepSeek Harness(DSH)、OfficeAce、OpenClaw、AtomCode
12
12
 
13
13
  ## 前置条件
14
14
 
@@ -118,6 +118,21 @@ npx --yes huaweicloud-devkit uninstall --target openclaw
118
118
  rm -rf ~/.npm/_npx/ # 仅 Linux/macOS;Windows 路径待确认
119
119
  ```
120
120
 
121
+ ### AtomCode
122
+
123
+ ```bash
124
+ npx --yes huaweicloud-devkit install --target atomcode
125
+ ```
126
+
127
+ 安装后**重启 AtomCode 会话**。
128
+
129
+ ```bash
130
+ npx --yes huaweicloud-devkit doctor --target atomcode
131
+ npx --yes huaweicloud-devkit status --target atomcode
132
+ npx --yes huaweicloud-devkit update --target atomcode
133
+ npx --yes huaweicloud-devkit uninstall --target atomcode
134
+ ```
135
+
121
136
  ### 其他 Agent
122
137
 
123
138
  任何支持 MCP 协议的 Agent,直接使用标准 MCP 配置:
package/bin/setup.cjs CHANGED
@@ -4,7 +4,8 @@ const path = require('node:path');
4
4
  const fileUrl = require('node:url').pathToFileURL(
5
5
  path.join(__dirname, '..', 'plugins', 'huaweicloud-core', 'src', 'setup-cli.mjs'),
6
6
  ).href;
7
- import(fileUrl).catch((e) => {
8
- console.error(e.message);
7
+
8
+ import(fileUrl).catch((error) => {
9
+ console.error(error.message);
9
10
  process.exit(1);
10
11
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.0.3-next.0",
3
+ "version": "1.1.0-next.0",
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": [
@@ -36,7 +36,9 @@
36
36
  "@eslint/js": "^10.0.1",
37
37
  "eslint": "^10.8.1",
38
38
  "eslint-config-prettier": "^10.1.8",
39
+ "eslint-plugin-import-x": "^4.17.1",
39
40
  "eslint-plugin-n": "^18.3.0",
41
+ "eslint-plugin-unicorn": "^73.0.0",
40
42
  "globals": "^17.11.0",
41
43
  "markdownlint-cli2": "^0.23.2",
42
44
  "prettier": "^3.9.6"
@@ -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.3-next.0",
20
+ "version": "1.1.0-next.0",
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.0.3-next.0",
3
+ "version": "1.1.0-next.0",
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.3-next.0",
20
+ "version": "1.1.0-next.0",
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.0.3-next.0",
3
+ "version": "1.1.0-next.0",
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.0.3-next.0",
3
+ "version": "1.1.0-next.0",
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.0.3-next.0",
5
+ "version": "1.1.0-next.0",
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.",
@@ -14,6 +14,7 @@ export const SUPPORTED_AGENT_TARGETS = [
14
14
  'officeace',
15
15
  'hermes',
16
16
  'openclaw',
17
+ 'atomcode',
17
18
  ];
18
19
 
19
20
  function baseHome() {
@@ -169,6 +170,15 @@ function officeaceRegistered() {
169
170
  return hasMcp || hasSkills;
170
171
  }
171
172
 
173
+ function atomcodeHome() {
174
+ return process.env.ATOMCODE_HOME || join(baseHome(), '.atomcode');
175
+ }
176
+
177
+ function atomcodeRegistered() {
178
+ const cfg = readJsonSafe(join(atomcodeHome(), 'mcp.json'));
179
+ return Boolean(cfg?.mcpServers?.['huaweicloud-devkit']);
180
+ }
181
+
172
182
  function hermesHome() {
173
183
  if (process.env.HERMES_HOME) return process.env.HERMES_HOME;
174
184
  // Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
@@ -202,6 +212,7 @@ export function getAgentRegistrationStatuses(target = 'all') {
202
212
  if (agent === 'dsh') configured = dshRegistered();
203
213
  if (agent === 'officeace') configured = officeaceRegistered();
204
214
  if (agent === 'hermes') configured = hermesRegistered();
215
+ if (agent === 'atomcode') configured = atomcodeRegistered();
205
216
  result.agents[agent] = { configured };
206
217
  }
207
218
  return result;
@@ -1,5 +1,6 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { spawnSync } from 'node:child_process';
3
+
3
4
  import { getAgentRegistrationStatuses } from './agent-registration.mjs';
4
5
  import { globalCredentialsPath, obsConfigPath, readGlobalCredentials, writeObsConfig } from './credentials.mjs';
5
6
 
@@ -1,4 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
+
2
3
  import { classifyHcloudArgs, redactSecrets, assertAllowed } from './safety-policy.mjs';
3
4
  import { getProxySettings } from './proxy/proxy-config.mjs';
4
5
 
@@ -9,7 +10,7 @@ const DEFAULT_MAX_RETRIES = 1;
9
10
  export function planHcloudCommand(args, options = {}) {
10
11
  const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
11
12
  const classification = classifyHcloudArgs(normalizedArgs, options);
12
- const command = ['hcloud', ...normalizedArgs].map(quoteShellArg).join(' ');
13
+ const command = ['hcloud', ...normalizedArgs].map((arg) => quoteShellArg(arg)).join(' ');
13
14
  const warnings = planningWarnings(normalizedArgs);
14
15
  const paramValidation = validateRequiredParams(normalizedArgs);
15
16
  if (paramValidation.missing.length > 0) {
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
+
4
5
  import { getProxyDispatcher } from './proxy/proxy-agent.mjs';
5
6
 
6
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -113,7 +114,11 @@ export async function getServiceIcon(service = '', category = '') {
113
114
  };
114
115
  }
115
116
  const { manifest, source } = await loadManifest();
116
- const tokens = query.replace(/[,;]/g, ' ').split(/\s+/).filter(Boolean).map(normalizeToken);
117
+ const tokens = query
118
+ .replace(/[,;]/g, ' ')
119
+ .split(/\s+/)
120
+ .filter(Boolean)
121
+ .map((t) => normalizeToken(t));
117
122
 
118
123
  const index = { aliases: {}, tags: {}, categories: {}, descriptions: {} };
119
124
  for (const icon of manifest.icons) {
@@ -3,6 +3,7 @@ import { stdin, stdout } from 'node:process';
3
3
  import { rmSync, existsSync, readFileSync } from 'node:fs';
4
4
  import { resolve, dirname, join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
+
6
7
  import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
7
8
 
8
9
  try {
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
+
4
5
  import { evaluateCommandRisk, mergeRiskDecision } from './risk-rule-engine.mjs';
5
6
 
6
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -1,4 +1,5 @@
1
1
  import crypto from 'node:crypto';
2
+
2
3
  import { resolveCredentialsWithRuntime } from '../auth/credentials.mjs';
3
4
  import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
4
5
 
@@ -6,6 +6,7 @@ import { createHash, randomBytes } from 'node:crypto';
6
6
  import { join, dirname, basename } from 'node:path';
7
7
  import { tmpdir } from 'node:os';
8
8
  import { fileURLToPath, pathToFileURL } from 'node:url';
9
+
9
10
  import { createConnection, getCredentials } from './hwlink-api.mjs';
10
11
  import { getWebSocketImpl } from '../proxy/proxy-agent.mjs';
11
12
 
@@ -16,6 +17,10 @@ export const WS_EXEC_INDEX_URL = pathToFileURL(join(__dirname, '..', 'ws-exec',
16
17
 
17
18
  let currentWorkspaceId = process.env.HW_WORKSPACE_ID || null;
18
19
 
20
+ function getCurrentWorkspaceId() {
21
+ return currentWorkspaceId;
22
+ }
23
+
19
24
  function setWorkspaceId(id) {
20
25
  currentWorkspaceId = id;
21
26
  process.env.HW_WORKSPACE_ID = id;
@@ -366,7 +371,7 @@ async function uploadViaTunnel(localPort, archivePath, archiveSize, archiveRemot
366
371
  }
367
372
  try {
368
373
  resolve(JSON.parse(body));
369
- } catch (e) {
374
+ } catch (error) {
370
375
  reject(new Error(`invalid JSON response: ${body.slice(0, 200)}`));
371
376
  }
372
377
  });
@@ -387,7 +392,7 @@ async function uploadViaTunnel(localPort, archivePath, archiveSize, archiveRemot
387
392
  }
388
393
  try {
389
394
  resolve(JSON.parse(body));
390
- } catch (e) {
395
+ } catch (error) {
391
396
  reject(new Error(`invalid JSON response: ${body.slice(0, 200)}`));
392
397
  }
393
398
  });
@@ -431,8 +436,8 @@ async function waitForServerReady(localPort) {
431
436
  return;
432
437
  }
433
438
  uploadLog(`waitForServerReady: health check returned non-200 (retry ${i + 1})`);
434
- } catch (err) {
435
- uploadLog(`waitForServerReady: health check failed: ${err.message} (retry ${i + 1})`);
439
+ } catch (error) {
440
+ uploadLog(`waitForServerReady: health check failed: ${error.message} (retry ${i + 1})`);
436
441
  }
437
442
  await new Promise((r) => setTimeout(r, SERVER_HEALTH_INTERVAL_MS));
438
443
  }
@@ -615,4 +620,4 @@ export async function closeAllSessions() {
615
620
  }
616
621
  }
617
622
 
618
- export { currentWorkspaceId, setWorkspaceId, runNodeExec };
623
+ export { getCurrentWorkspaceId, setWorkspaceId, runNodeExec };
@@ -62,8 +62,8 @@ async function fetchJson(url, label = '') {
62
62
  return JSON.parse(decoded);
63
63
  }
64
64
  return data;
65
- } catch (e) {
66
- throw new Error(`Failed to fetch ${label}: ${e.message}`, { cause: e });
65
+ } catch (error) {
66
+ throw new Error(`Failed to fetch ${label}: ${error.message}`, { cause: error });
67
67
  } finally {
68
68
  clearTimeout(timer);
69
69
  }
@@ -100,7 +100,7 @@ function expandKeywords(rawKeyword, cnEnMap) {
100
100
  if (en === lower) expanded.push(cn);
101
101
  }
102
102
  }
103
- const unique = [...new Set(expanded)].sort();
103
+ const unique = [...new Set(expanded)].sort((a, b) => a.localeCompare(b));
104
104
  const specific = unique.filter((kw) => !isGeneric(kw));
105
105
  const generic = unique.filter((kw) => isGeneric(kw));
106
106
  return [specific, generic];
@@ -15,6 +15,7 @@ import { createInterface } from 'node:readline';
15
15
  import { spawnSync } from 'node:child_process';
16
16
  import { randomUUID } from 'node:crypto';
17
17
  import Database from 'better-sqlite3';
18
+
18
19
  import { getAuthStatus, syncAuth } from './auth/service.mjs';
19
20
  import { SUPPORTED_AGENT_TARGETS } from './auth/agent-registration.mjs';
20
21
  import {
@@ -112,6 +113,19 @@ function workbuddyPluginsDir() {
112
113
  return join(homedir(), '.workbuddy', 'huaweicloud-plugins');
113
114
  }
114
115
 
116
+ function atomcodeHome() {
117
+ return process.env.ATOMCODE_HOME || join(homedir(), '.atomcode');
118
+ }
119
+ function atomcodeSkillsDir() {
120
+ return join(atomcodeHome(), 'skills');
121
+ }
122
+ function atomcodeMcpConfigFile() {
123
+ return join(atomcodeHome(), 'mcp.json');
124
+ }
125
+ function atomcodePluginsDir() {
126
+ return join(atomcodeHome(), 'huaweicloud-plugins');
127
+ }
128
+
115
129
  function dshRoot() {
116
130
  return process.env.DSH_HOME || join(homedir(), '.dsh');
117
131
  }
@@ -248,13 +262,13 @@ function ensureOfficeaceMcpInSqlite() {
248
262
  }
249
263
  db.close();
250
264
  return true;
251
- } catch (err) {
265
+ } catch (error) {
252
266
  if (db) {
253
267
  try {
254
268
  db.close();
255
269
  } catch {}
256
270
  }
257
- console.log(` \x1b[31mFailed to write MCP config: ${err.message}\x1b[0m`);
271
+ console.log(` \x1b[31mFailed to write MCP config: ${error.message}\x1b[0m`);
258
272
  return false;
259
273
  }
260
274
  }
@@ -273,13 +287,13 @@ function removeOfficeaceMcpFromSqlite() {
273
287
  console.log(` MCP config removed: ${dbPath}`);
274
288
  }
275
289
  db.close();
276
- } catch (err) {
290
+ } catch (error) {
277
291
  if (db) {
278
292
  try {
279
293
  db.close();
280
294
  } catch {}
281
295
  }
282
- console.log(` \x1b[31mFailed to remove MCP config: ${err.message}\x1b[0m`);
296
+ console.log(` \x1b[31mFailed to remove MCP config: ${error.message}\x1b[0m`);
283
297
  }
284
298
  }
285
299
 
@@ -476,8 +490,8 @@ function removeIfExists(p) {
476
490
  try {
477
491
  rmSync(p, { recursive: true, force: true });
478
492
  return true;
479
- } catch (e) {
480
- console.log(` \x1b[33m[WARN]\x1b[0m Could not remove ${p}: ${e.message}`);
493
+ } catch (error) {
494
+ console.log(` \x1b[33m[WARN]\x1b[0m Could not remove ${p}: ${error.message}`);
481
495
  return false;
482
496
  }
483
497
  }
@@ -1202,6 +1216,148 @@ function workbuddyStatus() {
1202
1216
  }
1203
1217
  }
1204
1218
 
1219
+ function ensureAtomcodeMcpConfig() {
1220
+ const configPath = atomcodeMcpConfigFile();
1221
+ const mcpPath = join(atomcodePluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1222
+ const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
1223
+ const hcloudBin = findHcloudBin();
1224
+ if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
1225
+ let config = {};
1226
+ if (existsSync(configPath)) {
1227
+ try {
1228
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1229
+ } catch {
1230
+ console.log(
1231
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
1232
+ );
1233
+ return false;
1234
+ }
1235
+ const existing = config.mcpServers?.['huaweicloud-devkit'];
1236
+ if (
1237
+ existing &&
1238
+ existing.command === 'node' &&
1239
+ Array.isArray(existing.args) &&
1240
+ existing.args[0] === mcpPath &&
1241
+ existing.timeout === 300000
1242
+ ) {
1243
+ console.log(` MCP config unchanged: ${configPath}`);
1244
+ return false;
1245
+ }
1246
+ }
1247
+ config.mcpServers = config.mcpServers || {};
1248
+ config.mcpServers['huaweicloud-devkit'] = {
1249
+ command: 'node',
1250
+ args: [mcpPath],
1251
+ env,
1252
+ timeout: 300000,
1253
+ };
1254
+ mkdirSync(dirname(configPath), { recursive: true });
1255
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1256
+ console.log(` MCP config updated: ${configPath}`);
1257
+ return true;
1258
+ }
1259
+
1260
+ async function installAtomCode() {
1261
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1262
+ const srcDir = join(PLUGIN_ROOT, 'src');
1263
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1264
+ const pluginDest = atomcodePluginsDir();
1265
+
1266
+ copyDir(skillsSrc, atomcodeSkillsDir());
1267
+ console.log(` Skills -> ${atomcodeSkillsDir()}`);
1268
+
1269
+ copyDir(srcDir, join(pluginDest, 'src'));
1270
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1271
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1272
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1273
+
1274
+ ensureAtomcodeMcpConfig();
1275
+ installRuntimeDeps(pluginDest);
1276
+ }
1277
+
1278
+ // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
1279
+ async function updateAtomCode() {
1280
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1281
+ const srcDir = join(PLUGIN_ROOT, 'src');
1282
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1283
+ const pluginDest = atomcodePluginsDir();
1284
+
1285
+ copyDir(skillsSrc, atomcodeSkillsDir());
1286
+ const stale = pruneStale(atomcodeSkillsDir(), skillsSrc);
1287
+ console.log(` Skills updated -> ${atomcodeSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1288
+ copyDir(srcDir, join(pluginDest, 'src'));
1289
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1290
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1291
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1292
+ ensureAtomcodeMcpConfig();
1293
+ mkdirSync(pluginDest, { recursive: true });
1294
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1295
+ installRuntimeDeps(pluginDest);
1296
+ }
1297
+
1298
+ function uninstallAtomCode() {
1299
+ const skillsDir = atomcodeSkillsDir();
1300
+ let removed = 0;
1301
+ if (existsSync(skillsDir)) {
1302
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
1303
+ if (entry.name.startsWith('huawei')) {
1304
+ removeIfExists(join(skillsDir, entry.name));
1305
+ removed++;
1306
+ }
1307
+ }
1308
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1309
+ }
1310
+
1311
+ if (removeIfExists(atomcodePluginsDir())) {
1312
+ console.log(' Removed MCP server and safety policy');
1313
+ }
1314
+
1315
+ const configPath = atomcodeMcpConfigFile();
1316
+ if (existsSync(configPath)) {
1317
+ let config = {};
1318
+ try {
1319
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1320
+ } catch {}
1321
+ if (config.mcpServers?.['huaweicloud-devkit']) {
1322
+ delete config.mcpServers['huaweicloud-devkit'];
1323
+ if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
1324
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1325
+ console.log(` MCP config cleaned: ${configPath}`);
1326
+ }
1327
+ }
1328
+ }
1329
+
1330
+ function atomcodeStatus() {
1331
+ const pluginDir = atomcodePluginsDir();
1332
+ const skillsDir = atomcodeSkillsDir();
1333
+ console.log(
1334
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1335
+ );
1336
+ console.log(
1337
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1338
+ );
1339
+ let skillCount = 0;
1340
+ if (existsSync(skillsDir)) {
1341
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1342
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1343
+ ).length;
1344
+ }
1345
+ console.log(
1346
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1347
+ );
1348
+ const configPath = atomcodeMcpConfigFile();
1349
+ if (existsSync(configPath)) {
1350
+ try {
1351
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
1352
+ console.log(
1353
+ ` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1354
+ );
1355
+ } catch {
1356
+ console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
1357
+ }
1358
+ }
1359
+ }
1360
+
1205
1361
  function dshMcpServerPath() {
1206
1362
  return join(dshPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1207
1363
  }
@@ -1694,9 +1850,6 @@ function removeHermesMcpConfigBlock() {
1694
1850
  }
1695
1851
  // If we just skipped the only server under mcp_servers, remove the key too
1696
1852
  if (line.trim() === 'mcp_servers:') {
1697
- // Peek ahead to check if this mcp_servers block only has huaweicloud-devkit
1698
- const remaining = out.join('\n');
1699
- // Remove trailing mcp_servers: if it's the last meaningful line
1700
1853
  continue; // skip the mcp_servers key for now, re-add if other servers exist
1701
1854
  }
1702
1855
  out.push(line);
@@ -1848,6 +2001,7 @@ function autoDetectTarget() {
1848
2001
  ['officeace', () => existsSync(officeaceCapabilitiesDir())],
1849
2002
  ['hermes', () => existsSync(hermesHomeDir())],
1850
2003
  ['openclaw', () => existsSync(join(homedir(), '.openclaw'))],
2004
+ ['atomcode', () => existsSync(atomcodeHome())],
1851
2005
  ];
1852
2006
  const detected = checks.filter(([, check]) => check()).map(([name]) => name);
1853
2007
  if (detected.length === 0) {
@@ -1908,6 +2062,10 @@ async function cmdInstall() {
1908
2062
  console.log('\n[OpenClaw]');
1909
2063
  await installCodexDesktop();
1910
2064
  }
2065
+ if (target === 'atomcode' || target === 'all') {
2066
+ console.log('\n[AtomCode]');
2067
+ await installAtomCode();
2068
+ }
1911
2069
  if (target === 'codex' || target === 'all') {
1912
2070
  console.log('\n[Codex]');
1913
2071
  if (!hasCodexCLI()) {
@@ -1949,7 +2107,9 @@ async function cmdInstall() {
1949
2107
  ? 'Hermes Agent'
1950
2108
  : target === 'openclaw'
1951
2109
  ? 'OpenClaw'
1952
- : '当前 agent';
2110
+ : target === 'atomcode'
2111
+ ? 'AtomCode'
2112
+ : '当前 agent';
1953
2113
  const pad = ' '.repeat(24 - appName.length);
1954
2114
  if (target === 'officeace') {
1955
2115
  console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════════╗`);
@@ -2002,9 +2162,11 @@ async function cmdInstall() {
2002
2162
  ? officeacePluginsDir()
2003
2163
  : target === 'openclaw'
2004
2164
  ? codexDesktopPluginsDir()
2005
- : target === 'codex-desktop'
2006
- ? codexDesktopPluginsDir()
2007
- : opencodePluginsDir();
2165
+ : target === 'atomcode'
2166
+ ? atomcodePluginsDir()
2167
+ : target === 'codex-desktop'
2168
+ ? codexDesktopPluginsDir()
2169
+ : opencodePluginsDir();
2008
2170
  mkdirSync(markerDir, { recursive: true });
2009
2171
  writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
2010
2172
  if (target === 'opencode' || target === 'all') {
@@ -2028,6 +2190,9 @@ async function cmdInstall() {
2028
2190
  if (target === 'openclaw' || target === 'all') {
2029
2191
  console.log('Or describe your Huawei Cloud task in OpenClaw');
2030
2192
  }
2193
+ if (target === 'atomcode' || target === 'all') {
2194
+ console.log('Or describe your Huawei Cloud task in AtomCode');
2195
+ }
2031
2196
  }
2032
2197
 
2033
2198
  async function cmdUninstall() {
@@ -2063,6 +2228,10 @@ async function cmdUninstall() {
2063
2228
  console.log('\n[OpenClaw]');
2064
2229
  uninstallCodexDesktop();
2065
2230
  }
2231
+ if (target === 'atomcode' || target === 'all') {
2232
+ console.log('\n[AtomCode]');
2233
+ uninstallAtomCode();
2234
+ }
2066
2235
  if (target === 'codex-desktop' || target === 'codex' || target === 'all') {
2067
2236
  console.log('\n[Codex]');
2068
2237
  uninstallCodexDesktop();
@@ -2139,6 +2308,10 @@ async function cmdStatus() {
2139
2308
  ` Skills: ${cdSkillCount > 0 ? `\x1b[32m${cdSkillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
2140
2309
  );
2141
2310
  }
2311
+ if (target === 'atomcode' || target === 'all') {
2312
+ console.log('\n[AtomCode]');
2313
+ atomcodeStatus();
2314
+ }
2142
2315
  if (target === 'codex' || target === 'all') {
2143
2316
  console.log('\n[Codex]');
2144
2317
  if (!hasCodexCLI()) {
@@ -2181,6 +2354,7 @@ async function cmdDoctor() {
2181
2354
  const dshPluginDir = dshPluginsDir();
2182
2355
  const officeacePluginDir = officeacePluginsDir();
2183
2356
  const hermesPluginDir = hermesPluginsDir();
2357
+ const atomcodePluginDir = atomcodePluginsDir();
2184
2358
  const mcpOk =
2185
2359
  existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ||
2186
2360
  existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ||
@@ -2188,7 +2362,8 @@ async function cmdDoctor() {
2188
2362
  existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ||
2189
2363
  existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs')) ||
2190
2364
  existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs')) ||
2191
- existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'));
2365
+ existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs')) ||
2366
+ existsSync(join(atomcodePluginDir, 'src', 'mcp-server.mjs'));
2192
2367
  const mcpTarget = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs'))
2193
2368
  ? 'OpenCode'
2194
2369
  : existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs'))
@@ -2203,7 +2378,9 @@ async function cmdDoctor() {
2203
2378
  ? 'OfficeAce'
2204
2379
  : existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
2205
2380
  ? 'Hermes Agent'
2206
- : '';
2381
+ : existsSync(join(atomcodePluginDir, 'src', 'mcp-server.mjs'))
2382
+ ? 'AtomCode'
2383
+ : '';
2207
2384
  check('MCP server installed', mcpOk, 'Run: npx huaweicloud-devkit install');
2208
2385
 
2209
2386
  if (mcpOk) {
@@ -2229,7 +2406,8 @@ async function cmdDoctor() {
2229
2406
  existsSync(join(workbuddyPluginDir, 'safety', 'policy.json')) ||
2230
2407
  existsSync(join(dshPluginDir, 'safety', 'policy.json')) ||
2231
2408
  existsSync(join(officeacePluginDir, 'safety', 'policy.json')) ||
2232
- existsSync(join(hermesPluginDir, 'safety', 'policy.json'));
2409
+ existsSync(join(hermesPluginDir, 'safety', 'policy.json')) ||
2410
+ existsSync(join(atomcodePluginDir, 'safety', 'policy.json'));
2233
2411
  check('Safety policy installed', safetyOk, 'Run: npx huaweicloud-devkit install');
2234
2412
 
2235
2413
  // MCP config — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH
@@ -2306,6 +2484,18 @@ async function cmdDoctor() {
2306
2484
  } catch {}
2307
2485
  }
2308
2486
  }
2487
+ if (!mcpConfigured) {
2488
+ const atomcodeCfg = atomcodeMcpConfigFile();
2489
+ if (existsSync(atomcodeCfg)) {
2490
+ try {
2491
+ const cfg = JSON.parse(readFileSync(atomcodeCfg, 'utf8'));
2492
+ if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
2493
+ mcpConfigured = true;
2494
+ mcpCfgTarget = 'AtomCode';
2495
+ }
2496
+ } catch {}
2497
+ }
2498
+ }
2309
2499
  check(
2310
2500
  'MCP configured',
2311
2501
  mcpConfigured,
@@ -2357,6 +2547,7 @@ async function cmdDoctor() {
2357
2547
  dshSkillsDir(),
2358
2548
  officeaceSkillsDir(),
2359
2549
  hermesSkillsDir(),
2550
+ atomcodeSkillsDir(),
2360
2551
  ];
2361
2552
  let skillCount = 0;
2362
2553
  const missingSkills = [];
@@ -2413,6 +2604,7 @@ async function cmdDoctor() {
2413
2604
  { path: join(codeartsPluginsDir(), '.installed'), name: 'CodeArts' },
2414
2605
  { path: join(dshPluginsDir(), '.installed'), name: 'DSH' },
2415
2606
  { path: join(officeacePluginsDir(), '.installed'), name: 'OfficeAce' },
2607
+ { path: join(atomcodePluginsDir(), '.installed'), name: 'AtomCode' },
2416
2608
  ];
2417
2609
  for (const marker of installedMarkers) {
2418
2610
  if (existsSync(marker.path)) {
@@ -2542,6 +2734,18 @@ async function cmdUpdate() {
2542
2734
  return;
2543
2735
  }
2544
2736
 
2737
+ if (target === 'atomcode') {
2738
+ if (!existsSync(join(atomcodePluginsDir(), 'src', 'mcp-server.mjs'))) {
2739
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2740
+ return;
2741
+ }
2742
+ console.log('[AtomCode]');
2743
+ await updateAtomCode();
2744
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2745
+ console.log(`\x1b[33mRestart the AtomCode session for changes to take effect.\x1b[0m`);
2746
+ return;
2747
+ }
2748
+
2545
2749
  if (target === 'all') {
2546
2750
  let updatedAny = false;
2547
2751
  if (existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
@@ -2584,6 +2788,11 @@ async function cmdUpdate() {
2584
2788
  await updateCodexDesktop();
2585
2789
  updatedAny = true;
2586
2790
  }
2791
+ if (existsSync(join(atomcodePluginsDir(), 'src', 'mcp-server.mjs'))) {
2792
+ console.log('\n[AtomCode]');
2793
+ await updateAtomCode();
2794
+ updatedAny = true;
2795
+ }
2587
2796
  if (codexStatus()) {
2588
2797
  console.log('\n[Codex]');
2589
2798
  installCodex();
@@ -2721,8 +2930,8 @@ async function cmdInstallHcloud() {
2721
2930
  }
2722
2931
 
2723
2932
  console.log(' Or restart terminal and: hcloud version');
2724
- } catch (e) {
2725
- console.log(`\n\x1b[33mAuto-install failed: ${e.message}\x1b[0m`);
2933
+ } catch (error) {
2934
+ console.log(`\n\x1b[33mAuto-install failed: ${error.message}\x1b[0m`);
2726
2935
  console.log(` Manual: download ${url}, unzip to ${installDir}, add to PATH`);
2727
2936
  console.log(` Guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_01.html`);
2728
2937
  if (detectCodeartsSandbox() === 'sandbox') {
@@ -3079,7 +3288,7 @@ async function main() {
3079
3288
  default:
3080
3289
  console.log(BANNER);
3081
3290
  console.log(
3082
- 'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|hermes|openclaw|all>]\n',
3291
+ 'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|hermes|openclaw|atomcode|all>]\n',
3083
3292
  );
3084
3293
  console.log('Commands:');
3085
3294
  console.log(' install Install skills, MCP server, safety policy');
@@ -3094,7 +3303,7 @@ async function main() {
3094
3303
  console.log(' help Show this help');
3095
3304
  console.log('\nOptions:');
3096
3305
  console.log(
3097
- ' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, officeace, hermes, openclaw, all',
3306
+ ' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, officeace, hermes, openclaw, atomcode, all',
3098
3307
  );
3099
3308
  console.log('\nExamples:');
3100
3309
  console.log(' npx huaweicloud-devkit install');
@@ -3104,6 +3313,7 @@ async function main() {
3104
3313
  console.log(' npx huaweicloud-devkit install --target dsh');
3105
3314
  console.log(' npx huaweicloud-devkit install --target officeace');
3106
3315
  console.log(' npx huaweicloud-devkit install --target hermes');
3316
+ console.log(' npx huaweicloud-devkit install --target atomcode');
3107
3317
  console.log(' npx huaweicloud-devkit install --target all');
3108
3318
  console.log(' npx huaweicloud-devkit auth init');
3109
3319
  console.log(' npx huaweicloud-devkit auth sync --target all');
@@ -3114,7 +3324,7 @@ async function main() {
3114
3324
  }
3115
3325
  }
3116
3326
 
3117
- main().catch((e) => {
3118
- console.error(`\x1b[31mError: ${e.message}\x1b[0m`);
3327
+ main().catch((error) => {
3328
+ console.error(`\x1b[31mError: ${error.message}\x1b[0m`);
3119
3329
  process.exit(1);
3120
3330
  });
@@ -1,11 +1,12 @@
1
- import { planHcloudCommand, runHcloud } from './hcloud-cli.mjs';
2
- import { classifyTextCommand, redactSecrets } from './safety-policy.mjs';
3
- import { evaluateArtifacts, evaluateCommandRisk, evaluateDeployPlan } from './risk-rule-engine.mjs';
4
1
  import { readFileSync, readdirSync, existsSync, writeFileSync } from 'node:fs';
5
2
  import { join, dirname } from 'node:path';
6
3
  import { fileURLToPath } from 'node:url';
7
4
  import { homedir } from 'node:os';
8
5
  import { spawnSync } from 'node:child_process';
6
+
7
+ import { evaluateArtifacts, evaluateCommandRisk, evaluateDeployPlan } from './risk-rule-engine.mjs';
8
+ import { classifyTextCommand, redactSecrets } from './safety-policy.mjs';
9
+ import { planHcloudCommand, runHcloud } from './hcloud-cli.mjs';
9
10
  import { searchMarketplace } from './search-market.mjs';
10
11
  import { getServiceIcon } from './icon-library.mjs';
11
12
  import {
@@ -14,7 +15,7 @@ import {
14
15
  closeSession,
15
16
  uploadFileWithSession,
16
17
  uploadProjectWithSession,
17
- currentWorkspaceId,
18
+ getCurrentWorkspaceId,
18
19
  setWorkspaceId,
19
20
  } from './sandbox/session-manager.mjs';
20
21
  import { hdkitCheckUser, hdkitSignAgreement, hdkitConnect, hdkitCredentials } from './sandbox/hdkitservice-api.mjs';
@@ -425,7 +426,7 @@ export const TOOL_DEFINITIONS = [
425
426
  target: {
426
427
  type: 'string',
427
428
  description:
428
- 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, openclaw, or all (default).',
429
+ 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, openclaw, atomcode, or all (default).',
429
430
  },
430
431
  },
431
432
  },
@@ -685,7 +686,7 @@ export async function callTool(name, args = {}) {
685
686
  setRuntimeCredentials(args.ak, args.sk, undefined, args.region);
686
687
  return { status: 'ok', message: 'Runtime credentials set for this MCP session.' };
687
688
  case 'huaweicloud_sandbox_exec_with_session': {
688
- const sandboxWsId2 = args.workspace_id || currentWorkspaceId;
689
+ const sandboxWsId2 = args.workspace_id || getCurrentWorkspaceId();
689
690
  if (!sandboxWsId2) {
690
691
  throw new Error(
691
692
  'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
@@ -698,7 +699,7 @@ export async function callTool(name, args = {}) {
698
699
  return { stdout: sandboxResult2.stdout, exitCode: sandboxResult2.exitCode };
699
700
  }
700
701
  case 'huaweicloud_sandbox_exec_one_shot': {
701
- const sandboxWsId3 = args.workspace_id || currentWorkspaceId;
702
+ const sandboxWsId3 = args.workspace_id || getCurrentWorkspaceId();
702
703
  if (!sandboxWsId3) {
703
704
  throw new Error(
704
705
  'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
@@ -711,7 +712,7 @@ export async function callTool(name, args = {}) {
711
712
  return { stdout: sandboxResult3.stdout, exitCode: sandboxResult3.exitCode };
712
713
  }
713
714
  case 'huaweicloud_sandbox_close_session': {
714
- const sandboxWsId4 = args.workspace_id || currentWorkspaceId;
715
+ const sandboxWsId4 = args.workspace_id || getCurrentWorkspaceId();
715
716
  if (!sandboxWsId4) {
716
717
  throw new Error(
717
718
  'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
@@ -726,7 +727,7 @@ export async function callTool(name, args = {}) {
726
727
  if (!args.local_path || !args.remote_path) {
727
728
  throw new Error('local_path and remote_path are required.');
728
729
  }
729
- const sandboxWsId5 = args.workspace_id || currentWorkspaceId;
730
+ const sandboxWsId5 = args.workspace_id || getCurrentWorkspaceId();
730
731
  if (!sandboxWsId5) {
731
732
  throw new Error(
732
733
  'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
@@ -747,7 +748,7 @@ export async function callTool(name, args = {}) {
747
748
  if (!args.local_dir) {
748
749
  throw new Error('local_dir is required.');
749
750
  }
750
- const sandboxWsId6 = args.workspace_id || currentWorkspaceId;
751
+ const sandboxWsId6 = args.workspace_id || getCurrentWorkspaceId();
751
752
  if (!sandboxWsId6) {
752
753
  throw new Error(
753
754
  'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
@@ -942,11 +943,11 @@ async function setupObsConfigFromHcloud(profile) {
942
943
 
943
944
  try {
944
945
  writeFileSync(obsConfigPath, configContent, { encoding: 'utf8', mode: 0o600 });
945
- } catch (e) {
946
+ } catch (error) {
946
947
  return {
947
948
  ok: false,
948
949
  error: 'Failed to write OBS config file.',
949
- detail: e.message,
950
+ detail: error.message,
950
951
  path: obsConfigPath,
951
952
  };
952
953
  }
@@ -1132,10 +1133,10 @@ function serviceCatalog(intent = '') {
1132
1133
  },
1133
1134
  ];
1134
1135
  const matched = [];
1135
- const tokens = it.split(/[\s,./-]+/).filter((t) => t.length > 0);
1136
+ const tokens = new Set(it.split(/[\s,./-]+/).filter((t) => t.length > 0));
1136
1137
  const cjk = /[\u4e00-\u9fff]/;
1137
1138
  for (const route of routeMap) {
1138
- if (route.keywords.some((kw) => (kw.includes(' ') || cjk.test(kw) ? it.includes(kw) : tokens.includes(kw)))) {
1139
+ if (route.keywords.some((kw) => (kw.includes(' ') || cjk.test(kw) ? it.includes(kw) : tokens.has(kw)))) {
1139
1140
  matched.push(route);
1140
1141
  }
1141
1142
  }
@@ -1328,8 +1329,8 @@ async function searchDocs(query, topic = 'all') {
1328
1329
  }
1329
1330
  }
1330
1331
  }
1331
- } catch (err) {
1332
- return { ok: false, error: err.message, results: [] };
1332
+ } catch (error) {
1333
+ return { ok: false, error: error.message, results: [] };
1333
1334
  }
1334
1335
  results.sort((a, b) => b.relevance - a.relevance);
1335
1336
  return { ok: true, query: q, topic, count: results.length, results: results.slice(0, 10) };
@@ -1366,10 +1367,12 @@ async function retrieveSkill(name) {
1366
1367
  }
1367
1368
 
1368
1369
  async function listRegions() {
1369
- const result = await runHcloud(['IAM', 'KeystoneListRegions'], { timeoutMs: 30000, maxRetries: 0 }).catch((err) => ({
1370
- ok: false,
1371
- error: err.message,
1372
- }));
1370
+ const result = await runHcloud(['IAM', 'KeystoneListRegions'], { timeoutMs: 30000, maxRetries: 0 }).catch(
1371
+ (error) => ({
1372
+ ok: false,
1373
+ error: error.message,
1374
+ }),
1375
+ );
1373
1376
  if (!result.ok) {
1374
1377
  return {
1375
1378
  ok: false,
@@ -93,7 +93,7 @@ class HwlinkWebSocketMultiplexer {
93
93
  const { WebSocketImpl = globalThis.WebSocket, protocol = 'devenv', onFrame, trace = false } = options;
94
94
 
95
95
  if (typeof WebSocketImpl !== 'function') {
96
- throw new Error('global WebSocket is unavailable; use Node.js 22+ or pass WebSocketImpl');
96
+ throw new TypeError('global WebSocket is unavailable; use Node.js 22+ or pass WebSocketImpl');
97
97
  }
98
98
  if (!Number.isInteger(source) || source < -0x80000000 || source > 0xffffffff) {
99
99
  throw new Error('hwlink source must be an int32 or uint32 number');
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { createServer } from 'node:net';
11
+
11
12
  import {
12
13
  OpCode,
13
14
  ReserveSource,
@@ -287,10 +287,12 @@ class WebSocketShellSession {
287
287
  const markerIndex = pending.buffer.lastIndexOf(doneMarker);
288
288
  if (markerIndex === -1) return;
289
289
 
290
+ // eslint-disable-next-line unicorn/no-unsafe-buffer-conversion -- pending.buffer is a String, not a Buffer
290
291
  const afterMarker = pending.buffer.slice(markerIndex + doneMarker.length);
291
292
  const exitMatch = afterMarker.match(/^(\d+)/);
292
293
  if (!exitMatch) return;
293
294
 
295
+ // eslint-disable-next-line unicorn/no-unsafe-buffer-conversion -- pending.buffer is a String, not a Buffer
294
296
  const rawOutput = pending.buffer.slice(0, markerIndex);
295
297
  const stdout = cleanCommandOutput(rawOutput, {
296
298
  inputEchoed: this.inputEchoed,