refactor(core): 移除 UI 交互层,保持 core 为纯能力模块

- 删除 terminal.ts 及其测试(UI 交互应属于 cli 包)
- 清理 index.ts 中的 CLI 入口代码
- 合并 lib.ts 导出到 index.ts
- 移除 commander、inquirer 依赖
- 更新 package.json 导出配置
This commit is contained in:
2025-12-13 00:42:07 +08:00
parent 5b7d62e793
commit 5d4afecd48
5 changed files with 95 additions and 1212 deletions
+84 -431
View File
@@ -1,36 +1,38 @@
#!/usr/bin/env node
export { Agent } from './core/agent.js';
export { toolRegistry, todoManager, initTaskContext, updateTaskDescription, updateSkillDescription } from './tools/index.js';
export { loadConfig, initConfig } from './utils/config.js';
export { SessionStorage } from './session/storage.js';
export { SessionManager } from './session/index.js';
export type { SessionData, SessionSummary } from './session/types.js';
import { Command } from 'commander';
import { Agent } from './core/agent.js';
import { TerminalUI } from './ui/terminal.js';
import { loadConfig, initConfig } from './utils/config.js';
import { toolRegistry, todoManager, initTaskContext, updateTaskDescription, updateSkillDescription } from './tools/index.js';
import { getPermissionManager, promptPermission } from './permission/index.js';
import { SessionManager } from './session/index.js';
import { agentRegistry } from './agent/index.js';
import { initLSP, shutdownLSP } from './lsp/index.js';
import { getCommandRegistry } from './commands/index.js';
import { getSkillRegistry } from './skills/index.js';
import {
// Types
export type { UserInput } from './types/index.js';
// Permission
export { getPermissionManager, promptPermission } from './permission/index.js';
// LSP
export { initLSP, shutdownLSP } from './lsp/index.js';
export {
printServerList,
installServer,
installAllServers,
showServerInfo,
} from './lsp/cli.js';
import {
getMCPManager,
loadMCPConfig,
createMCPToolAdapter,
} from './mcp/index.js';
// ============================================================================
// 库导出(供 server 等包使用)
// ============================================================================
export { Agent } from './core/agent.js';
export { toolRegistry } from './tools/index.js';
export { loadConfig } from './utils/config.js';
export { SessionStorage } from './session/storage.js';
export type { SessionData, SessionSummary } from './session/types.js';
// Skills
export { getSkillRegistry } from './skills/index.js';
// Image utils
export {
extractImageReferences,
loadImages,
loadImage,
formatFileSize,
isImagePath,
IMAGE_EXTENSIONS,
} from './utils/image.js';
export type { ImageInfo, ImageLoadResult } from './utils/image.js';
// Commands
export { getCommandRegistry, createCommandExecutor, createCommandManager } from './commands/index.js';
@@ -91,408 +93,59 @@ export type {
PathValidationResult,
} from './checkpoint/index.js';
const program = new Command();
// MCP 管理器实例
let mcpInitialized = false;
/**
* 初始化 MCP 系统
* 加载配置、连接服务器、注册工具
*/
async function initMCP(workdir: string): Promise<void> {
if (mcpInitialized) {
return;
}
const mcpConfig = loadMCPConfig(workdir);
// 如果没有 MCP 配置,跳过初始化
if (!mcpConfig.mcp || Object.keys(mcpConfig.mcp).length === 0) {
return;
}
const mcpManager = getMCPManager();
// 监听工具变化事件
mcpManager.on('tools:changed', () => {
registerMCPTools(mcpManager);
});
// 监听服务器事件(用于日志)
mcpManager.on('server:connected', (name) => {
console.log(`🔌 MCP 服务器已连接: ${name}`);
});
mcpManager.on('server:disconnected', (name) => {
console.log(`🔌 MCP 服务器已断开: ${name}`);
});
mcpManager.on('server:error', (name, error) => {
console.error(`❌ MCP 服务器 ${name} 错误:`, error);
});
try {
await mcpManager.initialize(mcpConfig);
registerMCPTools(mcpManager);
mcpInitialized = true;
// 显示 MCP 状态
const statuses = mcpManager.getServerStatuses();
const connected = statuses.filter((s) => s.status === 'connected');
if (connected.length > 0) {
const totalTools = connected.reduce((sum, s) => sum + s.toolCount, 0);
console.log(
`🔌 MCP: ${connected.length} 个服务器已连接,${totalTools} 个工具可用`
);
}
} catch (error) {
console.error(
'❌ MCP 初始化失败:',
error instanceof Error ? error.message : String(error)
);
}
}
/**
* 将 MCP 工具注册到工具注册表
*/
function registerMCPTools(
mcpManager: ReturnType<typeof getMCPManager>
): void {
const adapter = createMCPToolAdapter(mcpManager);
const mcpTools = mcpManager.getTools();
const adaptedTools = adapter.adaptTools(mcpTools);
// 注册到工具注册表
toolRegistry.registerAll(adaptedTools);
}
/**
* 关闭 MCP 系统
*/
async function shutdownMCP(): Promise<void> {
if (mcpInitialized) {
const mcpManager = getMCPManager();
await mcpManager.shutdown();
mcpInitialized = false;
}
}
program
.name('ai-assist')
.description('AI Terminal Assistant - 终端中的 AI 编程助手')
.version('1.0.0');
// 初始化命令
program
.command('init')
.description('初始化配置(设置 API Key 等)')
.action(async () => {
await initConfig();
});
// LSP 命令组
const lspCommand = program
.command('lsp')
.description('语言服务器管理');
lspCommand
.command('list')
.description('列出所有语言服务器及其安装状态')
.action(() => {
printServerList();
});
lspCommand
.command('install [servers...]')
.description('安装指定的语言服务器')
.option('-a, --all', '安装所有语言服务器')
.action(async (servers: string[], options: { all?: boolean }) => {
if (options.all) {
await installAllServers();
} else if (servers.length === 0) {
console.log('用法: ai-assist lsp install <server> [server2] ...');
console.log(' ai-assist lsp install --all');
console.log('\n运行 "ai-assist lsp list" 查看可用的服务器');
} else {
for (const server of servers) {
await installServer(server);
}
}
});
lspCommand
.command('info <server>')
.description('显示语言服务器详细信息')
.action((server: string) => {
showServerInfo(server);
});
// MCP 命令组
const mcpCommand = program.command('mcp').description('MCP 服务器管理');
mcpCommand
.command('list')
.description('列出所有 MCP 服务器及其状态')
.action(async () => {
const mcpConfig = loadMCPConfig(process.cwd());
if (!mcpConfig.mcp || Object.keys(mcpConfig.mcp).length === 0) {
console.log('没有配置 MCP 服务器');
console.log('\n配置方法:');
console.log(' 在 ~/.ai-assist/config.json 或 .ai-assist/config.json 中添加 mcp 配置');
console.log('\n示例:');
console.log(' {');
console.log(' "mcp": {');
console.log(' "filesystem": {');
console.log(' "type": "local",');
console.log(' "command": ["npx", "-y", "@anthropic-ai/mcp-server-filesystem", "/path/to/dir"]');
console.log(' }');
console.log(' }');
console.log(' }');
return;
}
const mcpManager = getMCPManager();
// 尝试连接以获取工具数量
try {
if (!mcpManager.isInitialized()) {
await mcpManager.initialize(mcpConfig);
}
} catch {
// 忽略连接错误,仍然显示配置的服务器
}
const statuses = mcpManager.getServerStatuses();
console.log('\nMCP 服务器列表:\n');
const statusIcons: Record<string, string> = {
connected: '✅',
connecting: '🔄',
disconnected: '⭕',
disabled: '🚫',
error: '❌',
};
for (const status of statuses) {
const icon = statusIcons[status.status] || '❓';
const toolInfo = status.toolCount > 0 ? ` (${status.toolCount} 个工具)` : '';
const errorInfo = status.error ? ` - ${status.error}` : '';
console.log(
` ${icon} ${status.name} [${status.type}] - ${status.status}${toolInfo}${errorInfo}`
);
}
console.log('');
// 关闭连接
await mcpManager.shutdown();
});
mcpCommand
.command('tools [server]')
.description('列出 MCP 服务器提供的工具')
.action(async (server?: string) => {
const mcpConfig = loadMCPConfig(process.cwd());
if (!mcpConfig.mcp || Object.keys(mcpConfig.mcp).length === 0) {
console.log('没有配置 MCP 服务器');
return;
}
const mcpManager = getMCPManager();
try {
if (!mcpManager.isInitialized()) {
await mcpManager.initialize(mcpConfig);
}
const tools = mcpManager.getTools();
if (tools.length === 0) {
console.log('没有可用的 MCP 工具');
return;
}
// 按服务器分组
const toolsByServer = new Map<string, typeof tools>();
for (const tool of tools) {
if (server && tool.server !== server) {
continue;
}
const serverTools = toolsByServer.get(tool.server) || [];
serverTools.push(tool);
toolsByServer.set(tool.server, serverTools);
}
if (toolsByServer.size === 0) {
console.log(server ? `服务器 "${server}" 没有提供工具` : '没有可用的工具');
return;
}
console.log('\nMCP 工具列表:\n');
for (const [serverName, serverTools] of toolsByServer) {
console.log(`📦 ${serverName}:`);
for (const tool of serverTools) {
console.log(` ${tool.name}`);
if (tool.description) {
console.log(` ${tool.description.substring(0, 80)}${tool.description.length > 80 ? '...' : ''}`);
}
}
console.log('');
}
} catch (error) {
console.error(
'获取工具列表失败:',
error instanceof Error ? error.message : String(error)
);
} finally {
await mcpManager.shutdown();
}
});
mcpCommand
.command('test <server>')
.description('测试 MCP 服务器连接')
.action(async (server: string) => {
const mcpConfig = loadMCPConfig(process.cwd());
if (!mcpConfig.mcp?.[server]) {
console.log(`❌ 未找到服务器配置: ${server}`);
return;
}
console.log(`🔄 正在连接 ${server}...`);
const mcpManager = getMCPManager();
try {
await mcpManager.initialize({
mcp: { [server]: mcpConfig.mcp[server] },
tools: mcpConfig.tools,
});
const status = mcpManager.getServerStatus(server);
if (status?.status === 'connected') {
console.log(`✅ 连接成功!`);
console.log(` 工具数量: ${status.toolCount}`);
const tools = mcpManager.getTools();
if (tools.length > 0) {
console.log(' 可用工具:');
for (const tool of tools) {
console.log(` - ${tool.originalName}`);
}
}
} else {
console.log(`❌ 连接失败: ${status?.error || '未知错误'}`);
}
} catch (error) {
console.error(
`❌ 连接失败:`,
error instanceof Error ? error.message : String(error)
);
} finally {
await mcpManager.shutdown();
}
});
// 初始化权限系统
function setupPermissions(): void {
const permissionManager = getPermissionManager();
permissionManager.setAskCallback(promptPermission);
}
// 单次查询命令
program
.command('ask <question>')
.description('单次提问(不进入交互模式)')
.action(async (question: string) => {
setupPermissions();
const config = loadConfig();
const agent = new Agent(config);
// 设置工具注册表(支持动态工具发现)
agent.setRegistry(toolRegistry);
try {
await agent.chat(question, (text) => {
process.stdout.write(text);
});
console.log('');
} catch (error) {
console.error(
'错误:',
error instanceof Error ? error.message : String(error)
);
process.exit(1);
}
});
// 默认:交互模式
program.action(async () => {
setupPermissions();
const config = loadConfig();
const agent = new Agent(config);
// 初始化 LSP 系统
initLSP(process.cwd());
// 初始化 MCP 系统(加载外部工具服务器)
await initMCP(process.cwd());
// 设置工具注册表(支持动态工具发现)
agent.setRegistry(toolRegistry);
// 初始化会话管理器(支持会话持久化)
const sessionManager = new SessionManager();
await sessionManager.init(process.cwd());
agent.setSessionManager(sessionManager);
// 初始化 todoManager(让 todo 工具可以访问会话)
todoManager.setSessionManager(sessionManager);
// 初始化 Agent 注册表(加载预设和用户配置)
await agentRegistry.init(process.cwd());
// 初始化 Task 工具上下文
initTaskContext(config, sessionManager);
updateTaskDescription();
// 初始化 Skill 注册表
const skillRegistry = getSkillRegistry();
await skillRegistry.initialize(process.cwd());
updateSkillDescription();
// 初始化 Command 注册表
const commandRegistry = getCommandRegistry();
await commandRegistry.initialize(process.cwd());
// 显示会话恢复信息
const session = sessionManager.getSession();
if (session && session.messages.length > 0) {
console.log(`\n📂 已恢复会话 (${session.messages.length} 条消息)`);
}
// 启动终端 UI
const ui = new TerminalUI(agent);
// 优雅退出
process.on('SIGINT', async () => {
console.log('\n\n👋 再见!');
await shutdownMCP();
await shutdownLSP();
await sessionManager.close();
ui.close();
process.exit(0);
});
await ui.start();
});
program.parse();
// Hooks
export {
HookManager,
getHookManager,
initHookManager,
resetHookManager,
loadProjectConfig,
loadHookConfig,
loadPluginList,
createDefaultConfig,
getConfigFilePath,
} from './hooks/index.js';
export type {
HookType,
HookConfig,
HookEvent,
HookEventListener,
ShellCommandConfig,
FileHookConfig,
Hooks,
Plugin,
PluginInput,
ToolExecuteBeforeInput,
ToolExecuteBeforeOutput,
ToolExecuteAfterInput,
ToolExecuteAfterOutput,
SessionStartInput,
SessionEndInput,
MessageBeforeInput,
MessageBeforeOutput,
MessageAfterInput,
FileChangeInput,
FileChangeOutput,
ProjectConfig,
} from './hooks/index.js';
// Agent Registry & Presets
export { agentRegistry, AgentRegistry } from './agent/index.js';
export { loadAgentConfig, saveAgentConfig, getConfigTemplate } from './agent/index.js';
export { presetAgents, isPresetAgent, getPresetAgentNames } from './agent/index.js';
export type {
AgentMode,
AgentInfo,
AgentConfigFile,
AgentModelConfig,
AgentToolConfig,
AgentPermission,
} from './agent/index.js';
// MCP
export {
getMCPManager,
loadMCPConfig,
createMCPToolAdapter,
} from './mcp/index.js';