huaweicloud-devkit 1.0.2-dev.0 → 1.0.2-dev.11
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/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 +3 -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 +8 -0
- package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +40 -0
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +134 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +2 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +3 -1
- 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/sandbox/hdkitservice-api.mjs +87 -0
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +153 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +105 -0
- package/plugins/huaweicloud-core/src/setup-cli.mjs +582 -68
- package/plugins/huaweicloud-core/src/tools.mjs +159 -3
- 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,8 @@ 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';
|
|
9
11
|
|
|
10
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
13
|
const PLUGIN_ROOT = resolve(__dirname, '..');
|
|
@@ -40,7 +42,7 @@ function opencodeConfigFile() {
|
|
|
40
42
|
function codexDesktopSkillsDir() { return join(homedir(), '.agents', 'skills'); }
|
|
41
43
|
function codexDesktopCommandsDir() { return join(homedir(), '.agents', 'commands'); }
|
|
42
44
|
function codexDesktopPluginsDir() { return join(homedir(), '.agents', 'huaweicloud-plugins'); }
|
|
43
|
-
function
|
|
45
|
+
function codexConfigToml() { return join(homedir(), '.codex', 'config.toml'); }
|
|
44
46
|
|
|
45
47
|
function codeartsSkillsDir() { return join(homedir(), '.codeartsdoer', 'skills'); }
|
|
46
48
|
function codeartsMcpSettingsDir() { return join(homedir(), '.codeartsdoer', 'mcp'); }
|
|
@@ -127,11 +129,21 @@ function removeIfExists(p) {
|
|
|
127
129
|
|
|
128
130
|
function updateOpenCodeConfig(pluginDir) {
|
|
129
131
|
const configPath = opencodeConfigFile();
|
|
132
|
+
const mcpPath = join(pluginDir, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
130
133
|
let config = {};
|
|
131
134
|
if (existsSync(configPath)) {
|
|
132
|
-
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
|
|
135
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
|
|
136
|
+
console.log(` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath} (jsonc comments?). Skipping MCP config write; ensure "mcp.huaweicloud-devkit" points to ${mcpPath}.`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const existing = config.mcp?.['huaweicloud-devkit'];
|
|
140
|
+
if (existing && existing.type === 'local'
|
|
141
|
+
&& Array.isArray(existing.command) && existing.command[0] === 'node'
|
|
142
|
+
&& existing.command[1] === mcpPath) {
|
|
143
|
+
console.log(` OpenCode MCP config unchanged: ${configPath}`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
133
146
|
}
|
|
134
|
-
const mcpPath = join(pluginDir, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
135
147
|
config.mcp = config.mcp || {};
|
|
136
148
|
config.mcp['huaweicloud-devkit'] = {
|
|
137
149
|
type: 'local',
|
|
@@ -230,6 +242,14 @@ function uninstallCodex() {
|
|
|
230
242
|
shell: true, windowsHide: true, stdio: 'pipe',
|
|
231
243
|
});
|
|
232
244
|
console.log(` ${r.stdout ? r.stdout.toString().trim() : r.stderr.toString().trim()}`);
|
|
245
|
+
|
|
246
|
+
for (const name of new Set([marketplaceName, 'HuaweiCloud-Devkit'])) {
|
|
247
|
+
console.log(` Removing Codex marketplace: ${name}`);
|
|
248
|
+
const r2 = spawnSync(`codex plugin marketplace remove "${name}"`, [], {
|
|
249
|
+
shell: true, windowsHide: true, stdio: 'pipe',
|
|
250
|
+
});
|
|
251
|
+
console.log(` ${r2.stdout ? r2.stdout.toString().trim() : r2.stderr.toString().trim()}`);
|
|
252
|
+
}
|
|
233
253
|
}
|
|
234
254
|
|
|
235
255
|
function codexStatus() {
|
|
@@ -288,6 +308,101 @@ function uninstallOpenCode() {
|
|
|
288
308
|
removeOpenCodeConfig();
|
|
289
309
|
}
|
|
290
310
|
|
|
311
|
+
// Remove huawei* entries in targetDir that no longer exist in sourceDir (stale files from an older version).
|
|
312
|
+
function pruneStale(targetDir, sourceDir) {
|
|
313
|
+
if (!existsSync(targetDir) || !existsSync(sourceDir)) return 0;
|
|
314
|
+
const sourceNames = new Set(readdirSync(sourceDir));
|
|
315
|
+
let removed = 0;
|
|
316
|
+
for (const entry of readdirSync(targetDir, { withFileTypes: true })) {
|
|
317
|
+
if (!entry.name.startsWith('huawei')) continue;
|
|
318
|
+
if (!sourceNames.has(entry.name)) {
|
|
319
|
+
removeIfExists(join(targetDir, entry.name));
|
|
320
|
+
removed++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return removed;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
327
|
+
async function updateOpenCode() {
|
|
328
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
329
|
+
const commandsSrc = join(PACKAGE_ROOT, 'integrations', 'opencode', 'commands');
|
|
330
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
331
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
332
|
+
const pluginDest = opencodePluginsDir();
|
|
333
|
+
|
|
334
|
+
copyDir(skillsSrc, opencodeSkillsDir());
|
|
335
|
+
const staleSkills = pruneStale(opencodeSkillsDir(), skillsSrc);
|
|
336
|
+
console.log(` Skills updated -> ${opencodeSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`);
|
|
337
|
+
copyDir(commandsSrc, opencodeCommandsDir());
|
|
338
|
+
const staleCommands = pruneStale(opencodeCommandsDir(), commandsSrc);
|
|
339
|
+
console.log(` Commands updated -> ${opencodeCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`);
|
|
340
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
341
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
342
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
343
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
344
|
+
updateOpenCodeConfig(pluginDest);
|
|
345
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
346
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function codexMcpServerPath() {
|
|
350
|
+
return join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function codexConfigSectionText(mcpPath) {
|
|
354
|
+
return [
|
|
355
|
+
'[mcp_servers.huaweicloud-devkit]',
|
|
356
|
+
'command = "node"',
|
|
357
|
+
`args = ["${mcpPath}"]`,
|
|
358
|
+
'',
|
|
359
|
+
'[mcp_servers.huaweicloud-devkit.env]',
|
|
360
|
+
'HUAWEICLOUD_AGENT_TOOLKIT_MODE = "local"',
|
|
361
|
+
'',
|
|
362
|
+
].join('\n');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Returns true when the config file was written, false when it was already correct.
|
|
366
|
+
function ensureCodexConfigSection(mcpPath) {
|
|
367
|
+
const configPath = codexConfigToml();
|
|
368
|
+
let existing = '';
|
|
369
|
+
if (existsSync(configPath)) {
|
|
370
|
+
try { existing = readFileSync(configPath, 'utf8'); } catch {}
|
|
371
|
+
if (existing.includes('[mcp_servers.huaweicloud-devkit]')) {
|
|
372
|
+
if (existing.includes(`args = ["${mcpPath}"]`)) {
|
|
373
|
+
console.log(` Config unchanged: ${configPath}`);
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
removeCodexConfigSection();
|
|
377
|
+
existing = '';
|
|
378
|
+
if (existsSync(configPath)) {
|
|
379
|
+
try { existing = readFileSync(configPath, 'utf8'); } catch {}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
384
|
+
if (existing && !existing.endsWith('\n')) existing += '\n';
|
|
385
|
+
writeFileSync(configPath, existing + codexConfigSectionText(mcpPath));
|
|
386
|
+
console.log(` Config updated: ${configPath}`);
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function removeCodexConfigSection() {
|
|
391
|
+
const configPath = codexConfigToml();
|
|
392
|
+
if (!existsSync(configPath)) return;
|
|
393
|
+
const lines = readFileSync(configPath, 'utf8').split(/\r?\n/);
|
|
394
|
+
const out = [];
|
|
395
|
+
let skip = false;
|
|
396
|
+
for (const line of lines) {
|
|
397
|
+
if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) { skip = true; continue; }
|
|
398
|
+
if (skip && line.startsWith('[')) skip = false;
|
|
399
|
+
if (!skip) out.push(line);
|
|
400
|
+
}
|
|
401
|
+
while (out.length > 0 && out[out.length - 1].trim() === '') out.pop();
|
|
402
|
+
writeFileSync(configPath, out.join('\n') + (out.length > 0 ? '\n' : ''));
|
|
403
|
+
console.log(' Config cleaned');
|
|
404
|
+
}
|
|
405
|
+
|
|
291
406
|
async function installCodexDesktop() {
|
|
292
407
|
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
293
408
|
const commandsSrc = join(PACKAGE_ROOT, 'integrations', 'opencode', 'commands');
|
|
@@ -305,7 +420,7 @@ async function installCodexDesktop() {
|
|
|
305
420
|
console.log(` Safety Policy -> ${join(codexDesktopPluginsDir(), 'safety')}`);
|
|
306
421
|
|
|
307
422
|
// Generate .mcp.json with absolute paths for Codex Desktop MCP server discovery
|
|
308
|
-
const mcpServerAbsPath =
|
|
423
|
+
const mcpServerAbsPath = codexMcpServerPath();
|
|
309
424
|
const mcpConfig = {
|
|
310
425
|
mcpServers: {
|
|
311
426
|
'huaweicloud-devkit': {
|
|
@@ -325,20 +440,50 @@ async function installCodexDesktop() {
|
|
|
325
440
|
console.log(` Plugin Manifest -> ${join(codexDesktopPluginsDir(), '.codex-plugin')}`);
|
|
326
441
|
}
|
|
327
442
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
443
|
+
ensureCodexConfigSection(mcpServerAbsPath);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
447
|
+
async function updateCodexDesktop() {
|
|
448
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
449
|
+
const commandsSrc = join(PACKAGE_ROOT, 'integrations', 'opencode', 'commands');
|
|
450
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
451
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
452
|
+
const pluginDest = codexDesktopPluginsDir();
|
|
453
|
+
|
|
454
|
+
copyDir(skillsSrc, codexDesktopSkillsDir());
|
|
455
|
+
const staleSkills = pruneStale(codexDesktopSkillsDir(), skillsSrc);
|
|
456
|
+
console.log(` Skills updated -> ${codexDesktopSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`);
|
|
457
|
+
copyDir(commandsSrc, codexDesktopCommandsDir());
|
|
458
|
+
const staleCommands = pruneStale(codexDesktopCommandsDir(), commandsSrc);
|
|
459
|
+
console.log(` Commands updated -> ${codexDesktopCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`);
|
|
460
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
461
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
462
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
463
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
464
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
465
|
+
|
|
466
|
+
const mcpServerAbsPath = codexMcpServerPath();
|
|
467
|
+
const mcpConfig = {
|
|
468
|
+
mcpServers: {
|
|
469
|
+
'huaweicloud-devkit': {
|
|
470
|
+
command: 'node',
|
|
471
|
+
args: [mcpServerAbsPath],
|
|
472
|
+
env: { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' },
|
|
473
|
+
},
|
|
474
|
+
},
|
|
339
475
|
};
|
|
340
|
-
writeFileSync(
|
|
341
|
-
console.log(` Config updated
|
|
476
|
+
writeFileSync(join(pluginDest, '.mcp.json'), JSON.stringify(mcpConfig, null, 2));
|
|
477
|
+
console.log(` MCP Config updated -> ${join(pluginDest, '.mcp.json')}`);
|
|
478
|
+
|
|
479
|
+
const codexPluginSrc = join(PLUGIN_ROOT, '.codex-plugin');
|
|
480
|
+
if (existsSync(codexPluginSrc)) {
|
|
481
|
+
copyDir(codexPluginSrc, join(pluginDest, '.codex-plugin'));
|
|
482
|
+
console.log(` Plugin Manifest updated -> ${join(pluginDest, '.codex-plugin')}`);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
ensureCodexConfigSection(mcpServerAbsPath);
|
|
486
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
342
487
|
}
|
|
343
488
|
|
|
344
489
|
function uninstallCodexDesktop() {
|
|
@@ -369,29 +514,28 @@ function uninstallCodexDesktop() {
|
|
|
369
514
|
if (removeIfExists(codexDesktopPluginsDir())) {
|
|
370
515
|
console.log(' Removed MCP server and safety policy');
|
|
371
516
|
}
|
|
372
|
-
|
|
373
|
-
if (existsSync(configPath)) {
|
|
374
|
-
let config = {};
|
|
375
|
-
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
|
|
376
|
-
if (config.mcp?.['huaweicloud-devkit']) {
|
|
377
|
-
delete config.mcp['huaweicloud-devkit'];
|
|
378
|
-
if (Object.keys(config.mcp).length === 0) delete config.mcp;
|
|
379
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
380
|
-
console.log(' Config cleaned');
|
|
381
|
-
}
|
|
382
|
-
}
|
|
517
|
+
removeCodexConfigSection();
|
|
383
518
|
}
|
|
384
519
|
|
|
385
520
|
function registerCodeartsMcp(configPath) {
|
|
386
521
|
const mcpPath = join(codeartsPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
522
|
+
const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
|
|
523
|
+
const hcloudBin = findHcloudBin();
|
|
524
|
+
if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
|
|
387
525
|
let config = {};
|
|
388
526
|
if (existsSync(configPath)) {
|
|
389
|
-
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
|
|
527
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
|
|
528
|
+
console.log(` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const existing = config.mcpServers?.['huaweicloud-devkit'];
|
|
532
|
+
if (existing && existing.command === 'node'
|
|
533
|
+
&& Array.isArray(existing.args) && existing.args[0] === mcpPath) {
|
|
534
|
+
console.log(` MCP config unchanged: ${configPath}`);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
390
537
|
}
|
|
391
538
|
config.mcpServers = config.mcpServers || {};
|
|
392
|
-
const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
|
|
393
|
-
const hcloudBin = findHcloudBin();
|
|
394
|
-
if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
|
|
395
539
|
config.mcpServers['huaweicloud-devkit'] = {
|
|
396
540
|
command: 'node',
|
|
397
541
|
args: [mcpPath],
|
|
@@ -423,6 +567,28 @@ async function installCodeArts() {
|
|
|
423
567
|
registerCodeartsMcp(codeartsProjectMcpSettingsFile());
|
|
424
568
|
}
|
|
425
569
|
|
|
570
|
+
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
571
|
+
async function updateCodeArts() {
|
|
572
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
573
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
574
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
575
|
+
const pluginDest = codeartsPluginsDir();
|
|
576
|
+
|
|
577
|
+
for (const dir of [codeartsSkillsDir(), codeartsProjectSkillsDir()]) {
|
|
578
|
+
copyDir(skillsSrc, dir);
|
|
579
|
+
const stale = pruneStale(dir, skillsSrc);
|
|
580
|
+
console.log(` Skills updated -> ${dir}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
|
|
581
|
+
}
|
|
582
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
583
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
584
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
585
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
586
|
+
registerCodeartsMcp(codeartsMcpSettingsFile());
|
|
587
|
+
registerCodeartsMcp(codeartsProjectMcpSettingsFile());
|
|
588
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
589
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
590
|
+
}
|
|
591
|
+
|
|
426
592
|
function uninstallCodeArts() {
|
|
427
593
|
let removed = 0;
|
|
428
594
|
for (const skillsDir of [codeartsSkillsDir(), codeartsProjectSkillsDir()]) {
|
|
@@ -472,6 +638,38 @@ function codeartsStatus() {
|
|
|
472
638
|
}
|
|
473
639
|
}
|
|
474
640
|
|
|
641
|
+
// Returns true when the config file was written, false when it was already correct.
|
|
642
|
+
function ensureWorkbuddyMcpConfig() {
|
|
643
|
+
const configPath = workbuddyMcpConfigFile();
|
|
644
|
+
const mcpPath = join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
645
|
+
const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
|
|
646
|
+
const hcloudBin = findHcloudBin();
|
|
647
|
+
if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
|
|
648
|
+
let config = {};
|
|
649
|
+
if (existsSync(configPath)) {
|
|
650
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
|
|
651
|
+
console.log(` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`);
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
const existing = config.mcpServers?.['huaweicloud-devkit'];
|
|
655
|
+
if (existing && existing.command === 'node'
|
|
656
|
+
&& Array.isArray(existing.args) && existing.args[0] === mcpPath) {
|
|
657
|
+
console.log(` MCP config unchanged: ${configPath}`);
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
config.mcpServers = config.mcpServers || {};
|
|
662
|
+
config.mcpServers['huaweicloud-devkit'] = {
|
|
663
|
+
command: 'node',
|
|
664
|
+
args: [mcpPath],
|
|
665
|
+
env,
|
|
666
|
+
};
|
|
667
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
668
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
669
|
+
console.log(` MCP config updated: ${configPath}`);
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
|
|
475
673
|
async function installWorkBuddy() {
|
|
476
674
|
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
477
675
|
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
@@ -486,25 +684,26 @@ async function installWorkBuddy() {
|
|
|
486
684
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
487
685
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
488
686
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
const
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
687
|
+
ensureWorkbuddyMcpConfig();
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
691
|
+
async function updateWorkBuddy() {
|
|
692
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
693
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
694
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
695
|
+
const pluginDest = workbuddyPluginsDir();
|
|
696
|
+
|
|
697
|
+
copyDir(skillsSrc, workbuddySkillsDir());
|
|
698
|
+
const stale = pruneStale(workbuddySkillsDir(), skillsSrc);
|
|
699
|
+
console.log(` Skills updated -> ${workbuddySkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
|
|
700
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
701
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
702
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
703
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
704
|
+
ensureWorkbuddyMcpConfig();
|
|
705
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
706
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
508
707
|
}
|
|
509
708
|
|
|
510
709
|
function uninstallWorkBuddy() {
|
|
@@ -658,12 +857,17 @@ async function cmdInstall() {
|
|
|
658
857
|
console.log(`\nKooCLI (hcloud) detected.`);
|
|
659
858
|
}
|
|
660
859
|
|
|
661
|
-
console.log(`\
|
|
860
|
+
console.log(`\n\x1b[1m下一步:\x1b[0m`);
|
|
861
|
+
console.log(` 1. 配置统一凭据:npx huaweicloud-devkit auth init`);
|
|
862
|
+
console.log(` 2. 重启 ${appName} 会话(MCP 工具重启后生效)`);
|
|
863
|
+
console.log(` 3. 运行自检:npx huaweicloud-devkit doctor`);
|
|
662
864
|
|
|
663
865
|
// Write install marker for doctor to detect
|
|
664
866
|
const markerDir = target === 'codearts' ? codeartsPluginsDir()
|
|
665
867
|
: target === 'workbuddy' ? workbuddyPluginsDir()
|
|
868
|
+
: target === 'codex-desktop' ? codexDesktopPluginsDir()
|
|
666
869
|
: opencodePluginsDir();
|
|
870
|
+
mkdirSync(markerDir, { recursive: true });
|
|
667
871
|
writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
|
|
668
872
|
if (target === 'opencode' || target === 'all') {
|
|
669
873
|
console.log('Or describe your Huawei Cloud task in OpenCode');
|
|
@@ -696,15 +900,30 @@ async function cmdUninstall() {
|
|
|
696
900
|
console.log('\n[WorkBuddy]');
|
|
697
901
|
uninstallWorkBuddy();
|
|
698
902
|
}
|
|
699
|
-
if (target === 'codex' || target === 'all') {
|
|
903
|
+
if (target === 'codex-desktop' || target === 'codex' || target === 'all') {
|
|
700
904
|
console.log('\n[Codex]');
|
|
701
905
|
uninstallCodexDesktop();
|
|
702
|
-
if (
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
906
|
+
if (target === 'codex' || target === 'all') {
|
|
907
|
+
if (!hasCodexCLI()) {
|
|
908
|
+
console.log(' \x1b[33mCodex CLI not found. Run "npm uninstall -g codex" to fully remove.\x1b[0m');
|
|
909
|
+
} else {
|
|
910
|
+
uninstallCodex();
|
|
911
|
+
}
|
|
706
912
|
}
|
|
707
913
|
}
|
|
914
|
+
if (target === 'all') {
|
|
915
|
+
const vaultPath = globalCredentialsPath();
|
|
916
|
+
if (removeIfExists(vaultPath)) {
|
|
917
|
+
console.log(' Removed credential vault');
|
|
918
|
+
}
|
|
919
|
+
const vaultDir = dirname(vaultPath);
|
|
920
|
+
try {
|
|
921
|
+
if (existsSync(vaultDir) && readdirSync(vaultDir).length === 0) {
|
|
922
|
+
rmSync(vaultDir, { recursive: true, force: true });
|
|
923
|
+
console.log(` Removed empty directory: ${vaultDir}`);
|
|
924
|
+
}
|
|
925
|
+
} catch {}
|
|
926
|
+
}
|
|
708
927
|
console.log(`\n\x1b[32mUninstall complete.\x1b[0m`);
|
|
709
928
|
}
|
|
710
929
|
|
|
@@ -783,11 +1002,11 @@ async function cmdDoctor() {
|
|
|
783
1002
|
if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) { mcpConfigured = true; mcpCfgTarget = 'OpenCode'; }
|
|
784
1003
|
} catch {}
|
|
785
1004
|
}
|
|
786
|
-
const codexCfg =
|
|
1005
|
+
const codexCfg = codexConfigToml();
|
|
787
1006
|
if (!mcpConfigured && existsSync(codexCfg)) {
|
|
788
1007
|
try {
|
|
789
|
-
const cfg =
|
|
790
|
-
if (cfg.
|
|
1008
|
+
const cfg = readFileSync(codexCfg, 'utf8');
|
|
1009
|
+
if (cfg.includes('[mcp_servers.huaweicloud-devkit]')) { mcpConfigured = true; mcpCfgTarget = 'Codex Desktop'; }
|
|
791
1010
|
} catch {}
|
|
792
1011
|
}
|
|
793
1012
|
const workbuddyCfg = workbuddyMcpConfigFile();
|
|
@@ -822,7 +1041,7 @@ async function cmdDoctor() {
|
|
|
822
1041
|
// Check auth
|
|
823
1042
|
const authCheck = spawnSync(`"${hcloudBin}" configure list`, [], { shell: true, windowsHide: true, stdio: 'pipe', timeout: 5000 });
|
|
824
1043
|
const hasAuth = authCheck.status === 0 && /access.?key/i.test(authCheck.stdout.toString());
|
|
825
|
-
check('hcloud credentials configured', hasAuth, 'Run:
|
|
1044
|
+
check('hcloud credentials configured', hasAuth, 'Run: npx huaweicloud-devkit auth init');
|
|
826
1045
|
}
|
|
827
1046
|
|
|
828
1047
|
// Skills
|
|
@@ -860,6 +1079,7 @@ async function cmdDoctor() {
|
|
|
860
1079
|
// Detect "installed but not restarted" — check all supported agents
|
|
861
1080
|
const installedMarkers = [
|
|
862
1081
|
{ path: join(opencodePluginsDir(), '.installed'), name: 'OpenCode' },
|
|
1082
|
+
{ path: join(codexDesktopPluginsDir(), '.installed'), name: 'Codex Desktop' },
|
|
863
1083
|
{ path: join(workbuddyPluginsDir(), '.installed'), name: 'WorkBuddy' },
|
|
864
1084
|
{ path: join(codeartsPluginsDir(), '.installed'), name: 'CodeArts' },
|
|
865
1085
|
];
|
|
@@ -878,13 +1098,105 @@ async function cmdUpdate() {
|
|
|
878
1098
|
console.log(BANNER);
|
|
879
1099
|
const target = parseTarget();
|
|
880
1100
|
|
|
881
|
-
if (target === 'opencode'
|
|
882
|
-
if (!existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))
|
|
883
|
-
|
|
884
|
-
|
|
1101
|
+
if (target === 'opencode') {
|
|
1102
|
+
if (!existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1103
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
console.log('[OpenCode]');
|
|
1107
|
+
await updateOpenCode();
|
|
1108
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1109
|
+
console.log(`\x1b[33mMCP 工具在重启 OpenCode 会话后才生效。\x1b[0m`);
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (target === 'codex-desktop') {
|
|
1114
|
+
if (!existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1115
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
console.log('[Codex Desktop]');
|
|
1119
|
+
await updateCodexDesktop();
|
|
1120
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1121
|
+
console.log(`\x1b[33mMCP 工具在重启 Codex Desktop 会话后才生效。\x1b[0m`);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (target === 'codex') {
|
|
1126
|
+
if (!hasCodexCLI()) {
|
|
1127
|
+
console.log(` \x1b[31mCodex CLI not found.\x1b[0m`);
|
|
1128
|
+
if (process.platform === 'win32') {
|
|
1129
|
+
console.log(` \x1b[33mTip: use --target codex-desktop for Codex Desktop on Windows\x1b[0m`);
|
|
1130
|
+
}
|
|
1131
|
+
console.log(` \x1b[31mInstall Codex CLI: https://github.com/openai/codex-cli\x1b[0m`);
|
|
1132
|
+
process.exitCode = 1;
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
console.log('[Codex]');
|
|
1136
|
+
installCodex();
|
|
1137
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1138
|
+
console.log(`\x1b[33mRestart the Codex session for changes to take effect.\x1b[0m`);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
if (target === 'codearts') {
|
|
1143
|
+
if (!existsSync(join(codeartsPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1144
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
console.log('[CodeArts]');
|
|
1148
|
+
await updateCodeArts();
|
|
1149
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1150
|
+
console.log(`\x1b[33mMCP 工具在重启 CodeArts 会话后才生效。\x1b[0m`);
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
if (target === 'workbuddy') {
|
|
1155
|
+
if (!existsSync(join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1156
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
console.log('[WorkBuddy]');
|
|
1160
|
+
await updateWorkBuddy();
|
|
1161
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1162
|
+
console.log(`\x1b[33mMCP 工具在重启 WorkBuddy 会话后才生效。\x1b[0m`);
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
if (target === 'all') {
|
|
1167
|
+
let updatedAny = false;
|
|
1168
|
+
if (existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1169
|
+
console.log('[OpenCode]');
|
|
1170
|
+
await updateOpenCode();
|
|
1171
|
+
updatedAny = true;
|
|
1172
|
+
}
|
|
1173
|
+
if (existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1174
|
+
console.log('\n[Codex Desktop]');
|
|
1175
|
+
await updateCodexDesktop();
|
|
1176
|
+
updatedAny = true;
|
|
1177
|
+
}
|
|
1178
|
+
if (existsSync(join(codeartsPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1179
|
+
console.log('\n[CodeArts]');
|
|
1180
|
+
await updateCodeArts();
|
|
1181
|
+
updatedAny = true;
|
|
1182
|
+
}
|
|
1183
|
+
if (existsSync(join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1184
|
+
console.log('\n[WorkBuddy]');
|
|
1185
|
+
await updateWorkBuddy();
|
|
1186
|
+
updatedAny = true;
|
|
1187
|
+
}
|
|
1188
|
+
if (codexStatus()) {
|
|
1189
|
+
console.log('\n[Codex]');
|
|
1190
|
+
installCodex();
|
|
1191
|
+
updatedAny = true;
|
|
1192
|
+
}
|
|
1193
|
+
if (!updatedAny) {
|
|
885
1194
|
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
886
1195
|
return;
|
|
887
1196
|
}
|
|
1197
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1198
|
+
console.log(`\x1b[33mMCP 工具在重启各 agent 会话后才生效。\x1b[0m`);
|
|
1199
|
+
return;
|
|
888
1200
|
}
|
|
889
1201
|
|
|
890
1202
|
await cmdUninstall();
|
|
@@ -951,9 +1263,24 @@ async function cmdInstallHcloud() {
|
|
|
951
1263
|
// Clean up zip
|
|
952
1264
|
rmSync(zipPath, { force: true });
|
|
953
1265
|
|
|
954
|
-
// Add to PATH
|
|
1266
|
+
// Add to user PATH (append + dedupe within the User scope only; never copy
|
|
1267
|
+
// session/system entries into the user PATH, and never use setx PATH which
|
|
1268
|
+
// overwrites the whole variable and truncates at 1024 chars).
|
|
955
1269
|
console.log(' Adding to user PATH...');
|
|
956
|
-
|
|
1270
|
+
const pathPs = [
|
|
1271
|
+
'$ErrorActionPreference = "Stop"',
|
|
1272
|
+
`$target = '${installDir.replace(/'/g, "''")}'`,
|
|
1273
|
+
'$cur = [Environment]::GetEnvironmentVariable("Path", "User")',
|
|
1274
|
+
'if (-not $cur) { $cur = "" }',
|
|
1275
|
+
'$parts = @($cur -split ";" | Where-Object { $_ -ne "" })',
|
|
1276
|
+
'if ($parts -notcontains $target) {',
|
|
1277
|
+
' [Environment]::SetEnvironmentVariable("Path", (@($parts) + $target) -join ";", "User")',
|
|
1278
|
+
' Write-Output " Added to user PATH (deduped): $target"',
|
|
1279
|
+
'} else {',
|
|
1280
|
+
' Write-Output " Already in user PATH: $target"',
|
|
1281
|
+
'}',
|
|
1282
|
+
].join('; ');
|
|
1283
|
+
spawnSync('powershell', ['-NoProfile', '-Command', pathPs], { stdio: 'inherit', windowsHide: true, timeout: 30000 });
|
|
957
1284
|
|
|
958
1285
|
console.log(`\n\x1b[32mInstall complete.\x1b[0m`);
|
|
959
1286
|
console.log(` Verify: ${join(installDir, 'hcloud.exe')} version`);
|
|
@@ -1012,11 +1339,191 @@ async function cmdInstallHcloud() {
|
|
|
1012
1339
|
|
|
1013
1340
|
console.log('\nAfter install, set HCLOUD_BIN if hcloud is not on PATH.');
|
|
1014
1341
|
console.log('\n\x1b[1m\x1b[33m=== Configure credentials SAFELY ===\x1b[0m');
|
|
1015
|
-
console.log('
|
|
1342
|
+
console.log(' Unified credentials (recommended): npx huaweicloud-devkit auth init');
|
|
1343
|
+
console.log(' KooCLI only (alternative): hcloud configure init');
|
|
1016
1344
|
console.log(' NEVER: hcloud configure set --cli-access-key=xxx (AK/SK in shell history!)');
|
|
1017
1345
|
console.log('\nThen run: npx huaweicloud-devkit doctor');
|
|
1018
1346
|
}
|
|
1019
1347
|
|
|
1348
|
+
function readLineQuestion(prompt) {
|
|
1349
|
+
return new Promise((resolve) => {
|
|
1350
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1351
|
+
rl.question(prompt, (answer) => {
|
|
1352
|
+
rl.close();
|
|
1353
|
+
resolve(answer.trim());
|
|
1354
|
+
});
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
async function readSecret(prompt) {
|
|
1359
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1360
|
+
throw new Error(
|
|
1361
|
+
`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.`
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
process.stdout.write(prompt);
|
|
1366
|
+
const wasRaw = process.stdin.isRaw;
|
|
1367
|
+
process.stdin.setRawMode(true);
|
|
1368
|
+
process.stdin.resume();
|
|
1369
|
+
|
|
1370
|
+
return new Promise((resolve) => {
|
|
1371
|
+
let value = '';
|
|
1372
|
+
const onData = (chunk) => {
|
|
1373
|
+
for (const ch of chunk.toString('utf8')) {
|
|
1374
|
+
if (ch === '\r' || ch === '\n') {
|
|
1375
|
+
cleanup();
|
|
1376
|
+
resolve(value.trim());
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
if (ch === '\u0003') {
|
|
1380
|
+
cleanup();
|
|
1381
|
+
process.exit(130);
|
|
1382
|
+
}
|
|
1383
|
+
if (ch === '\b' || ch === '\u007f') {
|
|
1384
|
+
value = value.slice(0, -1);
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
value += ch;
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
const cleanup = () => {
|
|
1391
|
+
process.stdin.setRawMode(wasRaw);
|
|
1392
|
+
process.stdin.pause();
|
|
1393
|
+
process.stdin.off('data', onData);
|
|
1394
|
+
process.stdout.write('\n');
|
|
1395
|
+
};
|
|
1396
|
+
process.stdin.on('data', onData);
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
function configureHcloud(credentials) {
|
|
1401
|
+
const hcloudBin = findHcloudBin() || (process.env.HCLOUD_BIN || 'hcloud');
|
|
1402
|
+
const args = [
|
|
1403
|
+
'configure',
|
|
1404
|
+
'set',
|
|
1405
|
+
`--cli-access-key=${credentials.ak}`,
|
|
1406
|
+
`--cli-secret-key=${credentials.sk}`,
|
|
1407
|
+
`--cli-region=${credentials.region || ''}`,
|
|
1408
|
+
];
|
|
1409
|
+
const r = spawnSync(hcloudBin, args, {
|
|
1410
|
+
shell: false,
|
|
1411
|
+
windowsHide: true,
|
|
1412
|
+
stdio: 'pipe',
|
|
1413
|
+
timeout: 30000,
|
|
1414
|
+
});
|
|
1415
|
+
return {
|
|
1416
|
+
ok: r.status === 0,
|
|
1417
|
+
code: r.status,
|
|
1418
|
+
error: String(r.stderr || '').trim().slice(0, 240),
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function printAuthAgents(agents = {}) {
|
|
1423
|
+
for (const [agent, info] of Object.entries(agents)) {
|
|
1424
|
+
console.log(` ${agent}: ${info.configured ? '[OK]' : '[MISSING]'}`);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
function printAuthStatus(status) {
|
|
1429
|
+
console.log(`Credentials vault: ${status.credentialsConfigured ? 'configured' : 'missing'} (${status.credentialsPath})`);
|
|
1430
|
+
console.log(`OBS config: ${status.obsConfigured ? 'configured' : 'missing'} (${status.obsConfigPath})`);
|
|
1431
|
+
console.log(`KooCLI: ${status.kooCliInstalled ? 'installed' : 'missing'}`);
|
|
1432
|
+
console.log('Agent MCP registration:');
|
|
1433
|
+
printAuthAgents(status.agents);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
async function cmdAuthInit() {
|
|
1437
|
+
console.log(BANNER);
|
|
1438
|
+
console.log('HuaweiCloud DevKit Unified Authentication Setup\n');
|
|
1439
|
+
console.log('\x1b[1m获取 AK/SK(如果还没有):\x1b[0m');
|
|
1440
|
+
console.log(' 1. 打开华为云"访问密钥"页签:');
|
|
1441
|
+
console.log(' https://console.huaweicloud.com/iam/?region=cn-north-4#/mine/accessKey');
|
|
1442
|
+
console.log(' 2. 点击"新增访问密钥",完成身份验证');
|
|
1443
|
+
console.log(' 3. 下载凭证文件(内含 AK 和 SK)。');
|
|
1444
|
+
console.log(' 注意:SK 只在创建密钥时显示一次,请妥善保存该文件。\n');
|
|
1445
|
+
|
|
1446
|
+
let ak = process.env.HW_ACCESS_KEY || '';
|
|
1447
|
+
let sk = process.env.HW_SECRET_KEY || '';
|
|
1448
|
+
let securityToken = process.env.HW_SECURITY_TOKEN || '';
|
|
1449
|
+
let region = process.env.HW_REGION || process.env.HUAWEICLOUD_REGION || '';
|
|
1450
|
+
|
|
1451
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
1452
|
+
if (!interactive && (!ak || !sk)) {
|
|
1453
|
+
console.error('\x1b[31mNon-interactive session detected. Provide credentials via environment variables instead:\x1b[0m');
|
|
1454
|
+
console.error(' HW_ACCESS_KEY, HW_SECRET_KEY');
|
|
1455
|
+
console.error(' (Or run "npx huaweicloud-devkit auth init" in a real terminal.)');
|
|
1456
|
+
process.exitCode = 1;
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
if (!ak) ak = await readLineQuestion('Access Key ID (AK): ');
|
|
1461
|
+
if (!sk) sk = await readSecret('Secret Access Key (SK): ');
|
|
1462
|
+
if (interactive && !securityToken) securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
|
|
1463
|
+
if (!region) region = 'cn-north-4';
|
|
1464
|
+
|
|
1465
|
+
if (!ak || !sk) {
|
|
1466
|
+
console.error('\nAK and SK are required.');
|
|
1467
|
+
process.exitCode = 1;
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
|
|
1472
|
+
const vaultPath = writeGlobalCredentials({ ak, sk, securityToken, region });
|
|
1473
|
+
|
|
1474
|
+
try {
|
|
1475
|
+
writeObsConfig({ ak, sk, securityToken, region });
|
|
1476
|
+
} catch (error) {
|
|
1477
|
+
console.log(`OBS config sync failed: ${error.message}`);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
if (findHcloudBin()) {
|
|
1481
|
+
const result = configureHcloud({ ak, sk, region });
|
|
1482
|
+
if (!result.ok) console.log(`KooCLI update failed: ${result.error || result.code}`);
|
|
1483
|
+
} else {
|
|
1484
|
+
console.log('KooCLI not found. Run "npx huaweicloud-devkit install-hcloud" and then "auth sync".');
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
console.log('\nCredentials synchronized.');
|
|
1488
|
+
console.log('\nNext steps:');
|
|
1489
|
+
console.log(' npx huaweicloud-devkit install --target all');
|
|
1490
|
+
console.log(' Restart your agent sessions.');
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
async function cmdAuthSync() {
|
|
1494
|
+
const target = parseTarget();
|
|
1495
|
+
console.log(BANNER);
|
|
1496
|
+
console.log('Synchronizing Huawei Cloud authentication...\n');
|
|
1497
|
+
|
|
1498
|
+
const credentials = readGlobalCredentials();
|
|
1499
|
+
if (!credentials?.ak || !credentials?.sk) {
|
|
1500
|
+
console.error('No global credentials found. Run "npx huaweicloud-devkit auth init" first.');
|
|
1501
|
+
process.exitCode = 1;
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
const result = syncAuth(target);
|
|
1506
|
+
if (result.ok) {
|
|
1507
|
+
console.log('Credentials synchronized.');
|
|
1508
|
+
} else {
|
|
1509
|
+
console.error(result.error);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
async function cmdAuthStatus() {
|
|
1514
|
+
const target = parseTarget();
|
|
1515
|
+
console.log(BANNER);
|
|
1516
|
+
console.log('HuaweiCloud DevKit Authentication Status\n');
|
|
1517
|
+
printAuthStatus(getAuthStatus(target));
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
async function cmdAuth() {
|
|
1521
|
+
const sub = (process.argv[3] || 'status').toLowerCase();
|
|
1522
|
+
if (sub === 'init' || sub === 'setup') return cmdAuthInit();
|
|
1523
|
+
if (sub === 'sync' || sub === 'refresh') return cmdAuthSync();
|
|
1524
|
+
return cmdAuthStatus();
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1020
1527
|
async function main() {
|
|
1021
1528
|
const cmd = process.argv[2] || 'help';
|
|
1022
1529
|
|
|
@@ -1047,6 +1554,9 @@ async function main() {
|
|
|
1047
1554
|
case 'install-hcloud':
|
|
1048
1555
|
await cmdInstallHcloud();
|
|
1049
1556
|
break;
|
|
1557
|
+
case 'auth':
|
|
1558
|
+
await cmdAuth();
|
|
1559
|
+
break;
|
|
1050
1560
|
case 'help':
|
|
1051
1561
|
case '--help':
|
|
1052
1562
|
case '-h':
|
|
@@ -1061,6 +1571,7 @@ async function main() {
|
|
|
1061
1571
|
console.log(' status Show installation status');
|
|
1062
1572
|
console.log(' doctor Self-check: hcloud, MCP, skills, auth');
|
|
1063
1573
|
console.log(' install-hcloud Show KooCLI install commands for your OS');
|
|
1574
|
+
console.log(' auth Manage unified auth: init | sync | status');
|
|
1064
1575
|
console.log(' help Show this help');
|
|
1065
1576
|
console.log('\nOptions:');
|
|
1066
1577
|
console.log(' --target Target agent: opencode (default), codex, codearts, workbuddy, all');
|
|
@@ -1070,6 +1581,9 @@ async function main() {
|
|
|
1070
1581
|
console.log(' npx huaweicloud-devkit install --target codearts');
|
|
1071
1582
|
console.log(' npx huaweicloud-devkit install --target workbuddy');
|
|
1072
1583
|
console.log(' npx huaweicloud-devkit install --target all');
|
|
1584
|
+
console.log(' npx huaweicloud-devkit auth init');
|
|
1585
|
+
console.log(' npx huaweicloud-devkit auth sync --target all');
|
|
1586
|
+
console.log(' npx huaweicloud-devkit auth status --target all');
|
|
1073
1587
|
break;
|
|
1074
1588
|
}
|
|
1075
1589
|
}
|