huaweicloud-devkit 1.0.2-dev.1 → 1.0.2-dev.12
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 +27 -3
- package/README.zh-CN.md +27 -3
- package/bin/setup.cjs +0 -0
- package/package.json +1 -1
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.mcp.json +2 -1
- package/plugins/huaweicloud-core/safety/policy.json +15 -1
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +21 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +2 -2
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +6 -0
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +135 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +4 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +2 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +20 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +1 -1
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +87 -0
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +95 -0
- package/plugins/huaweicloud-core/src/auth/service.mjs +63 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +11 -0
- package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +155 -0
- package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +87 -0
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +91 -0
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +162 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +112 -0
- package/plugins/huaweicloud-core/src/search-market.mjs +7 -2
- package/plugins/huaweicloud-core/src/setup-cli.mjs +662 -71
- package/plugins/huaweicloud-core/src/tools.mjs +170 -4
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +427 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +132 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +227 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +202 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +158 -0
- package/plugins/huaweicloud-core/src/ws-exec/index.js +19 -0
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +338 -0
|
@@ -6,6 +6,9 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { homedir, platform } from 'node:os';
|
|
7
7
|
import { createInterface } from 'node:readline';
|
|
8
8
|
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { getAuthStatus, syncAuth } from './auth/service.mjs';
|
|
10
|
+
import { globalCredentialsPath, readGlobalCredentials, writeGlobalCredentials, writeObsConfig } from './auth/credentials.mjs';
|
|
11
|
+
import { proxyConfigPath, readProxyConfig, writeProxyConfig, clearProxyConfig, getProxySettings } from './proxy/proxy-config.mjs';
|
|
9
12
|
|
|
10
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
14
|
const PLUGIN_ROOT = resolve(__dirname, '..');
|
|
@@ -127,11 +130,21 @@ function removeIfExists(p) {
|
|
|
127
130
|
|
|
128
131
|
function updateOpenCodeConfig(pluginDir) {
|
|
129
132
|
const configPath = opencodeConfigFile();
|
|
133
|
+
const mcpPath = join(pluginDir, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
130
134
|
let config = {};
|
|
131
135
|
if (existsSync(configPath)) {
|
|
132
|
-
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
|
|
136
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
|
|
137
|
+
console.log(` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath} (jsonc comments?). Skipping MCP config write; ensure "mcp.huaweicloud-devkit" points to ${mcpPath}.`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const existing = config.mcp?.['huaweicloud-devkit'];
|
|
141
|
+
if (existing && existing.type === 'local'
|
|
142
|
+
&& Array.isArray(existing.command) && existing.command[0] === 'node'
|
|
143
|
+
&& existing.command[1] === mcpPath) {
|
|
144
|
+
console.log(` OpenCode MCP config unchanged: ${configPath}`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
133
147
|
}
|
|
134
|
-
const mcpPath = join(pluginDir, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
135
148
|
config.mcp = config.mcp || {};
|
|
136
149
|
config.mcp['huaweicloud-devkit'] = {
|
|
137
150
|
type: 'local',
|
|
@@ -296,6 +309,101 @@ function uninstallOpenCode() {
|
|
|
296
309
|
removeOpenCodeConfig();
|
|
297
310
|
}
|
|
298
311
|
|
|
312
|
+
// Remove huawei* entries in targetDir that no longer exist in sourceDir (stale files from an older version).
|
|
313
|
+
function pruneStale(targetDir, sourceDir) {
|
|
314
|
+
if (!existsSync(targetDir) || !existsSync(sourceDir)) return 0;
|
|
315
|
+
const sourceNames = new Set(readdirSync(sourceDir));
|
|
316
|
+
let removed = 0;
|
|
317
|
+
for (const entry of readdirSync(targetDir, { withFileTypes: true })) {
|
|
318
|
+
if (!entry.name.startsWith('huawei')) continue;
|
|
319
|
+
if (!sourceNames.has(entry.name)) {
|
|
320
|
+
removeIfExists(join(targetDir, entry.name));
|
|
321
|
+
removed++;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return removed;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
328
|
+
async function updateOpenCode() {
|
|
329
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
330
|
+
const commandsSrc = join(PACKAGE_ROOT, 'integrations', 'opencode', 'commands');
|
|
331
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
332
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
333
|
+
const pluginDest = opencodePluginsDir();
|
|
334
|
+
|
|
335
|
+
copyDir(skillsSrc, opencodeSkillsDir());
|
|
336
|
+
const staleSkills = pruneStale(opencodeSkillsDir(), skillsSrc);
|
|
337
|
+
console.log(` Skills updated -> ${opencodeSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`);
|
|
338
|
+
copyDir(commandsSrc, opencodeCommandsDir());
|
|
339
|
+
const staleCommands = pruneStale(opencodeCommandsDir(), commandsSrc);
|
|
340
|
+
console.log(` Commands updated -> ${opencodeCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`);
|
|
341
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
342
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
343
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
344
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
345
|
+
updateOpenCodeConfig(pluginDest);
|
|
346
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
347
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function codexMcpServerPath() {
|
|
351
|
+
return join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function codexConfigSectionText(mcpPath) {
|
|
355
|
+
return [
|
|
356
|
+
'[mcp_servers.huaweicloud-devkit]',
|
|
357
|
+
'command = "node"',
|
|
358
|
+
`args = ["${mcpPath}"]`,
|
|
359
|
+
'',
|
|
360
|
+
'[mcp_servers.huaweicloud-devkit.env]',
|
|
361
|
+
'HUAWEICLOUD_AGENT_TOOLKIT_MODE = "local"',
|
|
362
|
+
'',
|
|
363
|
+
].join('\n');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Returns true when the config file was written, false when it was already correct.
|
|
367
|
+
function ensureCodexConfigSection(mcpPath) {
|
|
368
|
+
const configPath = codexConfigToml();
|
|
369
|
+
let existing = '';
|
|
370
|
+
if (existsSync(configPath)) {
|
|
371
|
+
try { existing = readFileSync(configPath, 'utf8'); } catch {}
|
|
372
|
+
if (existing.includes('[mcp_servers.huaweicloud-devkit]')) {
|
|
373
|
+
if (existing.includes(`args = ["${mcpPath}"]`)) {
|
|
374
|
+
console.log(` Config unchanged: ${configPath}`);
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
removeCodexConfigSection();
|
|
378
|
+
existing = '';
|
|
379
|
+
if (existsSync(configPath)) {
|
|
380
|
+
try { existing = readFileSync(configPath, 'utf8'); } catch {}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
385
|
+
if (existing && !existing.endsWith('\n')) existing += '\n';
|
|
386
|
+
writeFileSync(configPath, existing + codexConfigSectionText(mcpPath));
|
|
387
|
+
console.log(` Config updated: ${configPath}`);
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function removeCodexConfigSection() {
|
|
392
|
+
const configPath = codexConfigToml();
|
|
393
|
+
if (!existsSync(configPath)) return;
|
|
394
|
+
const lines = readFileSync(configPath, 'utf8').split(/\r?\n/);
|
|
395
|
+
const out = [];
|
|
396
|
+
let skip = false;
|
|
397
|
+
for (const line of lines) {
|
|
398
|
+
if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) { skip = true; continue; }
|
|
399
|
+
if (skip && line.startsWith('[')) skip = false;
|
|
400
|
+
if (!skip) out.push(line);
|
|
401
|
+
}
|
|
402
|
+
while (out.length > 0 && out[out.length - 1].trim() === '') out.pop();
|
|
403
|
+
writeFileSync(configPath, out.join('\n') + (out.length > 0 ? '\n' : ''));
|
|
404
|
+
console.log(' Config cleaned');
|
|
405
|
+
}
|
|
406
|
+
|
|
299
407
|
async function installCodexDesktop() {
|
|
300
408
|
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
301
409
|
const commandsSrc = join(PACKAGE_ROOT, 'integrations', 'opencode', 'commands');
|
|
@@ -313,7 +421,7 @@ async function installCodexDesktop() {
|
|
|
313
421
|
console.log(` Safety Policy -> ${join(codexDesktopPluginsDir(), 'safety')}`);
|
|
314
422
|
|
|
315
423
|
// Generate .mcp.json with absolute paths for Codex Desktop MCP server discovery
|
|
316
|
-
const mcpServerAbsPath =
|
|
424
|
+
const mcpServerAbsPath = codexMcpServerPath();
|
|
317
425
|
const mcpConfig = {
|
|
318
426
|
mcpServers: {
|
|
319
427
|
'huaweicloud-devkit': {
|
|
@@ -333,29 +441,50 @@ async function installCodexDesktop() {
|
|
|
333
441
|
console.log(` Plugin Manifest -> ${join(codexDesktopPluginsDir(), '.codex-plugin')}`);
|
|
334
442
|
}
|
|
335
443
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
444
|
+
ensureCodexConfigSection(mcpServerAbsPath);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
448
|
+
async function updateCodexDesktop() {
|
|
449
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
450
|
+
const commandsSrc = join(PACKAGE_ROOT, 'integrations', 'opencode', 'commands');
|
|
451
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
452
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
453
|
+
const pluginDest = codexDesktopPluginsDir();
|
|
454
|
+
|
|
455
|
+
copyDir(skillsSrc, codexDesktopSkillsDir());
|
|
456
|
+
const staleSkills = pruneStale(codexDesktopSkillsDir(), skillsSrc);
|
|
457
|
+
console.log(` Skills updated -> ${codexDesktopSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`);
|
|
458
|
+
copyDir(commandsSrc, codexDesktopCommandsDir());
|
|
459
|
+
const staleCommands = pruneStale(codexDesktopCommandsDir(), commandsSrc);
|
|
460
|
+
console.log(` Commands updated -> ${codexDesktopCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`);
|
|
461
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
462
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
463
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
464
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
465
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
466
|
+
|
|
467
|
+
const mcpServerAbsPath = codexMcpServerPath();
|
|
468
|
+
const mcpConfig = {
|
|
469
|
+
mcpServers: {
|
|
470
|
+
'huaweicloud-devkit': {
|
|
471
|
+
command: 'node',
|
|
472
|
+
args: [mcpServerAbsPath],
|
|
473
|
+
env: { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' },
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
writeFileSync(join(pluginDest, '.mcp.json'), JSON.stringify(mcpConfig, null, 2));
|
|
478
|
+
console.log(` MCP Config updated -> ${join(pluginDest, '.mcp.json')}`);
|
|
479
|
+
|
|
480
|
+
const codexPluginSrc = join(PLUGIN_ROOT, '.codex-plugin');
|
|
481
|
+
if (existsSync(codexPluginSrc)) {
|
|
482
|
+
copyDir(codexPluginSrc, join(pluginDest, '.codex-plugin'));
|
|
483
|
+
console.log(` Plugin Manifest updated -> ${join(pluginDest, '.codex-plugin')}`);
|
|
354
484
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
writeFileSync(
|
|
358
|
-
console.log(` Config updated: ${configPath}`);
|
|
485
|
+
|
|
486
|
+
ensureCodexConfigSection(mcpServerAbsPath);
|
|
487
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
359
488
|
}
|
|
360
489
|
|
|
361
490
|
function uninstallCodexDesktop() {
|
|
@@ -386,32 +515,28 @@ function uninstallCodexDesktop() {
|
|
|
386
515
|
if (removeIfExists(codexDesktopPluginsDir())) {
|
|
387
516
|
console.log(' Removed MCP server and safety policy');
|
|
388
517
|
}
|
|
389
|
-
|
|
390
|
-
if (existsSync(configPath)) {
|
|
391
|
-
const lines = readFileSync(configPath, 'utf8').split(/\r?\n/);
|
|
392
|
-
const out = [];
|
|
393
|
-
let skip = false;
|
|
394
|
-
for (const line of lines) {
|
|
395
|
-
if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) { skip = true; continue; }
|
|
396
|
-
if (skip && line.startsWith('[')) skip = false;
|
|
397
|
-
if (!skip) out.push(line);
|
|
398
|
-
}
|
|
399
|
-
while (out.length > 0 && out[out.length - 1].trim() === '') out.pop();
|
|
400
|
-
writeFileSync(configPath, out.join('\n') + (out.length > 0 ? '\n' : ''));
|
|
401
|
-
console.log(' Config cleaned');
|
|
402
|
-
}
|
|
518
|
+
removeCodexConfigSection();
|
|
403
519
|
}
|
|
404
520
|
|
|
405
521
|
function registerCodeartsMcp(configPath) {
|
|
406
522
|
const mcpPath = join(codeartsPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
523
|
+
const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
|
|
524
|
+
const hcloudBin = findHcloudBin();
|
|
525
|
+
if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
|
|
407
526
|
let config = {};
|
|
408
527
|
if (existsSync(configPath)) {
|
|
409
|
-
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
|
|
528
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
|
|
529
|
+
console.log(` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const existing = config.mcpServers?.['huaweicloud-devkit'];
|
|
533
|
+
if (existing && existing.command === 'node'
|
|
534
|
+
&& Array.isArray(existing.args) && existing.args[0] === mcpPath) {
|
|
535
|
+
console.log(` MCP config unchanged: ${configPath}`);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
410
538
|
}
|
|
411
539
|
config.mcpServers = config.mcpServers || {};
|
|
412
|
-
const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
|
|
413
|
-
const hcloudBin = findHcloudBin();
|
|
414
|
-
if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
|
|
415
540
|
config.mcpServers['huaweicloud-devkit'] = {
|
|
416
541
|
command: 'node',
|
|
417
542
|
args: [mcpPath],
|
|
@@ -443,6 +568,28 @@ async function installCodeArts() {
|
|
|
443
568
|
registerCodeartsMcp(codeartsProjectMcpSettingsFile());
|
|
444
569
|
}
|
|
445
570
|
|
|
571
|
+
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
572
|
+
async function updateCodeArts() {
|
|
573
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
574
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
575
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
576
|
+
const pluginDest = codeartsPluginsDir();
|
|
577
|
+
|
|
578
|
+
for (const dir of [codeartsSkillsDir(), codeartsProjectSkillsDir()]) {
|
|
579
|
+
copyDir(skillsSrc, dir);
|
|
580
|
+
const stale = pruneStale(dir, skillsSrc);
|
|
581
|
+
console.log(` Skills updated -> ${dir}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
|
|
582
|
+
}
|
|
583
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
584
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
585
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
586
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
587
|
+
registerCodeartsMcp(codeartsMcpSettingsFile());
|
|
588
|
+
registerCodeartsMcp(codeartsProjectMcpSettingsFile());
|
|
589
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
590
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
591
|
+
}
|
|
592
|
+
|
|
446
593
|
function uninstallCodeArts() {
|
|
447
594
|
let removed = 0;
|
|
448
595
|
for (const skillsDir of [codeartsSkillsDir(), codeartsProjectSkillsDir()]) {
|
|
@@ -492,6 +639,38 @@ function codeartsStatus() {
|
|
|
492
639
|
}
|
|
493
640
|
}
|
|
494
641
|
|
|
642
|
+
// Returns true when the config file was written, false when it was already correct.
|
|
643
|
+
function ensureWorkbuddyMcpConfig() {
|
|
644
|
+
const configPath = workbuddyMcpConfigFile();
|
|
645
|
+
const mcpPath = join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
646
|
+
const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
|
|
647
|
+
const hcloudBin = findHcloudBin();
|
|
648
|
+
if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
|
|
649
|
+
let config = {};
|
|
650
|
+
if (existsSync(configPath)) {
|
|
651
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
|
|
652
|
+
console.log(` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`);
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
const existing = config.mcpServers?.['huaweicloud-devkit'];
|
|
656
|
+
if (existing && existing.command === 'node'
|
|
657
|
+
&& Array.isArray(existing.args) && existing.args[0] === mcpPath) {
|
|
658
|
+
console.log(` MCP config unchanged: ${configPath}`);
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
config.mcpServers = config.mcpServers || {};
|
|
663
|
+
config.mcpServers['huaweicloud-devkit'] = {
|
|
664
|
+
command: 'node',
|
|
665
|
+
args: [mcpPath],
|
|
666
|
+
env,
|
|
667
|
+
};
|
|
668
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
669
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
670
|
+
console.log(` MCP config updated: ${configPath}`);
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
|
|
495
674
|
async function installWorkBuddy() {
|
|
496
675
|
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
497
676
|
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
@@ -506,25 +685,26 @@ async function installWorkBuddy() {
|
|
|
506
685
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
507
686
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
508
687
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
688
|
+
ensureWorkbuddyMcpConfig();
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
692
|
+
async function updateWorkBuddy() {
|
|
693
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
694
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
695
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
696
|
+
const pluginDest = workbuddyPluginsDir();
|
|
697
|
+
|
|
698
|
+
copyDir(skillsSrc, workbuddySkillsDir());
|
|
699
|
+
const stale = pruneStale(workbuddySkillsDir(), skillsSrc);
|
|
700
|
+
console.log(` Skills updated -> ${workbuddySkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
|
|
701
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
702
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
703
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
704
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
705
|
+
ensureWorkbuddyMcpConfig();
|
|
706
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
707
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
528
708
|
}
|
|
529
709
|
|
|
530
710
|
function uninstallWorkBuddy() {
|
|
@@ -678,7 +858,10 @@ async function cmdInstall() {
|
|
|
678
858
|
console.log(`\nKooCLI (hcloud) detected.`);
|
|
679
859
|
}
|
|
680
860
|
|
|
681
|
-
console.log(`\
|
|
861
|
+
console.log(`\n\x1b[1m下一步:\x1b[0m`);
|
|
862
|
+
console.log(` 1. 配置统一凭据:npx huaweicloud-devkit auth init`);
|
|
863
|
+
console.log(` 2. 重启 ${appName} 会话(MCP 工具重启后生效)`);
|
|
864
|
+
console.log(` 3. 运行自检:npx huaweicloud-devkit doctor`);
|
|
682
865
|
|
|
683
866
|
// Write install marker for doctor to detect
|
|
684
867
|
const markerDir = target === 'codearts' ? codeartsPluginsDir()
|
|
@@ -729,6 +912,19 @@ async function cmdUninstall() {
|
|
|
729
912
|
}
|
|
730
913
|
}
|
|
731
914
|
}
|
|
915
|
+
if (target === 'all') {
|
|
916
|
+
const vaultPath = globalCredentialsPath();
|
|
917
|
+
if (removeIfExists(vaultPath)) {
|
|
918
|
+
console.log(' Removed credential vault');
|
|
919
|
+
}
|
|
920
|
+
const vaultDir = dirname(vaultPath);
|
|
921
|
+
try {
|
|
922
|
+
if (existsSync(vaultDir) && readdirSync(vaultDir).length === 0) {
|
|
923
|
+
rmSync(vaultDir, { recursive: true, force: true });
|
|
924
|
+
console.log(` Removed empty directory: ${vaultDir}`);
|
|
925
|
+
}
|
|
926
|
+
} catch {}
|
|
927
|
+
}
|
|
732
928
|
console.log(`\n\x1b[32mUninstall complete.\x1b[0m`);
|
|
733
929
|
}
|
|
734
930
|
|
|
@@ -846,7 +1042,7 @@ async function cmdDoctor() {
|
|
|
846
1042
|
// Check auth
|
|
847
1043
|
const authCheck = spawnSync(`"${hcloudBin}" configure list`, [], { shell: true, windowsHide: true, stdio: 'pipe', timeout: 5000 });
|
|
848
1044
|
const hasAuth = authCheck.status === 0 && /access.?key/i.test(authCheck.stdout.toString());
|
|
849
|
-
check('hcloud credentials configured', hasAuth, 'Run:
|
|
1045
|
+
check('hcloud credentials configured', hasAuth, 'Run: npx huaweicloud-devkit auth init');
|
|
850
1046
|
}
|
|
851
1047
|
|
|
852
1048
|
// Skills
|
|
@@ -869,6 +1065,14 @@ async function cmdDoctor() {
|
|
|
869
1065
|
warn++;
|
|
870
1066
|
}
|
|
871
1067
|
|
|
1068
|
+
const proxyConfig = readProxyConfig();
|
|
1069
|
+
const proxyEnv = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
|
|
1070
|
+
if (proxyConfig || proxyEnv) {
|
|
1071
|
+
const source = proxyEnv ? 'env' : 'file';
|
|
1072
|
+
const proxyUrl = proxyEnv || proxyConfig.https_proxy || proxyConfig.http_proxy;
|
|
1073
|
+
console.log(` \x1b[36m[INFO]\x1b[0m Proxy configured (${source}): ${proxyUrl}`);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
872
1076
|
console.log(`\nResults: ${pass} pass, ${warn} warn, ${fail} fail`);
|
|
873
1077
|
|
|
874
1078
|
if (mcpConfigured && !hcloudOk) {
|
|
@@ -903,13 +1107,105 @@ async function cmdUpdate() {
|
|
|
903
1107
|
console.log(BANNER);
|
|
904
1108
|
const target = parseTarget();
|
|
905
1109
|
|
|
906
|
-
if (target === 'opencode'
|
|
907
|
-
if (!existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))
|
|
908
|
-
&& !existsSync(join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs'))
|
|
909
|
-
&& !codexStatus()) {
|
|
1110
|
+
if (target === 'opencode') {
|
|
1111
|
+
if (!existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
910
1112
|
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
911
1113
|
return;
|
|
912
1114
|
}
|
|
1115
|
+
console.log('[OpenCode]');
|
|
1116
|
+
await updateOpenCode();
|
|
1117
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1118
|
+
console.log(`\x1b[33mMCP 工具在重启 OpenCode 会话后才生效。\x1b[0m`);
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
if (target === 'codex-desktop') {
|
|
1123
|
+
if (!existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1124
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
console.log('[Codex Desktop]');
|
|
1128
|
+
await updateCodexDesktop();
|
|
1129
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1130
|
+
console.log(`\x1b[33mMCP 工具在重启 Codex Desktop 会话后才生效。\x1b[0m`);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (target === 'codex') {
|
|
1135
|
+
if (!hasCodexCLI()) {
|
|
1136
|
+
console.log(` \x1b[31mCodex CLI not found.\x1b[0m`);
|
|
1137
|
+
if (process.platform === 'win32') {
|
|
1138
|
+
console.log(` \x1b[33mTip: use --target codex-desktop for Codex Desktop on Windows\x1b[0m`);
|
|
1139
|
+
}
|
|
1140
|
+
console.log(` \x1b[31mInstall Codex CLI: https://github.com/openai/codex-cli\x1b[0m`);
|
|
1141
|
+
process.exitCode = 1;
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
console.log('[Codex]');
|
|
1145
|
+
installCodex();
|
|
1146
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1147
|
+
console.log(`\x1b[33mRestart the Codex session for changes to take effect.\x1b[0m`);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
if (target === 'codearts') {
|
|
1152
|
+
if (!existsSync(join(codeartsPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1153
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
console.log('[CodeArts]');
|
|
1157
|
+
await updateCodeArts();
|
|
1158
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1159
|
+
console.log(`\x1b[33mMCP 工具在重启 CodeArts 会话后才生效。\x1b[0m`);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (target === 'workbuddy') {
|
|
1164
|
+
if (!existsSync(join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1165
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
console.log('[WorkBuddy]');
|
|
1169
|
+
await updateWorkBuddy();
|
|
1170
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1171
|
+
console.log(`\x1b[33mMCP 工具在重启 WorkBuddy 会话后才生效。\x1b[0m`);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if (target === 'all') {
|
|
1176
|
+
let updatedAny = false;
|
|
1177
|
+
if (existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1178
|
+
console.log('[OpenCode]');
|
|
1179
|
+
await updateOpenCode();
|
|
1180
|
+
updatedAny = true;
|
|
1181
|
+
}
|
|
1182
|
+
if (existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1183
|
+
console.log('\n[Codex Desktop]');
|
|
1184
|
+
await updateCodexDesktop();
|
|
1185
|
+
updatedAny = true;
|
|
1186
|
+
}
|
|
1187
|
+
if (existsSync(join(codeartsPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1188
|
+
console.log('\n[CodeArts]');
|
|
1189
|
+
await updateCodeArts();
|
|
1190
|
+
updatedAny = true;
|
|
1191
|
+
}
|
|
1192
|
+
if (existsSync(join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1193
|
+
console.log('\n[WorkBuddy]');
|
|
1194
|
+
await updateWorkBuddy();
|
|
1195
|
+
updatedAny = true;
|
|
1196
|
+
}
|
|
1197
|
+
if (codexStatus()) {
|
|
1198
|
+
console.log('\n[Codex]');
|
|
1199
|
+
installCodex();
|
|
1200
|
+
updatedAny = true;
|
|
1201
|
+
}
|
|
1202
|
+
if (!updatedAny) {
|
|
1203
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1207
|
+
console.log(`\x1b[33mMCP 工具在重启各 agent 会话后才生效。\x1b[0m`);
|
|
1208
|
+
return;
|
|
913
1209
|
}
|
|
914
1210
|
|
|
915
1211
|
await cmdUninstall();
|
|
@@ -976,9 +1272,24 @@ async function cmdInstallHcloud() {
|
|
|
976
1272
|
// Clean up zip
|
|
977
1273
|
rmSync(zipPath, { force: true });
|
|
978
1274
|
|
|
979
|
-
// Add to PATH
|
|
1275
|
+
// Add to user PATH (append + dedupe within the User scope only; never copy
|
|
1276
|
+
// session/system entries into the user PATH, and never use setx PATH which
|
|
1277
|
+
// overwrites the whole variable and truncates at 1024 chars).
|
|
980
1278
|
console.log(' Adding to user PATH...');
|
|
981
|
-
|
|
1279
|
+
const pathPs = [
|
|
1280
|
+
'$ErrorActionPreference = "Stop"',
|
|
1281
|
+
`$target = '${installDir.replace(/'/g, "''")}'`,
|
|
1282
|
+
'$cur = [Environment]::GetEnvironmentVariable("Path", "User")',
|
|
1283
|
+
'if (-not $cur) { $cur = "" }',
|
|
1284
|
+
'$parts = @($cur -split ";" | Where-Object { $_ -ne "" })',
|
|
1285
|
+
'if ($parts -notcontains $target) {',
|
|
1286
|
+
' [Environment]::SetEnvironmentVariable("Path", (@($parts) + $target) -join ";", "User")',
|
|
1287
|
+
' Write-Output " Added to user PATH (deduped): $target"',
|
|
1288
|
+
'} else {',
|
|
1289
|
+
' Write-Output " Already in user PATH: $target"',
|
|
1290
|
+
'}',
|
|
1291
|
+
].join('; ');
|
|
1292
|
+
spawnSync('powershell', ['-NoProfile', '-Command', pathPs], { stdio: 'inherit', windowsHide: true, timeout: 30000 });
|
|
982
1293
|
|
|
983
1294
|
console.log(`\n\x1b[32mInstall complete.\x1b[0m`);
|
|
984
1295
|
console.log(` Verify: ${join(installDir, 'hcloud.exe')} version`);
|
|
@@ -1037,11 +1348,278 @@ async function cmdInstallHcloud() {
|
|
|
1037
1348
|
|
|
1038
1349
|
console.log('\nAfter install, set HCLOUD_BIN if hcloud is not on PATH.');
|
|
1039
1350
|
console.log('\n\x1b[1m\x1b[33m=== Configure credentials SAFELY ===\x1b[0m');
|
|
1040
|
-
console.log('
|
|
1351
|
+
console.log(' Unified credentials (recommended): npx huaweicloud-devkit auth init');
|
|
1352
|
+
console.log(' KooCLI only (alternative): hcloud configure init');
|
|
1041
1353
|
console.log(' NEVER: hcloud configure set --cli-access-key=xxx (AK/SK in shell history!)');
|
|
1042
1354
|
console.log('\nThen run: npx huaweicloud-devkit doctor');
|
|
1043
1355
|
}
|
|
1044
1356
|
|
|
1357
|
+
function readLineQuestion(prompt) {
|
|
1358
|
+
return new Promise((resolve) => {
|
|
1359
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1360
|
+
rl.question(prompt, (answer) => {
|
|
1361
|
+
rl.close();
|
|
1362
|
+
resolve(answer.trim());
|
|
1363
|
+
});
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
async function readSecret(prompt) {
|
|
1368
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1369
|
+
throw new Error(
|
|
1370
|
+
`Cannot read "${prompt.trim()}" securely in a non-interactive session. Set HW_ACCESS_KEY/HW_SECRET_KEY environment variables instead, or run "npx huaweicloud-devkit auth init" in a real terminal.`
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
process.stdout.write(prompt);
|
|
1375
|
+
const wasRaw = process.stdin.isRaw;
|
|
1376
|
+
process.stdin.setRawMode(true);
|
|
1377
|
+
process.stdin.resume();
|
|
1378
|
+
|
|
1379
|
+
return new Promise((resolve) => {
|
|
1380
|
+
let value = '';
|
|
1381
|
+
const onData = (chunk) => {
|
|
1382
|
+
for (const ch of chunk.toString('utf8')) {
|
|
1383
|
+
if (ch === '\r' || ch === '\n') {
|
|
1384
|
+
cleanup();
|
|
1385
|
+
resolve(value.trim());
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
if (ch === '\u0003') {
|
|
1389
|
+
cleanup();
|
|
1390
|
+
process.exit(130);
|
|
1391
|
+
}
|
|
1392
|
+
if (ch === '\b' || ch === '\u007f') {
|
|
1393
|
+
value = value.slice(0, -1);
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
value += ch;
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
const cleanup = () => {
|
|
1400
|
+
process.stdin.setRawMode(wasRaw);
|
|
1401
|
+
process.stdin.pause();
|
|
1402
|
+
process.stdin.off('data', onData);
|
|
1403
|
+
process.stdout.write('\n');
|
|
1404
|
+
};
|
|
1405
|
+
process.stdin.on('data', onData);
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
function configureHcloud(credentials) {
|
|
1410
|
+
const hcloudBin = findHcloudBin() || (process.env.HCLOUD_BIN || 'hcloud');
|
|
1411
|
+
const args = [
|
|
1412
|
+
'configure',
|
|
1413
|
+
'set',
|
|
1414
|
+
`--cli-access-key=${credentials.ak}`,
|
|
1415
|
+
`--cli-secret-key=${credentials.sk}`,
|
|
1416
|
+
`--cli-region=${credentials.region || ''}`,
|
|
1417
|
+
];
|
|
1418
|
+
const r = spawnSync(hcloudBin, args, {
|
|
1419
|
+
shell: false,
|
|
1420
|
+
windowsHide: true,
|
|
1421
|
+
stdio: 'pipe',
|
|
1422
|
+
timeout: 30000,
|
|
1423
|
+
});
|
|
1424
|
+
return {
|
|
1425
|
+
ok: r.status === 0,
|
|
1426
|
+
code: r.status,
|
|
1427
|
+
error: String(r.stderr || '').trim().slice(0, 240),
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function printAuthAgents(agents = {}) {
|
|
1432
|
+
for (const [agent, info] of Object.entries(agents)) {
|
|
1433
|
+
console.log(` ${agent}: ${info.configured ? '[OK]' : '[MISSING]'}`);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
function printAuthStatus(status) {
|
|
1438
|
+
console.log(`Credentials vault: ${status.credentialsConfigured ? 'configured' : 'missing'} (${status.credentialsPath})`);
|
|
1439
|
+
console.log(`OBS config: ${status.obsConfigured ? 'configured' : 'missing'} (${status.obsConfigPath})`);
|
|
1440
|
+
console.log(`KooCLI: ${status.kooCliInstalled ? 'installed' : 'missing'}`);
|
|
1441
|
+
console.log('Agent MCP registration:');
|
|
1442
|
+
printAuthAgents(status.agents);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
async function cmdAuthInit() {
|
|
1446
|
+
console.log(BANNER);
|
|
1447
|
+
console.log('HuaweiCloud DevKit Unified Authentication Setup\n');
|
|
1448
|
+
console.log('\x1b[1m获取 AK/SK(如果还没有):\x1b[0m');
|
|
1449
|
+
console.log(' 1. 打开华为云"访问密钥"页签:');
|
|
1450
|
+
console.log(' https://console.huaweicloud.com/iam/?region=cn-north-4#/mine/accessKey');
|
|
1451
|
+
console.log(' 2. 点击"新增访问密钥",完成身份验证');
|
|
1452
|
+
console.log(' 3. 下载凭证文件(内含 AK 和 SK)。');
|
|
1453
|
+
console.log(' 注意:SK 只在创建密钥时显示一次,请妥善保存该文件。\n');
|
|
1454
|
+
|
|
1455
|
+
let ak = process.env.HW_ACCESS_KEY || '';
|
|
1456
|
+
let sk = process.env.HW_SECRET_KEY || '';
|
|
1457
|
+
let securityToken = process.env.HW_SECURITY_TOKEN || '';
|
|
1458
|
+
let region = process.env.HW_REGION || process.env.HUAWEICLOUD_REGION || '';
|
|
1459
|
+
|
|
1460
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
1461
|
+
if (!interactive && (!ak || !sk)) {
|
|
1462
|
+
console.error('\x1b[31mNon-interactive session detected. Provide credentials via environment variables instead:\x1b[0m');
|
|
1463
|
+
console.error(' HW_ACCESS_KEY, HW_SECRET_KEY');
|
|
1464
|
+
console.error(' (Or run "npx huaweicloud-devkit auth init" in a real terminal.)');
|
|
1465
|
+
process.exitCode = 1;
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
if (!ak) ak = await readLineQuestion('Access Key ID (AK): ');
|
|
1470
|
+
if (!sk) sk = await readSecret('Secret Access Key (SK): ');
|
|
1471
|
+
if (interactive && !securityToken) securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
|
|
1472
|
+
if (!region) region = 'cn-north-4';
|
|
1473
|
+
|
|
1474
|
+
if (!ak || !sk) {
|
|
1475
|
+
console.error('\nAK and SK are required.');
|
|
1476
|
+
process.exitCode = 1;
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
|
|
1481
|
+
const vaultPath = writeGlobalCredentials({ ak, sk, securityToken, region });
|
|
1482
|
+
|
|
1483
|
+
try {
|
|
1484
|
+
writeObsConfig({ ak, sk, securityToken, region });
|
|
1485
|
+
} catch (error) {
|
|
1486
|
+
console.log(`OBS config sync failed: ${error.message}`);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
if (findHcloudBin()) {
|
|
1490
|
+
const result = configureHcloud({ ak, sk, region });
|
|
1491
|
+
if (!result.ok) console.log(`KooCLI update failed: ${result.error || result.code}`);
|
|
1492
|
+
} else {
|
|
1493
|
+
console.log('KooCLI not found. Run "npx huaweicloud-devkit install-hcloud" and then "auth sync".');
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
console.log('\nCredentials synchronized.');
|
|
1497
|
+
console.log('\nNext steps:');
|
|
1498
|
+
console.log(' npx huaweicloud-devkit install --target all');
|
|
1499
|
+
console.log(' Restart your agent sessions.');
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
async function cmdAuthSync() {
|
|
1503
|
+
const target = parseTarget();
|
|
1504
|
+
console.log(BANNER);
|
|
1505
|
+
console.log('Synchronizing Huawei Cloud authentication...\n');
|
|
1506
|
+
|
|
1507
|
+
const credentials = readGlobalCredentials();
|
|
1508
|
+
if (!credentials?.ak || !credentials?.sk) {
|
|
1509
|
+
console.error('No global credentials found. Run "npx huaweicloud-devkit auth init" first.');
|
|
1510
|
+
process.exitCode = 1;
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
const result = syncAuth(target);
|
|
1515
|
+
if (result.ok) {
|
|
1516
|
+
console.log('Credentials synchronized.');
|
|
1517
|
+
} else {
|
|
1518
|
+
console.error(result.error);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
async function cmdAuthStatus() {
|
|
1523
|
+
const target = parseTarget();
|
|
1524
|
+
console.log(BANNER);
|
|
1525
|
+
console.log('HuaweiCloud DevKit Authentication Status\n');
|
|
1526
|
+
printAuthStatus(getAuthStatus(target));
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
async function cmdAuth() {
|
|
1530
|
+
const sub = (process.argv[3] || 'status').toLowerCase();
|
|
1531
|
+
if (sub === 'init' || sub === 'setup') return cmdAuthInit();
|
|
1532
|
+
if (sub === 'sync' || sub === 'refresh') return cmdAuthSync();
|
|
1533
|
+
return cmdAuthStatus();
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
async function cmdProxyInit() {
|
|
1537
|
+
console.log(BANNER);
|
|
1538
|
+
console.log('HuaweiCloud DevKit Proxy Configuration\n');
|
|
1539
|
+
console.log('Configure HTTP/HTTPS proxy for connections to Huawei Cloud services.');
|
|
1540
|
+
console.log('Proxy settings are saved to ~/.config/huaweicloud/proxy.json\n');
|
|
1541
|
+
|
|
1542
|
+
const existing = readProxyConfig() || {};
|
|
1543
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
1544
|
+
|
|
1545
|
+
if (!interactive) {
|
|
1546
|
+
console.error('\x1b[31mNon-interactive session. Set proxy via environment variables:\x1b[0m');
|
|
1547
|
+
console.error(' HTTPS_PROXY=http://proxy:port');
|
|
1548
|
+
console.error(' HTTP_PROXY=http://proxy:port');
|
|
1549
|
+
console.error(' NO_PROXY=localhost,127.0.0.1');
|
|
1550
|
+
console.error('\nOr run "npx huaweicloud-devkit proxy init" in a real terminal.');
|
|
1551
|
+
process.exitCode = 1;
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
const httpsProxy = await readLineQuestion(`HTTPS proxy [${existing.https_proxy || 'none'}]: `);
|
|
1556
|
+
const httpProxy = await readLineQuestion(`HTTP proxy [${existing.http_proxy || 'none'}]: `);
|
|
1557
|
+
const noProxy = await readLineQuestion(`NO_PROXY hosts [${existing.no_proxy || 'localhost,127.0.0.1'}]: `);
|
|
1558
|
+
|
|
1559
|
+
const config = {
|
|
1560
|
+
https_proxy: httpsProxy || existing.https_proxy || '',
|
|
1561
|
+
http_proxy: httpProxy || existing.http_proxy || '',
|
|
1562
|
+
no_proxy: noProxy || existing.no_proxy || 'localhost,127.0.0.1',
|
|
1563
|
+
};
|
|
1564
|
+
|
|
1565
|
+
const path = writeProxyConfig(config);
|
|
1566
|
+
console.log(`\nProxy configuration saved to ${path}`);
|
|
1567
|
+
console.log('\nEffective settings:');
|
|
1568
|
+
console.log(` HTTPS_PROXY: ${config.https_proxy || '(none)'}`);
|
|
1569
|
+
console.log(` HTTP_PROXY: ${config.http_proxy || '(none)'}`);
|
|
1570
|
+
console.log(` NO_PROXY: ${config.no_proxy || '(none)'}`);
|
|
1571
|
+
console.log('\nEnvironment variables (HTTPS_PROXY, HTTP_PROXY, NO_PROXY) override file settings.');
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
async function cmdProxyShow() {
|
|
1575
|
+
console.log(BANNER);
|
|
1576
|
+
console.log('HuaweiCloud DevKit Proxy Configuration\n');
|
|
1577
|
+
|
|
1578
|
+
const config = readProxyConfig();
|
|
1579
|
+
const configPath = proxyConfigPath();
|
|
1580
|
+
|
|
1581
|
+
console.log(`Config file: ${configPath}`);
|
|
1582
|
+
console.log(`File exists: ${config ? 'yes' : 'no'}\n`);
|
|
1583
|
+
|
|
1584
|
+
if (config) {
|
|
1585
|
+
console.log('File settings:');
|
|
1586
|
+
console.log(` https_proxy: ${config.https_proxy || '(empty)'}`);
|
|
1587
|
+
console.log(` http_proxy: ${config.http_proxy || '(empty)'}`);
|
|
1588
|
+
console.log(` no_proxy: ${config.no_proxy || '(empty)'}`);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
console.log('\nEnvironment variables:');
|
|
1592
|
+
console.log(` HTTPS_PROXY: ${process.env.HTTPS_PROXY || process.env.https_proxy || '(not set)'}`);
|
|
1593
|
+
console.log(` HTTP_PROXY: ${process.env.HTTP_PROXY || process.env.http_proxy || '(not set)'}`);
|
|
1594
|
+
console.log(` NO_PROXY: ${process.env.NO_PROXY || process.env.no_proxy || '(not set)'}`);
|
|
1595
|
+
|
|
1596
|
+
const effective = getProxySettings();
|
|
1597
|
+
console.log('\nEffective (env > file):');
|
|
1598
|
+
if (effective) {
|
|
1599
|
+
console.log(` https_proxy: ${effective.https_proxy || '(none)'}`);
|
|
1600
|
+
console.log(` http_proxy: ${effective.http_proxy || '(none)'}`);
|
|
1601
|
+
console.log(` no_proxy: ${effective.no_proxy || '(none)'}`);
|
|
1602
|
+
} else {
|
|
1603
|
+
console.log(' (no proxy configured)');
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
async function cmdProxyClear() {
|
|
1608
|
+
const removed = clearProxyConfig();
|
|
1609
|
+
if (removed) {
|
|
1610
|
+
console.log('Proxy configuration removed.');
|
|
1611
|
+
} else {
|
|
1612
|
+
console.log('No proxy configuration file found.');
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
async function cmdProxy() {
|
|
1617
|
+
const sub = (process.argv[3] || 'show').toLowerCase();
|
|
1618
|
+
if (sub === 'init' || sub === 'setup') return cmdProxyInit();
|
|
1619
|
+
if (sub === 'clear' || sub === 'remove' || sub === 'reset') return cmdProxyClear();
|
|
1620
|
+
return cmdProxyShow();
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1045
1623
|
async function main() {
|
|
1046
1624
|
const cmd = process.argv[2] || 'help';
|
|
1047
1625
|
|
|
@@ -1072,6 +1650,12 @@ async function main() {
|
|
|
1072
1650
|
case 'install-hcloud':
|
|
1073
1651
|
await cmdInstallHcloud();
|
|
1074
1652
|
break;
|
|
1653
|
+
case 'auth':
|
|
1654
|
+
await cmdAuth();
|
|
1655
|
+
break;
|
|
1656
|
+
case 'proxy':
|
|
1657
|
+
await cmdProxy();
|
|
1658
|
+
break;
|
|
1075
1659
|
case 'help':
|
|
1076
1660
|
case '--help':
|
|
1077
1661
|
case '-h':
|
|
@@ -1086,6 +1670,8 @@ async function main() {
|
|
|
1086
1670
|
console.log(' status Show installation status');
|
|
1087
1671
|
console.log(' doctor Self-check: hcloud, MCP, skills, auth');
|
|
1088
1672
|
console.log(' install-hcloud Show KooCLI install commands for your OS');
|
|
1673
|
+
console.log(' auth Manage unified auth: init | sync | status');
|
|
1674
|
+
console.log(' proxy Manage proxy config: init | show | clear');
|
|
1089
1675
|
console.log(' help Show this help');
|
|
1090
1676
|
console.log('\nOptions:');
|
|
1091
1677
|
console.log(' --target Target agent: opencode (default), codex, codearts, workbuddy, all');
|
|
@@ -1095,6 +1681,11 @@ async function main() {
|
|
|
1095
1681
|
console.log(' npx huaweicloud-devkit install --target codearts');
|
|
1096
1682
|
console.log(' npx huaweicloud-devkit install --target workbuddy');
|
|
1097
1683
|
console.log(' npx huaweicloud-devkit install --target all');
|
|
1684
|
+
console.log(' npx huaweicloud-devkit auth init');
|
|
1685
|
+
console.log(' npx huaweicloud-devkit auth sync --target all');
|
|
1686
|
+
console.log(' npx huaweicloud-devkit auth status --target all');
|
|
1687
|
+
console.log(' npx huaweicloud-devkit proxy init');
|
|
1688
|
+
console.log(' npx huaweicloud-devkit proxy show');
|
|
1098
1689
|
break;
|
|
1099
1690
|
}
|
|
1100
1691
|
}
|