File size: 4,224 Bytes
1140c43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#!/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 <init|install|remove|configure|enable|disable|list|call|mcp|http> <profile绝对路径> [包/工具] [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;
}