refactor(core): 移除 UI 交互层,保持 core 为纯能力模块
- 删除 terminal.ts 及其测试(UI 交互应属于 cli 包) - 清理 index.ts 中的 CLI 入口代码 - 合并 lib.ts 导出到 index.ts - 移除 commander、inquirer 依赖 - 更新 package.json 导出配置
This commit is contained in:
+84
-431
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user