huaweicloud-devkit 1.0.2-dev.9 → 1.1.2-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.
@@ -8,6 +8,7 @@ import { createInterface } from 'node:readline';
8
8
  import { spawnSync } from 'node:child_process';
9
9
  import { getAuthStatus, syncAuth } from './auth/service.mjs';
10
10
  import { globalCredentialsPath, readGlobalCredentials, writeGlobalCredentials, writeObsConfig } from './auth/credentials.mjs';
11
+ import { proxyConfigPath, readProxyConfig, writeProxyConfig, clearProxyConfig, getProxySettings } from './proxy/proxy-config.mjs';
11
12
 
12
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
14
  const PLUGIN_ROOT = resolve(__dirname, '..');
@@ -56,6 +57,15 @@ function workbuddySkillsDir() { return join(homedir(), '.workbuddy', 'skills');
56
57
  function workbuddyMcpConfigFile() { return join(homedir(), '.workbuddy', 'mcp.json'); }
57
58
  function workbuddyPluginsDir() { return join(homedir(), '.workbuddy', 'huaweicloud-plugins'); }
58
59
 
60
+ function dshRoot() { return process.env.DSH_HOME || join(homedir(), '.dsh'); }
61
+ function dshSkillsDir() { return join(dshRoot(), 'skills'); }
62
+ function dshProfileDir() { return join(dshRoot(), 'profiles', 'web'); }
63
+ function dshPatchFile() { return join(dshProfileDir(), 'cordis.patch.yml'); }
64
+ function dshPluginsDir() { return join(dshRoot(), 'huaweicloud-plugins'); }
65
+
66
+ const DSH_MCP_PATCH_START = '# HuaweiCloud DevKit DSH integration start';
67
+ const DSH_MCP_PATCH_END = '# HuaweiCloud DevKit DSH integration end';
68
+
59
69
  // Detect CodeArts sandbox mode (bash_mode in permission config).
60
70
  function detectCodeartsSandbox() {
61
71
  try {
@@ -758,6 +768,261 @@ function workbuddyStatus() {
758
768
  }
759
769
  }
760
770
 
771
+ function dshMcpServerPath() {
772
+ return join(dshPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
773
+ }
774
+
775
+ function dshPatchBlock() {
776
+ const hcloudBin = findHcloudBin();
777
+ const envLines = [
778
+ ' HUAWEICLOUD_AGENT_TOOLKIT_MODE: local',
779
+ " HDKITSERVICE_ENDPOINT: ''",
780
+ ];
781
+ if (hcloudBin) {
782
+ envLines.push(` HCLOUD_BIN: '${hcloudBin.replace(/\\/g, '/').replace(/'/g, "''")}'`);
783
+ }
784
+ return [
785
+ DSH_MCP_PATCH_START,
786
+ '- insert:',
787
+ ' - id: mcp-huaweicloud',
788
+ " name: '@deepseek-ai/dsh-mcp-client'",
789
+ ' config:',
790
+ ' serverName: huaweicloud',
791
+ ' transport: stdio',
792
+ ' command: node',
793
+ ' args:',
794
+ ` - '${dshMcpServerPath().replace(/'/g, "''")}'`,
795
+ ' env:',
796
+ ...envLines,
797
+ ' failOnStartupError: false',
798
+ DSH_MCP_PATCH_END,
799
+ ].join('\n');
800
+ }
801
+
802
+ function escapeRegExp(value) {
803
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
804
+ }
805
+
806
+ function removeManagedDshPatchBlock(content) {
807
+ const pattern = new RegExp(`\\n?${escapeRegExp(DSH_MCP_PATCH_START)}[\\s\\S]*?${escapeRegExp(DSH_MCP_PATCH_END)}\\s*`, 'g');
808
+ return String(content || '').replace(pattern, '\n').replace(/\n{3,}/g, '\n\n').trimEnd();
809
+ }
810
+
811
+ function dshPatchHasOnlyCommentsOrEmptyList(content) {
812
+ const meaningful = String(content || '')
813
+ .split(/\r?\n/)
814
+ .map((line) => line.trim())
815
+ .filter((line) => line && !line.startsWith('#'));
816
+ return meaningful.length === 0 || (meaningful.length === 1 && meaningful[0] === '[]');
817
+ }
818
+
819
+ function ensureDshMcpPatch() {
820
+ const patchFile = dshPatchFile();
821
+ const existing = existsSync(patchFile) ? readFileSync(patchFile, 'utf8') : '';
822
+ const cleaned = removeManagedDshPatchBlock(existing);
823
+ const block = dshPatchBlock();
824
+ let next;
825
+ if (dshPatchHasOnlyCommentsOrEmptyList(cleaned)) {
826
+ const prefix = cleaned
827
+ .split(/\r?\n/)
828
+ .filter((line) => line.trim() !== '[]')
829
+ .join('\n')
830
+ .trimEnd();
831
+ next = `${prefix ? `${prefix}\n` : ''}${block}\n`;
832
+ } else {
833
+ next = `${cleaned}\n\n${block}\n`;
834
+ }
835
+ if (existing.replace(/\r\n/g, '\n') === next) {
836
+ console.log(` DSH patch unchanged: ${patchFile}`);
837
+ return false;
838
+ }
839
+ mkdirSync(dirname(patchFile), { recursive: true });
840
+ writeFileSync(patchFile, next);
841
+ console.log(` DSH patch updated: ${patchFile}`);
842
+ return true;
843
+ }
844
+
845
+ function removeDshMcpPatch() {
846
+ const patchFile = dshPatchFile();
847
+ if (!existsSync(patchFile)) return false;
848
+ const existing = readFileSync(patchFile, 'utf8');
849
+ const cleaned = removeManagedDshPatchBlock(existing);
850
+ if (cleaned === existing.trimEnd()) return false;
851
+ const prefix = cleaned
852
+ .split(/\r?\n/)
853
+ .filter((line) => line.trim() !== '[]')
854
+ .join('\n')
855
+ .trimEnd();
856
+ const next = dshPatchHasOnlyCommentsOrEmptyList(cleaned)
857
+ ? `${prefix ? `${prefix}\n` : ''}[]\n`
858
+ : `${cleaned}\n`;
859
+ writeFileSync(patchFile, next);
860
+ console.log(` DSH patch cleaned: ${patchFile}`);
861
+ return true;
862
+ }
863
+
864
+ function dshPatchConfigured() {
865
+ const patchFile = dshPatchFile();
866
+ if (!existsSync(patchFile)) return false;
867
+ try {
868
+ const patch = readFileSync(patchFile, 'utf8');
869
+ return patch.includes('id: mcp-huaweicloud')
870
+ && patch.includes("@deepseek-ai/dsh-mcp-client")
871
+ && patch.includes('serverName: huaweicloud');
872
+ } catch {
873
+ return false;
874
+ }
875
+ }
876
+
877
+ function commandAvailable(command, args = ['--version']) {
878
+ try {
879
+ const r = spawnSync(command, args, { shell: false, windowsHide: true, stdio: 'pipe', timeout: 10000 });
880
+ if (r.status === 0) return true;
881
+ } catch {}
882
+ if (process.platform === 'win32') {
883
+ try {
884
+ const w = spawnSync('where.exe', [command], { windowsHide: true, stdio: 'pipe', timeout: 10000 });
885
+ return w.status === 0 && w.stdout.toString().trim().length > 0;
886
+ } catch {}
887
+ }
888
+ return false;
889
+ }
890
+
891
+ function dshMcpClientAvailable() {
892
+ const modulePath = join('node_modules', '@deepseek-ai', 'dsh-mcp-client', 'package.json');
893
+ const candidates = [
894
+ join(dshProfileDir(), modulePath),
895
+ join(dshRoot(), 'profiles', modulePath),
896
+ join(dshRoot(), modulePath),
897
+ ];
898
+ if (candidates.some((p) => existsSync(p))) return true;
899
+ const pkgPath = join(dshProfileDir(), 'package.json');
900
+ if (!existsSync(pkgPath)) return false;
901
+ try {
902
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
903
+ return Boolean(pkg.dependencies?.['@deepseek-ai/dsh-mcp-client']
904
+ || pkg.devDependencies?.['@deepseek-ai/dsh-mcp-client']);
905
+ } catch {
906
+ return false;
907
+ }
908
+ }
909
+
910
+ function tryInstallDshMcpClient() {
911
+ if (process.env.HUAWEICLOUD_DEVKIT_SKIP_DSH_PLUGIN_INSTALL === '1') {
912
+ console.log(' DSH MCP client install skipped by environment');
913
+ return false;
914
+ }
915
+ if (dshMcpClientAvailable()) {
916
+ console.log(' DSH MCP client package detected');
917
+ return true;
918
+ }
919
+ if (commandAvailable('dsh')) {
920
+ const r = spawnSync('dsh', ['plugin', '--profile', 'web', 'add', '@deepseek-ai/dsh-mcp-client'], {
921
+ env: { ...process.env, DSH_HOME: dshRoot() },
922
+ windowsHide: true,
923
+ stdio: 'pipe',
924
+ timeout: 60000,
925
+ });
926
+ if (r.status === 0) {
927
+ console.log(' DSH MCP client package installed via dsh');
928
+ return true;
929
+ }
930
+ const err = `${r.stderr || ''}${r.stdout || ''}`.trim().split(/\r?\n/).slice(-2).join(' ');
931
+ console.log(` \x1b[33m[WARN]\x1b[0m DSH MCP client auto-install failed${err ? `: ${err}` : ''}`);
932
+ }
933
+ if (commandAvailable('pnpm') && existsSync(join(dshProfileDir(), 'package.json'))) {
934
+ const r = spawnSync('pnpm', ['--dir', dshProfileDir(), 'add', '@deepseek-ai/dsh-mcp-client'], {
935
+ windowsHide: true,
936
+ stdio: 'pipe',
937
+ timeout: 60000,
938
+ });
939
+ if (r.status === 0) {
940
+ console.log(' DSH MCP client package installed via pnpm');
941
+ return true;
942
+ }
943
+ }
944
+ console.log(' \x1b[33m[WARN]\x1b[0m DSH MCP client package not detected.');
945
+ console.log(' Manual: npx @deepseek-ai/dsh plugin --profile web add @deepseek-ai/dsh-mcp-client');
946
+ console.log(' If pnpm is missing, run: corepack enable pnpm');
947
+ return false;
948
+ }
949
+
950
+ async function installDsh() {
951
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
952
+ const srcDir = join(PLUGIN_ROOT, 'src');
953
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
954
+ const pluginDest = dshPluginsDir();
955
+
956
+ copyDir(skillsSrc, dshSkillsDir());
957
+ console.log(` Skills -> ${dshSkillsDir()}`);
958
+ copyDir(srcDir, join(pluginDest, 'src'));
959
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
960
+ copyDir(safetyDir, join(pluginDest, 'safety'));
961
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
962
+ ensureDshMcpPatch();
963
+ tryInstallDshMcpClient();
964
+ mkdirSync(pluginDest, { recursive: true });
965
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
966
+ }
967
+
968
+ async function updateDsh() {
969
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
970
+ const srcDir = join(PLUGIN_ROOT, 'src');
971
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
972
+ const pluginDest = dshPluginsDir();
973
+
974
+ copyDir(skillsSrc, dshSkillsDir());
975
+ const stale = pruneStale(dshSkillsDir(), skillsSrc);
976
+ console.log(` Skills updated -> ${dshSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
977
+ copyDir(srcDir, join(pluginDest, 'src'));
978
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
979
+ copyDir(safetyDir, join(pluginDest, 'safety'));
980
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
981
+ ensureDshMcpPatch();
982
+ tryInstallDshMcpClient();
983
+ mkdirSync(pluginDest, { recursive: true });
984
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
985
+ }
986
+
987
+ function uninstallDsh() {
988
+ const skillsDir = dshSkillsDir();
989
+ let removed = 0;
990
+ if (existsSync(skillsDir)) {
991
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
992
+ if (entry.name.startsWith('huawei')) {
993
+ removeIfExists(join(skillsDir, entry.name));
994
+ removed++;
995
+ }
996
+ }
997
+ if (removed > 0) console.log(` Removed ${removed} skills`);
998
+ try {
999
+ if (readdirSync(skillsDir).length === 0) {
1000
+ rmSync(skillsDir, { recursive: true, force: true });
1001
+ console.log(` Removed empty skills directory: ${skillsDir}`);
1002
+ }
1003
+ } catch {}
1004
+ }
1005
+ if (removeIfExists(dshPluginsDir())) {
1006
+ console.log(' Removed MCP server and safety policy');
1007
+ }
1008
+ removeDshMcpPatch();
1009
+ }
1010
+
1011
+ function dshStatus() {
1012
+ const pluginDir = dshPluginsDir();
1013
+ const skillsDir = dshSkillsDir();
1014
+ console.log(` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
1015
+ console.log(` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
1016
+ let skillCount = 0;
1017
+ if (existsSync(skillsDir)) {
1018
+ skillCount = readdirSync(skillsDir, { withFileTypes: true })
1019
+ .filter((d) => d.isDirectory() && d.name.startsWith('huawei')).length;
1020
+ }
1021
+ console.log(` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`);
1022
+ console.log(` DSH patch: ${dshPatchConfigured() ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1023
+ console.log(` DSH MCP client package: ${dshMcpClientAvailable() ? '\x1b[32mDetected\x1b[0m' : '\x1b[33mCheck DSH profile\x1b[0m'}`);
1024
+ }
1025
+
761
1026
  function opencodeStatus() {
762
1027
  const pluginDir = opencodePluginsDir();
763
1028
  const skillsDir = opencodeSkillsDir();
@@ -788,6 +1053,7 @@ function parseTarget() {
788
1053
  if (val === 'codex-desktop') return 'codex-desktop';
789
1054
  if (val === 'codearts') return 'codearts';
790
1055
  if (val === 'workbuddy') return 'workbuddy';
1056
+ if (val === 'dsh') return 'dsh';
791
1057
  if (val === 'all') return 'all';
792
1058
  return 'opencode';
793
1059
  }
@@ -814,6 +1080,10 @@ async function cmdInstall() {
814
1080
  console.log('\n[WorkBuddy]');
815
1081
  await installWorkBuddy();
816
1082
  }
1083
+ if (target === 'dsh' || target === 'all') {
1084
+ console.log('\n[DSH]');
1085
+ await installDsh();
1086
+ }
817
1087
  if (target === 'codex' || target === 'all') {
818
1088
  console.log('\n[Codex]');
819
1089
  if (!hasCodexCLI()) {
@@ -836,11 +1106,13 @@ async function cmdInstall() {
836
1106
  } else {
837
1107
  installCodex();
838
1108
  }
839
- } console.log(`\n\x1b[32mInstallation complete!\x1b[0m`);
1109
+ }
1110
+ console.log(`\n\x1b[32mInstallation complete!\x1b[0m`);
840
1111
  const appName = target === 'codearts' ? 'CodeArts'
841
1112
  : target === 'codex-desktop' ? 'Codex Desktop'
842
1113
  : target === 'codex' ? 'Codex'
843
1114
  : target === 'workbuddy' ? 'WorkBuddy'
1115
+ : target === 'dsh' ? 'DSH'
844
1116
  : 'OpenCode';
845
1117
  const pad = ' '.repeat(24 - appName.length);
846
1118
  console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
@@ -863,7 +1135,8 @@ async function cmdInstall() {
863
1135
  console.log(` 3. 运行自检:npx huaweicloud-devkit doctor`);
864
1136
 
865
1137
  // Write install marker for doctor to detect
866
- const markerDir = target === 'codearts' ? codeartsPluginsDir()
1138
+ const markerDir = target === 'dsh' ? dshPluginsDir()
1139
+ : target === 'codearts' ? codeartsPluginsDir()
867
1140
  : target === 'workbuddy' ? workbuddyPluginsDir()
868
1141
  : target === 'codex-desktop' ? codexDesktopPluginsDir()
869
1142
  : opencodePluginsDir();
@@ -881,6 +1154,9 @@ async function cmdInstall() {
881
1154
  if (target === 'workbuddy' || target === 'all') {
882
1155
  console.log('Or describe your Huawei Cloud task in WorkBuddy');
883
1156
  }
1157
+ if (target === 'dsh' || target === 'all') {
1158
+ console.log('Or describe your Huawei Cloud task in DSH');
1159
+ }
884
1160
  }
885
1161
 
886
1162
  async function cmdUninstall() {
@@ -900,6 +1176,10 @@ async function cmdUninstall() {
900
1176
  console.log('\n[WorkBuddy]');
901
1177
  uninstallWorkBuddy();
902
1178
  }
1179
+ if (target === 'dsh' || target === 'all') {
1180
+ console.log('\n[DSH]');
1181
+ uninstallDsh();
1182
+ }
903
1183
  if (target === 'codex-desktop' || target === 'codex' || target === 'all') {
904
1184
  console.log('\n[Codex]');
905
1185
  uninstallCodexDesktop();
@@ -944,6 +1224,10 @@ async function cmdStatus() {
944
1224
  console.log('\n[WorkBuddy]');
945
1225
  workbuddyStatus();
946
1226
  }
1227
+ if (target === 'dsh' || target === 'all') {
1228
+ console.log('\n[DSH]');
1229
+ dshStatus();
1230
+ }
947
1231
  if (target === 'codex' || target === 'all') {
948
1232
  console.log('\n[Codex]');
949
1233
  if (!hasCodexCLI()) {
@@ -971,16 +1255,22 @@ async function cmdDoctor() {
971
1255
  // Node.js
972
1256
  check('Node.js >= 20', process.versions.node.split('.')[0] >= 20, 'Run: nvm install 20 && nvm use 20');
973
1257
 
974
- // MCP server — check OpenCode, Codex Desktop, CodeArts, and WorkBuddy paths
1258
+ // MCP server — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH paths
975
1259
  const opencodePluginDir = opencodePluginsDir();
976
1260
  const codexPluginDir = codexDesktopPluginsDir();
1261
+ const codeartsPluginDir = codeartsPluginsDir();
977
1262
  const workbuddyPluginDir = workbuddyPluginsDir();
1263
+ const dshPluginDir = dshPluginsDir();
978
1264
  const mcpOk = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs'))
979
1265
  || existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs'))
980
- || existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'));
1266
+ || existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs'))
1267
+ || existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'))
1268
+ || existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs'));
981
1269
  const mcpTarget = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ? 'OpenCode'
982
1270
  : existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ? 'Codex Desktop'
983
- : existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ? 'WorkBuddy' : '';
1271
+ : existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs')) ? 'CodeArts'
1272
+ : existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ? 'WorkBuddy'
1273
+ : existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs')) ? 'DSH' : '';
984
1274
  check('MCP server installed', mcpOk, 'Run: npx huaweicloud-devkit install');
985
1275
 
986
1276
  if (mcpOk) {
@@ -989,10 +1279,12 @@ async function cmdDoctor() {
989
1279
 
990
1280
  const safetyOk = existsSync(join(opencodePluginDir, 'safety', 'policy.json'))
991
1281
  || existsSync(join(codexPluginDir, 'safety', 'policy.json'))
992
- || existsSync(join(workbuddyPluginDir, 'safety', 'policy.json'));
1282
+ || existsSync(join(codeartsPluginDir, 'safety', 'policy.json'))
1283
+ || existsSync(join(workbuddyPluginDir, 'safety', 'policy.json'))
1284
+ || existsSync(join(dshPluginDir, 'safety', 'policy.json'));
993
1285
  check('Safety policy installed', safetyOk, 'Run: npx huaweicloud-devkit install');
994
1286
 
995
- // MCP config — check OpenCode, Codex Desktop, and WorkBuddy
1287
+ // MCP config — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH
996
1288
  let mcpConfigured = false;
997
1289
  let mcpCfgTarget = '';
998
1290
  const opencodeCfg = opencodeConfigFile();
@@ -1009,6 +1301,13 @@ async function cmdDoctor() {
1009
1301
  if (cfg.includes('[mcp_servers.huaweicloud-devkit]')) { mcpConfigured = true; mcpCfgTarget = 'Codex Desktop'; }
1010
1302
  } catch {}
1011
1303
  }
1304
+ const codeartsCfg = codeartsMcpSettingsFile();
1305
+ if (!mcpConfigured && existsSync(codeartsCfg)) {
1306
+ try {
1307
+ const cfg = JSON.parse(readFileSync(codeartsCfg, 'utf8'));
1308
+ if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) { mcpConfigured = true; mcpCfgTarget = 'CodeArts'; }
1309
+ } catch {}
1310
+ }
1012
1311
  const workbuddyCfg = workbuddyMcpConfigFile();
1013
1312
  if (!mcpConfigured && existsSync(workbuddyCfg)) {
1014
1313
  try {
@@ -1016,6 +1315,10 @@ async function cmdDoctor() {
1016
1315
  if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) { mcpConfigured = true; mcpCfgTarget = 'WorkBuddy'; }
1017
1316
  } catch {}
1018
1317
  }
1318
+ if (!mcpConfigured && dshPatchConfigured()) {
1319
+ mcpConfigured = true;
1320
+ mcpCfgTarget = 'DSH';
1321
+ }
1019
1322
  check('MCP configured', mcpConfigured, mcpCfgTarget ? `Found in ${mcpCfgTarget} config` : 'Run: npx huaweicloud-devkit install');
1020
1323
 
1021
1324
  // hcloud CLI
@@ -1045,7 +1348,7 @@ async function cmdDoctor() {
1045
1348
  }
1046
1349
 
1047
1350
  // Skills
1048
- const skillsOptions = [opencodeSkillsDir(), codexDesktopSkillsDir(), codeartsSkillsDir(), workbuddySkillsDir()];
1351
+ const skillsOptions = [opencodeSkillsDir(), codexDesktopSkillsDir(), codeartsSkillsDir(), workbuddySkillsDir(), dshSkillsDir()];
1049
1352
  let skillCount = 0, skillsDir = '', missingSkills = [];
1050
1353
  for (const dir of skillsOptions) {
1051
1354
  if (!existsSync(dir)) continue;
@@ -1064,6 +1367,14 @@ async function cmdDoctor() {
1064
1367
  warn++;
1065
1368
  }
1066
1369
 
1370
+ const proxyConfig = readProxyConfig();
1371
+ const proxyEnv = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
1372
+ if (proxyConfig || proxyEnv) {
1373
+ const source = proxyEnv ? 'env' : 'file';
1374
+ const proxyUrl = proxyEnv || proxyConfig.https_proxy || proxyConfig.http_proxy;
1375
+ console.log(` \x1b[36m[INFO]\x1b[0m Proxy configured (${source}): ${proxyUrl}`);
1376
+ }
1377
+
1067
1378
  console.log(`\nResults: ${pass} pass, ${warn} warn, ${fail} fail`);
1068
1379
 
1069
1380
  if (mcpConfigured && !hcloudOk) {
@@ -1082,6 +1393,7 @@ async function cmdDoctor() {
1082
1393
  { path: join(codexDesktopPluginsDir(), '.installed'), name: 'Codex Desktop' },
1083
1394
  { path: join(workbuddyPluginsDir(), '.installed'), name: 'WorkBuddy' },
1084
1395
  { path: join(codeartsPluginsDir(), '.installed'), name: 'CodeArts' },
1396
+ { path: join(dshPluginsDir(), '.installed'), name: 'DSH' },
1085
1397
  ];
1086
1398
  for (const marker of installedMarkers) {
1087
1399
  if (existsSync(marker.path)) {
@@ -1163,6 +1475,18 @@ async function cmdUpdate() {
1163
1475
  return;
1164
1476
  }
1165
1477
 
1478
+ if (target === 'dsh') {
1479
+ if (!existsSync(join(dshPluginsDir(), 'src', 'mcp-server.mjs'))) {
1480
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
1481
+ return;
1482
+ }
1483
+ console.log('[DSH]');
1484
+ await updateDsh();
1485
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
1486
+ console.log(`\x1b[33mRestart the DSH session for changes to take effect.\x1b[0m`);
1487
+ return;
1488
+ }
1489
+
1166
1490
  if (target === 'all') {
1167
1491
  let updatedAny = false;
1168
1492
  if (existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
@@ -1185,6 +1509,11 @@ async function cmdUpdate() {
1185
1509
  await updateWorkBuddy();
1186
1510
  updatedAny = true;
1187
1511
  }
1512
+ if (existsSync(join(dshPluginsDir(), 'src', 'mcp-server.mjs'))) {
1513
+ console.log('\n[DSH]');
1514
+ await updateDsh();
1515
+ updatedAny = true;
1516
+ }
1188
1517
  if (codexStatus()) {
1189
1518
  console.log('\n[Codex]');
1190
1519
  installCodex();
@@ -1524,6 +1853,93 @@ async function cmdAuth() {
1524
1853
  return cmdAuthStatus();
1525
1854
  }
1526
1855
 
1856
+ async function cmdProxyInit() {
1857
+ console.log(BANNER);
1858
+ console.log('HuaweiCloud DevKit Proxy Configuration\n');
1859
+ console.log('Configure HTTP/HTTPS proxy for connections to Huawei Cloud services.');
1860
+ console.log('Proxy settings are saved to ~/.config/huaweicloud/proxy.json\n');
1861
+
1862
+ const existing = readProxyConfig() || {};
1863
+ const interactive = process.stdin.isTTY && process.stdout.isTTY;
1864
+
1865
+ if (!interactive) {
1866
+ console.error('\x1b[31mNon-interactive session. Set proxy via environment variables:\x1b[0m');
1867
+ console.error(' HTTPS_PROXY=http://proxy:port');
1868
+ console.error(' HTTP_PROXY=http://proxy:port');
1869
+ console.error(' NO_PROXY=localhost,127.0.0.1');
1870
+ console.error('\nOr run "npx huaweicloud-devkit proxy init" in a real terminal.');
1871
+ process.exitCode = 1;
1872
+ return;
1873
+ }
1874
+
1875
+ const httpsProxy = await readLineQuestion(`HTTPS proxy [${existing.https_proxy || 'none'}]: `);
1876
+ const httpProxy = await readLineQuestion(`HTTP proxy [${existing.http_proxy || 'none'}]: `);
1877
+ const noProxy = await readLineQuestion(`NO_PROXY hosts [${existing.no_proxy || 'localhost,127.0.0.1'}]: `);
1878
+
1879
+ const config = {
1880
+ https_proxy: httpsProxy || existing.https_proxy || '',
1881
+ http_proxy: httpProxy || existing.http_proxy || '',
1882
+ no_proxy: noProxy || existing.no_proxy || 'localhost,127.0.0.1',
1883
+ };
1884
+
1885
+ const path = writeProxyConfig(config);
1886
+ console.log(`\nProxy configuration saved to ${path}`);
1887
+ console.log('\nEffective settings:');
1888
+ console.log(` HTTPS_PROXY: ${config.https_proxy || '(none)'}`);
1889
+ console.log(` HTTP_PROXY: ${config.http_proxy || '(none)'}`);
1890
+ console.log(` NO_PROXY: ${config.no_proxy || '(none)'}`);
1891
+ console.log('\nEnvironment variables (HTTPS_PROXY, HTTP_PROXY, NO_PROXY) override file settings.');
1892
+ }
1893
+
1894
+ async function cmdProxyShow() {
1895
+ console.log(BANNER);
1896
+ console.log('HuaweiCloud DevKit Proxy Configuration\n');
1897
+
1898
+ const config = readProxyConfig();
1899
+ const configPath = proxyConfigPath();
1900
+
1901
+ console.log(`Config file: ${configPath}`);
1902
+ console.log(`File exists: ${config ? 'yes' : 'no'}\n`);
1903
+
1904
+ if (config) {
1905
+ console.log('File settings:');
1906
+ console.log(` https_proxy: ${config.https_proxy || '(empty)'}`);
1907
+ console.log(` http_proxy: ${config.http_proxy || '(empty)'}`);
1908
+ console.log(` no_proxy: ${config.no_proxy || '(empty)'}`);
1909
+ }
1910
+
1911
+ console.log('\nEnvironment variables:');
1912
+ console.log(` HTTPS_PROXY: ${process.env.HTTPS_PROXY || process.env.https_proxy || '(not set)'}`);
1913
+ console.log(` HTTP_PROXY: ${process.env.HTTP_PROXY || process.env.http_proxy || '(not set)'}`);
1914
+ console.log(` NO_PROXY: ${process.env.NO_PROXY || process.env.no_proxy || '(not set)'}`);
1915
+
1916
+ const effective = getProxySettings();
1917
+ console.log('\nEffective (env > file):');
1918
+ if (effective) {
1919
+ console.log(` https_proxy: ${effective.https_proxy || '(none)'}`);
1920
+ console.log(` http_proxy: ${effective.http_proxy || '(none)'}`);
1921
+ console.log(` no_proxy: ${effective.no_proxy || '(none)'}`);
1922
+ } else {
1923
+ console.log(' (no proxy configured)');
1924
+ }
1925
+ }
1926
+
1927
+ async function cmdProxyClear() {
1928
+ const removed = clearProxyConfig();
1929
+ if (removed) {
1930
+ console.log('Proxy configuration removed.');
1931
+ } else {
1932
+ console.log('No proxy configuration file found.');
1933
+ }
1934
+ }
1935
+
1936
+ async function cmdProxy() {
1937
+ const sub = (process.argv[3] || 'show').toLowerCase();
1938
+ if (sub === 'init' || sub === 'setup') return cmdProxyInit();
1939
+ if (sub === 'clear' || sub === 'remove' || sub === 'reset') return cmdProxyClear();
1940
+ return cmdProxyShow();
1941
+ }
1942
+
1527
1943
  async function main() {
1528
1944
  const cmd = process.argv[2] || 'help';
1529
1945
 
@@ -1557,12 +1973,15 @@ async function main() {
1557
1973
  case 'auth':
1558
1974
  await cmdAuth();
1559
1975
  break;
1976
+ case 'proxy':
1977
+ await cmdProxy();
1978
+ break;
1560
1979
  case 'help':
1561
1980
  case '--help':
1562
1981
  case '-h':
1563
1982
  default:
1564
1983
  console.log(BANNER);
1565
- console.log('Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|all>]\n');
1984
+ console.log('Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|all>]\n');
1566
1985
  console.log('Commands:');
1567
1986
  console.log(' install Install skills, MCP server, safety policy');
1568
1987
  console.log(' uninstall Remove installed files');
@@ -1572,18 +1991,22 @@ async function main() {
1572
1991
  console.log(' doctor Self-check: hcloud, MCP, skills, auth');
1573
1992
  console.log(' install-hcloud Show KooCLI install commands for your OS');
1574
1993
  console.log(' auth Manage unified auth: init | sync | status');
1994
+ console.log(' proxy Manage proxy config: init | show | clear');
1575
1995
  console.log(' help Show this help');
1576
1996
  console.log('\nOptions:');
1577
- console.log(' --target Target agent: opencode (default), codex, codearts, workbuddy, all');
1997
+ console.log(' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, all');
1578
1998
  console.log('\nExamples:');
1579
1999
  console.log(' npx huaweicloud-devkit install');
1580
2000
  console.log(' npx huaweicloud-devkit install --target codex');
1581
2001
  console.log(' npx huaweicloud-devkit install --target codearts');
1582
2002
  console.log(' npx huaweicloud-devkit install --target workbuddy');
2003
+ console.log(' npx huaweicloud-devkit install --target dsh');
1583
2004
  console.log(' npx huaweicloud-devkit install --target all');
1584
2005
  console.log(' npx huaweicloud-devkit auth init');
1585
2006
  console.log(' npx huaweicloud-devkit auth sync --target all');
1586
2007
  console.log(' npx huaweicloud-devkit auth status --target all');
2008
+ console.log(' npx huaweicloud-devkit proxy init');
2009
+ console.log(' npx huaweicloud-devkit proxy show');
1587
2010
  break;
1588
2011
  }
1589
2012
  }