huaweicloud-devkit 1.0.2-dev.1 → 1.0.2-dev.3
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 +25 -2
- package/README.zh-CN.md +25 -2
- 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 +16 -1
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +21 -0
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +67 -0
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +87 -0
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +74 -0
- package/plugins/huaweicloud-core/src/auth/service.mjs +63 -0
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +97 -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 +513 -66
- package/plugins/huaweicloud-core/src/tools.mjs +166 -0
- 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, '..');
|
|
@@ -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',
|
|
@@ -296,6 +308,101 @@ function uninstallOpenCode() {
|
|
|
296
308
|
removeOpenCodeConfig();
|
|
297
309
|
}
|
|
298
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
|
+
|
|
299
406
|
async function installCodexDesktop() {
|
|
300
407
|
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
301
408
|
const commandsSrc = join(PACKAGE_ROOT, 'integrations', 'opencode', 'commands');
|
|
@@ -313,7 +420,7 @@ async function installCodexDesktop() {
|
|
|
313
420
|
console.log(` Safety Policy -> ${join(codexDesktopPluginsDir(), 'safety')}`);
|
|
314
421
|
|
|
315
422
|
// Generate .mcp.json with absolute paths for Codex Desktop MCP server discovery
|
|
316
|
-
const mcpServerAbsPath =
|
|
423
|
+
const mcpServerAbsPath = codexMcpServerPath();
|
|
317
424
|
const mcpConfig = {
|
|
318
425
|
mcpServers: {
|
|
319
426
|
'huaweicloud-devkit': {
|
|
@@ -333,29 +440,50 @@ async function installCodexDesktop() {
|
|
|
333
440
|
console.log(` Plugin Manifest -> ${join(codexDesktopPluginsDir(), '.codex-plugin')}`);
|
|
334
441
|
}
|
|
335
442
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
+
},
|
|
475
|
+
};
|
|
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')}`);
|
|
354
483
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
writeFileSync(
|
|
358
|
-
console.log(` Config updated: ${configPath}`);
|
|
484
|
+
|
|
485
|
+
ensureCodexConfigSection(mcpServerAbsPath);
|
|
486
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
359
487
|
}
|
|
360
488
|
|
|
361
489
|
function uninstallCodexDesktop() {
|
|
@@ -386,32 +514,28 @@ function uninstallCodexDesktop() {
|
|
|
386
514
|
if (removeIfExists(codexDesktopPluginsDir())) {
|
|
387
515
|
console.log(' Removed MCP server and safety policy');
|
|
388
516
|
}
|
|
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
|
-
}
|
|
517
|
+
removeCodexConfigSection();
|
|
403
518
|
}
|
|
404
519
|
|
|
405
520
|
function registerCodeartsMcp(configPath) {
|
|
406
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, '/');
|
|
407
525
|
let config = {};
|
|
408
526
|
if (existsSync(configPath)) {
|
|
409
|
-
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
|
+
}
|
|
410
537
|
}
|
|
411
538
|
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
539
|
config.mcpServers['huaweicloud-devkit'] = {
|
|
416
540
|
command: 'node',
|
|
417
541
|
args: [mcpPath],
|
|
@@ -443,6 +567,28 @@ async function installCodeArts() {
|
|
|
443
567
|
registerCodeartsMcp(codeartsProjectMcpSettingsFile());
|
|
444
568
|
}
|
|
445
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
|
+
|
|
446
592
|
function uninstallCodeArts() {
|
|
447
593
|
let removed = 0;
|
|
448
594
|
for (const skillsDir of [codeartsSkillsDir(), codeartsProjectSkillsDir()]) {
|
|
@@ -492,6 +638,38 @@ function codeartsStatus() {
|
|
|
492
638
|
}
|
|
493
639
|
}
|
|
494
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
|
+
|
|
495
673
|
async function installWorkBuddy() {
|
|
496
674
|
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
497
675
|
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
@@ -506,25 +684,26 @@ async function installWorkBuddy() {
|
|
|
506
684
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
507
685
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
508
686
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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());
|
|
528
707
|
}
|
|
529
708
|
|
|
530
709
|
function uninstallWorkBuddy() {
|
|
@@ -903,13 +1082,105 @@ async function cmdUpdate() {
|
|
|
903
1082
|
console.log(BANNER);
|
|
904
1083
|
const target = parseTarget();
|
|
905
1084
|
|
|
906
|
-
if (target === 'opencode'
|
|
907
|
-
if (!existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))
|
|
908
|
-
|
|
909
|
-
|
|
1085
|
+
if (target === 'opencode') {
|
|
1086
|
+
if (!existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1087
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
console.log('[OpenCode]');
|
|
1091
|
+
await updateOpenCode();
|
|
1092
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1093
|
+
console.log(`\x1b[33mMCP 工具在重启 OpenCode 会话后才生效。\x1b[0m`);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (target === 'codex-desktop') {
|
|
1098
|
+
if (!existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
910
1099
|
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
911
1100
|
return;
|
|
912
1101
|
}
|
|
1102
|
+
console.log('[Codex Desktop]');
|
|
1103
|
+
await updateCodexDesktop();
|
|
1104
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1105
|
+
console.log(`\x1b[33mMCP 工具在重启 Codex Desktop 会话后才生效。\x1b[0m`);
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
if (target === 'codex') {
|
|
1110
|
+
if (!hasCodexCLI()) {
|
|
1111
|
+
console.log(` \x1b[31mCodex CLI not found.\x1b[0m`);
|
|
1112
|
+
if (process.platform === 'win32') {
|
|
1113
|
+
console.log(` \x1b[33mTip: use --target codex-desktop for Codex Desktop on Windows\x1b[0m`);
|
|
1114
|
+
}
|
|
1115
|
+
console.log(` \x1b[31mInstall Codex CLI: https://github.com/openai/codex-cli\x1b[0m`);
|
|
1116
|
+
process.exitCode = 1;
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
console.log('[Codex]');
|
|
1120
|
+
installCodex();
|
|
1121
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1122
|
+
console.log(`\x1b[33mRestart the Codex session for changes to take effect.\x1b[0m`);
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
if (target === 'codearts') {
|
|
1127
|
+
if (!existsSync(join(codeartsPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1128
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
console.log('[CodeArts]');
|
|
1132
|
+
await updateCodeArts();
|
|
1133
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1134
|
+
console.log(`\x1b[33mMCP 工具在重启 CodeArts 会话后才生效。\x1b[0m`);
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
if (target === 'workbuddy') {
|
|
1139
|
+
if (!existsSync(join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1140
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
console.log('[WorkBuddy]');
|
|
1144
|
+
await updateWorkBuddy();
|
|
1145
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1146
|
+
console.log(`\x1b[33mMCP 工具在重启 WorkBuddy 会话后才生效。\x1b[0m`);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
if (target === 'all') {
|
|
1151
|
+
let updatedAny = false;
|
|
1152
|
+
if (existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1153
|
+
console.log('[OpenCode]');
|
|
1154
|
+
await updateOpenCode();
|
|
1155
|
+
updatedAny = true;
|
|
1156
|
+
}
|
|
1157
|
+
if (existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1158
|
+
console.log('\n[Codex Desktop]');
|
|
1159
|
+
await updateCodexDesktop();
|
|
1160
|
+
updatedAny = true;
|
|
1161
|
+
}
|
|
1162
|
+
if (existsSync(join(codeartsPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1163
|
+
console.log('\n[CodeArts]');
|
|
1164
|
+
await updateCodeArts();
|
|
1165
|
+
updatedAny = true;
|
|
1166
|
+
}
|
|
1167
|
+
if (existsSync(join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
1168
|
+
console.log('\n[WorkBuddy]');
|
|
1169
|
+
await updateWorkBuddy();
|
|
1170
|
+
updatedAny = true;
|
|
1171
|
+
}
|
|
1172
|
+
if (codexStatus()) {
|
|
1173
|
+
console.log('\n[Codex]');
|
|
1174
|
+
installCodex();
|
|
1175
|
+
updatedAny = true;
|
|
1176
|
+
}
|
|
1177
|
+
if (!updatedAny) {
|
|
1178
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
1182
|
+
console.log(`\x1b[33mMCP 工具在重启各 agent 会话后才生效。\x1b[0m`);
|
|
1183
|
+
return;
|
|
913
1184
|
}
|
|
914
1185
|
|
|
915
1186
|
await cmdUninstall();
|
|
@@ -1042,6 +1313,175 @@ async function cmdInstallHcloud() {
|
|
|
1042
1313
|
console.log('\nThen run: npx huaweicloud-devkit doctor');
|
|
1043
1314
|
}
|
|
1044
1315
|
|
|
1316
|
+
function readLineQuestion(prompt) {
|
|
1317
|
+
return new Promise((resolve) => {
|
|
1318
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1319
|
+
rl.question(prompt, (answer) => {
|
|
1320
|
+
rl.close();
|
|
1321
|
+
resolve(answer.trim());
|
|
1322
|
+
});
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
async function readSecret(prompt) {
|
|
1327
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1328
|
+
return readLineQuestion(prompt);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
process.stdout.write(prompt);
|
|
1332
|
+
const wasRaw = process.stdin.isRaw;
|
|
1333
|
+
process.stdin.setRawMode(true);
|
|
1334
|
+
process.stdin.resume();
|
|
1335
|
+
|
|
1336
|
+
return new Promise((resolve) => {
|
|
1337
|
+
let value = '';
|
|
1338
|
+
const onData = (chunk) => {
|
|
1339
|
+
for (const ch of chunk.toString('utf8')) {
|
|
1340
|
+
if (ch === '\r' || ch === '\n') {
|
|
1341
|
+
cleanup();
|
|
1342
|
+
resolve(value.trim());
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
if (ch === '\u0003') {
|
|
1346
|
+
cleanup();
|
|
1347
|
+
process.exit(130);
|
|
1348
|
+
}
|
|
1349
|
+
if (ch === '\b' || ch === '\u007f') {
|
|
1350
|
+
value = value.slice(0, -1);
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
value += ch;
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
const cleanup = () => {
|
|
1357
|
+
process.stdin.setRawMode(wasRaw);
|
|
1358
|
+
process.stdin.pause();
|
|
1359
|
+
process.stdin.off('data', onData);
|
|
1360
|
+
process.stdout.write('\n');
|
|
1361
|
+
};
|
|
1362
|
+
process.stdin.on('data', onData);
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function configureHcloud(credentials) {
|
|
1367
|
+
const hcloudBin = findHcloudBin() || (process.env.HCLOUD_BIN || 'hcloud');
|
|
1368
|
+
const args = [
|
|
1369
|
+
'configure',
|
|
1370
|
+
'set',
|
|
1371
|
+
`--cli-access-key=${credentials.ak}`,
|
|
1372
|
+
`--cli-secret-key=${credentials.sk}`,
|
|
1373
|
+
`--cli-region=${credentials.region || ''}`,
|
|
1374
|
+
];
|
|
1375
|
+
const r = spawnSync(hcloudBin, args, {
|
|
1376
|
+
shell: false,
|
|
1377
|
+
windowsHide: true,
|
|
1378
|
+
stdio: 'pipe',
|
|
1379
|
+
timeout: 30000,
|
|
1380
|
+
});
|
|
1381
|
+
return {
|
|
1382
|
+
ok: r.status === 0,
|
|
1383
|
+
code: r.status,
|
|
1384
|
+
error: String(r.stderr || '').trim().slice(0, 240),
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function printAuthAgents(agents = {}) {
|
|
1389
|
+
for (const [agent, info] of Object.entries(agents)) {
|
|
1390
|
+
console.log(` ${agent}: ${info.configured ? '[OK]' : '[MISSING]'}`);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
function printAuthStatus(status) {
|
|
1395
|
+
console.log(`Credentials vault: ${status.credentialsConfigured ? 'configured' : 'missing'} (${status.credentialsPath})`);
|
|
1396
|
+
console.log(`OBS config: ${status.obsConfigured ? 'configured' : 'missing'} (${status.obsConfigPath})`);
|
|
1397
|
+
console.log(`KooCLI: ${status.kooCliInstalled ? 'installed' : 'missing'}`);
|
|
1398
|
+
console.log('Agent MCP registration:');
|
|
1399
|
+
printAuthAgents(status.agents);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
async function cmdAuthInit() {
|
|
1403
|
+
console.log(BANNER);
|
|
1404
|
+
console.log('HuaweiCloud DevKit Unified Authentication Setup\n');
|
|
1405
|
+
|
|
1406
|
+
let ak = process.env.HW_ACCESS_KEY || '';
|
|
1407
|
+
let sk = process.env.HW_SECRET_KEY || '';
|
|
1408
|
+
let securityToken = process.env.HW_SECURITY_TOKEN || '';
|
|
1409
|
+
let region = process.env.HW_REGION || process.env.HUAWEICLOUD_REGION || '';
|
|
1410
|
+
|
|
1411
|
+
if (!ak) ak = await readSecret('Access Key ID (AK): ');
|
|
1412
|
+
if (!sk) sk = await readSecret('Secret Access Key (SK): ');
|
|
1413
|
+
if (!securityToken) securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
|
|
1414
|
+
if (!region) region = await readLineQuestion('Region (e.g. cn-north-4): ');
|
|
1415
|
+
|
|
1416
|
+
if (!ak || !sk) {
|
|
1417
|
+
console.error('\nAK and SK are required.');
|
|
1418
|
+
process.exitCode = 1;
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
if (!region) {
|
|
1422
|
+
console.error('\nRegion is required to generate the OBS endpoint.');
|
|
1423
|
+
process.exitCode = 1;
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
const vaultPath = writeGlobalCredentials({ ak, sk, securityToken, region });
|
|
1428
|
+
console.log(`\nCredentials stored: ${vaultPath}`);
|
|
1429
|
+
|
|
1430
|
+
try {
|
|
1431
|
+
const obs = writeObsConfig({ ak, sk, securityToken, region });
|
|
1432
|
+
console.log(`OBS config synced: ${obs.path} (${obs.endpoint})`);
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
console.log(`OBS config sync failed: ${error.message}`);
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
if (findHcloudBin()) {
|
|
1438
|
+
const result = configureHcloud({ ak, sk, region });
|
|
1439
|
+
console.log(result.ok ? 'KooCLI profile updated.' : `KooCLI update failed: ${result.error || result.code}`);
|
|
1440
|
+
} else {
|
|
1441
|
+
console.log('KooCLI not found. Run "npx huaweicloud-devkit install-hcloud" and then "auth sync".');
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
console.log('\nNext steps:');
|
|
1445
|
+
console.log(' npx huaweicloud-devkit install --target all');
|
|
1446
|
+
console.log(' Restart your agent sessions.');
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
async function cmdAuthSync() {
|
|
1450
|
+
const target = parseTarget();
|
|
1451
|
+
console.log(BANNER);
|
|
1452
|
+
console.log('Synchronizing Huawei Cloud authentication...\n');
|
|
1453
|
+
|
|
1454
|
+
const credentials = readGlobalCredentials();
|
|
1455
|
+
if (!credentials?.ak || !credentials?.sk) {
|
|
1456
|
+
console.error('No global credentials found. Run "npx huaweicloud-devkit auth init" first.');
|
|
1457
|
+
process.exitCode = 1;
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
const result = syncAuth(target);
|
|
1462
|
+
if (result.ok) {
|
|
1463
|
+
console.log(`OBS config synced: ${result.obs.path} (${result.obs.endpoint})`);
|
|
1464
|
+
} else {
|
|
1465
|
+
console.error(result.error);
|
|
1466
|
+
}
|
|
1467
|
+
console.log('Agent MCP registration:');
|
|
1468
|
+
printAuthAgents(result.agents);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
async function cmdAuthStatus() {
|
|
1472
|
+
const target = parseTarget();
|
|
1473
|
+
console.log(BANNER);
|
|
1474
|
+
console.log('HuaweiCloud DevKit Authentication Status\n');
|
|
1475
|
+
printAuthStatus(getAuthStatus(target));
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
async function cmdAuth() {
|
|
1479
|
+
const sub = (process.argv[3] || 'status').toLowerCase();
|
|
1480
|
+
if (sub === 'init' || sub === 'setup') return cmdAuthInit();
|
|
1481
|
+
if (sub === 'sync' || sub === 'refresh') return cmdAuthSync();
|
|
1482
|
+
return cmdAuthStatus();
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1045
1485
|
async function main() {
|
|
1046
1486
|
const cmd = process.argv[2] || 'help';
|
|
1047
1487
|
|
|
@@ -1072,6 +1512,9 @@ async function main() {
|
|
|
1072
1512
|
case 'install-hcloud':
|
|
1073
1513
|
await cmdInstallHcloud();
|
|
1074
1514
|
break;
|
|
1515
|
+
case 'auth':
|
|
1516
|
+
await cmdAuth();
|
|
1517
|
+
break;
|
|
1075
1518
|
case 'help':
|
|
1076
1519
|
case '--help':
|
|
1077
1520
|
case '-h':
|
|
@@ -1086,6 +1529,7 @@ async function main() {
|
|
|
1086
1529
|
console.log(' status Show installation status');
|
|
1087
1530
|
console.log(' doctor Self-check: hcloud, MCP, skills, auth');
|
|
1088
1531
|
console.log(' install-hcloud Show KooCLI install commands for your OS');
|
|
1532
|
+
console.log(' auth Manage unified auth: init | sync | status');
|
|
1089
1533
|
console.log(' help Show this help');
|
|
1090
1534
|
console.log('\nOptions:');
|
|
1091
1535
|
console.log(' --target Target agent: opencode (default), codex, codearts, workbuddy, all');
|
|
@@ -1095,6 +1539,9 @@ async function main() {
|
|
|
1095
1539
|
console.log(' npx huaweicloud-devkit install --target codearts');
|
|
1096
1540
|
console.log(' npx huaweicloud-devkit install --target workbuddy');
|
|
1097
1541
|
console.log(' npx huaweicloud-devkit install --target all');
|
|
1542
|
+
console.log(' npx huaweicloud-devkit auth init');
|
|
1543
|
+
console.log(' npx huaweicloud-devkit auth sync --target all');
|
|
1544
|
+
console.log(' npx huaweicloud-devkit auth status --target all');
|
|
1098
1545
|
break;
|
|
1099
1546
|
}
|
|
1100
1547
|
}
|