#!/usr/bin/env node import { initializeProfile, startRuntime, removeBundleOffline, configureProfile, pluginViews, environmentAllowlist, installStreamRedaction, assertProfileStopped } from '@message/runtime'; import { randomUUID } from 'node:crypto'; const [command, profile, ...args] = process.argv.slice(2); // 仅worker读取获准的环境引用值;stdout协议和stderr启动诊断都进行已知值脱敏。 if (command === 'http' || command === 'mcp' || command === 'call') { const secrets = new Set([...environmentAllowlist()].map(name => process.env[name]).filter(Boolean)); installStreamRedaction(secrets); } const commands = new Set(['init', 'install', 'remove', 'configure', 'enable', 'disable', 'list', 'call', 'mcp', 'http']); const print = value => process.stdout.write(`${JSON.stringify(value)}\n`); let runtime; try { if (!commands.has(command) || !profile) { throw new Error('用法: node apps/cloud/src/cli.mjs [包/工具] [JSON参数]'); } if (command === 'init') { print({ profile: initializeProfile(profile) }); } else if (command === 'configure') { print(await configureProfile(profile, args[0], JSON.parse(args[1] ?? '{}'))); } else if (command === 'remove') { const abort = new AbortController(); const cancel = () => abort.abort(); process.on('SIGTERM', cancel); process.on('SIGINT', cancel); const result = await removeBundleOffline(profile, args[0], abort.signal); print(result); if (result.application === 'failed') process.exitCode = 1; process.off('SIGTERM', cancel); process.off('SIGINT', cancel); } else { if (command === 'install') assertProfileStopped(profile); runtime = await startRuntime(profile, { transport: command === 'mcp' ? 'stdio' : command === 'http' ? 'http' : 'disabled', port: command === 'http' ? Number(args[0] ?? 0) : 0, management: ['install', 'enable', 'disable', 'list'].includes(command) }); const manager = runtime.ctx.pluginManager; if (command === 'mcp' || command === 'http') { // stdio 完全交给 MCP;HTTP 仅公布本机地址,收到信号时释放整个 profile。 if (command === 'http') { const endpoint = runtime.ctx.get('messageMcpExport'); if (!endpoint?.url) throw new Error('HTTP MCP 插件未成功启动'); process.stderr.write(`${JSON.stringify(endpoint)}\n`); } let closing; const close = () => closing ??= runtime.close().catch(error => { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }); if (command === 'mcp') process.stdin.on('end', close); process.once('SIGTERM', close); process.once('SIGINT', close); } else { let result; if (command === 'list') result = { version: runtime.version, bundles: await manager.listBundles(), plugins: await pluginViews(runtime), tools: runtime.ctx.tools.schemas() }; else if (!args[0]) throw new Error('缺少包名、安装路径或工具名称'); else if (command === 'install') { const requestId = randomUUID(); // 取消交给官方manager等待子进程退出与manifest恢复,不能只杀宿主留下pnpm。 const cancel = () => { void manager.cancelInstall(requestId).catch(error => process.stderr.write(`${error.message}\n`)); }; process.on('SIGTERM', cancel); process.on('SIGINT', cancel); try { result = await manager.installBundle(args[0], { requestId }); } finally { process.off('SIGTERM', cancel); process.off('SIGINT', cancel); } } else if (command === 'enable' || command === 'disable') result = await manager.setBundleEnabled(args[0], command === 'enable'); else result = await runtime.ctx.tools.execute({ callId: 'message-cli', name: args[0], arguments: JSON.parse(args[1] ?? '{}'), signal: new AbortController().signal, }); print(result); if (result.application === 'failed' || result.isError) process.exitCode = 1; await runtime.close(); } } } catch (error) { process.stderr.write(`${error.stack ?? error.message}\n`); if (runtime) await runtime.close(); process.exitCode = 1; }