feat: 重构为 Monorepo 架构并实现 HTTP Server

架构变更:
- 采用 pnpm workspaces 实现 Monorepo 结构
- 将现有代码迁移到 packages/core
- 新增 packages/server HTTP 服务层

Server 功能:
- REST API: 会话管理、工具管理、配置管理
- WebSocket: 实时双向通信支持
- SSE: 服务端事件推送
- Hono + Bun 作为运行时

API 端点:
- GET/POST /api/sessions - 会话 CRUD
- GET/POST /api/sessions/:id/messages - 消息管理
- GET /api/sessions/:id/events - SSE 事件流
- WS /api/ws/:sessionId - WebSocket 连接
- GET/POST /api/tools - 工具管理
- GET/PUT /api/config - 配置管理
This commit is contained in:
2025-12-12 10:42:20 +08:00
parent 59dbed926e
commit 5e32375f0e
301 changed files with 3281 additions and 43 deletions
+357
View File
@@ -0,0 +1,357 @@
/**
* 内置 Skills
*
* 提供一些常用的预定义 Skills
*/
import type { Skill } from '../types.js';
/**
* 代码审查 Skill
*/
export const codeReviewSkill: Skill = {
name: 'code-review',
displayName: '代码审查',
description: '对代码进行全面审查,检查潜在问题、最佳实践和改进建议',
category: 'development',
promptTemplate: `请对以下代码进行全面审查:
{{code}}
审查重点:
{{#if focus}}
- {{focus}}
{{else}}
- 代码质量和可读性
- 潜在的 bug 和错误
- 性能问题
- 安全隐患
- 最佳实践遵循情况
{{/if}}
请提供:
1. 发现的问题列表(按严重程度排序)
2. 具体的改进建议
3. 代码中做得好的地方`,
parameters: {
code: {
type: 'string',
description: '要审查的代码',
required: true,
},
focus: {
type: 'string',
description: '审查重点(可选)',
required: false,
},
},
keywords: ['review', 'code', 'quality', '审查', '代码', '质量'],
source: 'builtin',
enabled: true,
};
/**
* 代码解释 Skill
*/
export const explainCodeSkill: Skill = {
name: 'explain-code',
displayName: '代码解释',
description: '详细解释代码的功能、逻辑和工作原理',
category: 'development',
promptTemplate: `请详细解释以下代码:
{{code}}
{{#if level}}
解释级别:{{level}}
{{/if}}
请包含:
1. 代码的整体功能
2. 主要逻辑流程
3. 关键部分的详细解释
4. 使用的设计模式或技术(如果有)`,
parameters: {
code: {
type: 'string',
description: '要解释的代码',
required: true,
},
level: {
type: 'string',
description: '解释级别(beginner/intermediate/advanced',
required: false,
enum: ['beginner', 'intermediate', 'advanced'],
default: 'intermediate',
},
},
keywords: ['explain', 'code', 'understand', '解释', '理解', '说明'],
source: 'builtin',
enabled: true,
};
/**
* 文档生成 Skill
*/
export const generateDocsSkill: Skill = {
name: 'generate-docs',
displayName: '文档生成',
description: '为代码生成文档注释或 README',
category: 'documentation',
promptTemplate: `请为以下代码生成{{type}}
{{code}}
{{#if style}}
文档风格:{{style}}
{{/if}}
要求:
- 清晰描述功能和用途
- 说明参数和返回值(如果适用)
- 提供使用示例
- 使用规范的格式`,
parameters: {
code: {
type: 'string',
description: '要生成文档的代码',
required: true,
},
type: {
type: 'string',
description: '文档类型',
required: false,
enum: ['JSDoc', 'TSDoc', 'README', '注释', 'API文档'],
default: '文档注释',
},
style: {
type: 'string',
description: '文档风格(简洁/详细)',
required: false,
},
},
keywords: ['docs', 'documentation', 'jsdoc', '文档', '注释', '说明'],
source: 'builtin',
enabled: true,
};
/**
* 单元测试生成 Skill
*/
export const generateTestsSkill: Skill = {
name: 'generate-tests',
displayName: '测试生成',
description: '为代码生成单元测试',
category: 'testing',
promptTemplate: `请为以下代码生成单元测试:
{{code}}
测试框架:{{framework}}
要求:
- 覆盖主要功能路径
- 包含边界条件测试
- 包含错误处理测试
- 使用清晰的测试描述
- 遵循 AAA 模式(Arrange-Act-Assert`,
parameters: {
code: {
type: 'string',
description: '要测试的代码',
required: true,
},
framework: {
type: 'string',
description: '测试框架',
required: false,
enum: ['vitest', 'jest', 'mocha', 'pytest', 'unittest'],
default: 'vitest',
},
},
keywords: ['test', 'unit', 'testing', '测试', '单元测试', 'vitest', 'jest'],
source: 'builtin',
enabled: true,
};
/**
* 重构建议 Skill
*/
export const refactorSuggestSkill: Skill = {
name: 'refactor-suggest',
displayName: '重构建议',
description: '分析代码并提供重构建议',
category: 'development',
promptTemplate: `请分析以下代码并提供重构建议:
{{code}}
{{#if goal}}
重构目标:{{goal}}
{{/if}}
请提供:
1. 当前代码的问题分析
2. 具体的重构建议
3. 重构后的代码示例
4. 重构的好处说明`,
parameters: {
code: {
type: 'string',
description: '要重构的代码',
required: true,
},
goal: {
type: 'string',
description: '重构目标(如:提高可读性、优化性能、减少重复)',
required: false,
},
},
keywords: ['refactor', 'improve', 'optimize', '重构', '优化', '改进'],
source: 'builtin',
enabled: true,
};
/**
* Bug 修复 Skill
*/
export const fixBugSkill: Skill = {
name: 'fix-bug',
displayName: 'Bug 修复',
description: '分析代码问题并提供修复方案',
category: 'debugging',
promptTemplate: `请分析以下代码中的问题并提供修复方案:
代码:
{{code}}
{{#if error}}
错误信息:
{{error}}
{{/if}}
{{#if context}}
上下文:
{{context}}
{{/if}}
请提供:
1. 问题的根本原因分析
2. 修复方案
3. 修复后的代码
4. 如何避免类似问题的建议`,
parameters: {
code: {
type: 'string',
description: '有问题的代码',
required: true,
},
error: {
type: 'string',
description: '错误信息',
required: false,
},
context: {
type: 'string',
description: '额外的上下文信息',
required: false,
},
},
keywords: ['bug', 'fix', 'debug', 'error', '修复', '错误', '调试'],
source: 'builtin',
enabled: true,
};
/**
* Git Commit 消息生成 Skill
*/
export const gitCommitSkill: Skill = {
name: 'git-commit',
displayName: 'Git Commit',
description: '根据代码变更生成规范的 Git commit 消息',
category: 'git',
promptTemplate: `请根据以下代码变更生成规范的 Git commit 消息:
变更内容:
{{diff}}
{{#if type}}
Commit 类型:{{type}}
{{/if}}
要求:
- 遵循 Conventional Commits 规范
- 第一行不超过 50 个字符
- 清晰描述变更的目的
- 如果有 breaking changes,请说明`,
parameters: {
diff: {
type: 'string',
description: 'Git diff 内容或变更描述',
required: true,
},
type: {
type: 'string',
description: 'Commit 类型',
required: false,
enum: ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore'],
},
},
keywords: ['git', 'commit', 'message', '提交', '消息'],
source: 'builtin',
enabled: true,
};
/**
* API 设计 Skill
*/
export const apiDesignSkill: Skill = {
name: 'api-design',
displayName: 'API 设计',
description: '设计 RESTful API 接口',
category: 'architecture',
promptTemplate: `请为以下需求设计 RESTful API
需求描述:
{{requirement}}
{{#if constraints}}
约束条件:
{{constraints}}
{{/if}}
请提供:
1. API 端点设计(路径、方法、参数)
2. 请求/响应格式(JSON 示例)
3. 错误处理方案
4. 认证/授权建议(如果需要)`,
parameters: {
requirement: {
type: 'string',
description: 'API 需求描述',
required: true,
},
constraints: {
type: 'string',
description: '设计约束(如:现有系统兼容性、性能要求等)',
required: false,
},
},
keywords: ['api', 'rest', 'design', 'endpoint', 'API', '接口', '设计'],
source: 'builtin',
enabled: true,
};
/**
* 所有内置 Skills
*/
export const builtinSkills: Skill[] = [
codeReviewSkill,
explainCodeSkill,
generateDocsSkill,
generateTestsSkill,
refactorSuggestSkill,
fixBugSkill,
gitCommitSkill,
apiDesignSkill,
];
+29
View File
@@ -0,0 +1,29 @@
/**
* Skills 模块
*
* 提供 Skill 系统的所有功能导出
*/
// 类型
export type {
Skill,
SkillParameter,
SkillContext,
SkillExecutionResult,
SkillFile,
SkillSearchResult,
SkillRegistryConfig,
} from './types.js';
// 加载器
export { SkillLoader, skillLoader } from './loader.js';
// 注册表
export {
SkillRegistry,
getSkillRegistry,
resetSkillRegistry,
} from './registry.js';
// 内置 Skills
export { builtinSkills } from './builtin/index.js';
+201
View File
@@ -0,0 +1,201 @@
/**
* Skill 加载器
*
* 负责从文件系统加载 Skill 定义。
* 支持从以下位置加载:
* 1. 内置 Skills(代码中定义)
* 2. 用户 Skills~/.config/ai-terminal/skills/
* 3. 项目 Skills./.ai-terminal/skills/
*/
import * as fs from 'fs/promises';
import * as path from 'path';
import * as yaml from 'yaml';
import type { Skill, SkillFile } from './types.js';
/**
* Skill 加载器
*/
export class SkillLoader {
/**
* 从目录加载所有 Skills
*/
async loadFromDirectory(
dir: string,
source: 'user' | 'project'
): Promise<Skill[]> {
const skills: Skill[] = [];
try {
const exists = await fs
.access(dir)
.then(() => true)
.catch(() => false);
if (!exists) {
return skills;
}
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile()) {
const ext = path.extname(entry.name).toLowerCase();
if (['.yaml', '.yml', '.json', '.md'].includes(ext)) {
const filePath = path.join(dir, entry.name);
try {
const skill = await this.loadFromFile(filePath, source);
if (skill) {
skills.push(skill);
}
} catch (error) {
console.warn(`加载 Skill 文件失败: ${filePath}`, error);
}
}
} else if (entry.isDirectory()) {
// 递归加载子目录
const subDir = path.join(dir, entry.name);
const subSkills = await this.loadFromDirectory(subDir, source);
skills.push(...subSkills);
}
}
} catch (error) {
console.warn(`读取 Skills 目录失败: ${dir}`, error);
}
return skills;
}
/**
* 从单个文件加载 Skill
*/
async loadFromFile(
filePath: string,
source: 'user' | 'project'
): Promise<Skill | null> {
const ext = path.extname(filePath).toLowerCase();
const content = await fs.readFile(filePath, 'utf-8');
let skillData: SkillFile | null = null;
if (ext === '.md') {
// Markdown 格式:从 frontmatter 和内容中解析
skillData = this.parseMarkdownSkill(content, filePath);
} else if (ext === '.yaml' || ext === '.yml') {
// YAML 格式
skillData = yaml.parse(content) as SkillFile;
} else if (ext === '.json') {
// JSON 格式
skillData = JSON.parse(content) as SkillFile;
}
if (!skillData?.skill) {
return null;
}
// 验证必需字段
const { skill } = skillData;
if (!skill.name || !skill.promptTemplate) {
console.warn(`Skill 文件缺少必需字段: ${filePath}`);
return null;
}
return {
...skill,
source,
sourcePath: filePath,
enabled: skill.enabled ?? true,
};
}
/**
* 解析 Markdown 格式的 Skill
*
* 格式示例:
* ```markdown
* ---
* name: code-review
* description: 代码审查
* parameters:
* focus:
* type: string
* description: 审查重点
* ---
*
* # 代码审查
*
* 请审查以下代码,重点关注 {{focus}}:
*
* {{code}}
* ```
*/
private parseMarkdownSkill(
content: string,
filePath: string
): SkillFile | null {
// 解析 frontmatter
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if (!frontmatterMatch) {
// 没有 frontmatter,使用文件名作为 name,整个内容作为 promptTemplate
const name = path.basename(filePath, path.extname(filePath));
return {
skill: {
name,
description: `Skill: ${name}`,
promptTemplate: content.trim(),
},
};
}
const [, frontmatterStr, bodyContent] = frontmatterMatch;
try {
const frontmatter = yaml.parse(frontmatterStr) as Partial<Skill>;
// 如果 frontmatter 中没有 promptTemplate,使用 body 内容
const promptTemplate = frontmatter.promptTemplate || bodyContent.trim();
// 从文件名获取默认 name
const defaultName = path.basename(filePath, path.extname(filePath));
return {
skill: {
name: frontmatter.name || defaultName,
displayName: frontmatter.displayName,
description: frontmatter.description || `Skill: ${defaultName}`,
category: frontmatter.category,
promptTemplate,
parameters: frontmatter.parameters,
keywords: frontmatter.keywords,
version: frontmatter.version,
author: frontmatter.author,
enabled: frontmatter.enabled,
},
};
} catch (error) {
console.warn(`解析 Skill frontmatter 失败: ${filePath}`, error);
return null;
}
}
/**
* 获取用户 Skills 目录
*/
getUserSkillsDir(): string {
const home = process.env.HOME || process.env.USERPROFILE || '';
return path.join(home, '.config', 'ai-terminal', 'skills');
}
/**
* 获取项目 Skills 目录
*/
getProjectSkillsDir(workdir: string = process.cwd()): string {
return path.join(workdir, '.ai-terminal', 'skills');
}
}
/**
* 全局 Skill 加载器实例
*/
export const skillLoader = new SkillLoader();
+358
View File
@@ -0,0 +1,358 @@
/**
* Skill 注册表
*
* 管理所有可用的 Skills,支持:
* - 注册/注销 Skills
* - 按名称/分类/关键词查询
* - 渲染 Skill 提示模板
*/
import type {
Skill,
SkillContext,
SkillExecutionResult,
SkillSearchResult,
SkillRegistryConfig,
} from './types.js';
import { skillLoader } from './loader.js';
import { builtinSkills } from './builtin/index.js';
/**
* Skill 注册表
*/
export class SkillRegistry {
private skills = new Map<string, Skill>();
private config: SkillRegistryConfig;
private initialized = false;
constructor(config: SkillRegistryConfig = {}) {
this.config = {
autoLoad: true,
...config,
};
}
/**
* 初始化注册表
*/
async initialize(workdir: string = process.cwd()): Promise<void> {
if (this.initialized) {
return;
}
// 1. 注册内置 Skills
for (const skill of builtinSkills) {
this.register(skill);
}
// 2. 加载用户 Skills
if (this.config.autoLoad) {
const userDir =
this.config.userSkillsDir || skillLoader.getUserSkillsDir();
const userSkills = await skillLoader.loadFromDirectory(userDir, 'user');
for (const skill of userSkills) {
this.register(skill);
}
}
// 3. 加载项目 Skills
if (this.config.autoLoad) {
const projectDir =
this.config.projectSkillsDir || skillLoader.getProjectSkillsDir(workdir);
const projectSkills = await skillLoader.loadFromDirectory(
projectDir,
'project'
);
for (const skill of projectSkills) {
this.register(skill);
}
}
this.initialized = true;
}
/**
* 注册 Skill
*/
register(skill: Skill): void {
// 项目 Skills 优先级最高,可以覆盖同名的内置/用户 Skills
const existing = this.skills.get(skill.name);
if (existing) {
// 优先级: project > user > builtin
const priority = { project: 3, user: 2, builtin: 1 };
if (priority[skill.source] < priority[existing.source]) {
return; // 不覆盖更高优先级的 Skill
}
}
this.skills.set(skill.name, skill);
}
/**
* 注销 Skill
*/
unregister(name: string): boolean {
return this.skills.delete(name);
}
/**
* 获取 Skill
*/
get(name: string): Skill | undefined {
return this.skills.get(name);
}
/**
* 检查 Skill 是否存在
*/
has(name: string): boolean {
return this.skills.has(name);
}
/**
* 获取所有 Skills
*/
getAll(): Skill[] {
return Array.from(this.skills.values());
}
/**
* 获取启用的 Skills
*/
getEnabled(): Skill[] {
return this.getAll().filter((s) => s.enabled !== false);
}
/**
* 按分类获取 Skills
*/
getByCategory(category: string): Skill[] {
return this.getEnabled().filter((s) => s.category === category);
}
/**
* 获取所有分类
*/
getCategories(): string[] {
const categories = new Set<string>();
for (const skill of this.getEnabled()) {
if (skill.category) {
categories.add(skill.category);
}
}
return Array.from(categories).sort();
}
/**
* 搜索 Skills
*/
search(query: string, limit: number = 10): SkillSearchResult[] {
const queryLower = query.toLowerCase();
const results: SkillSearchResult[] = [];
for (const skill of this.getEnabled()) {
let score = 0;
let matchReason = '';
// 精确名称匹配
if (skill.name.toLowerCase() === queryLower) {
score = 100;
matchReason = '名称精确匹配';
}
// 名称前缀匹配
else if (skill.name.toLowerCase().startsWith(queryLower)) {
score = 80;
matchReason = '名称前缀匹配';
}
// 名称包含匹配
else if (skill.name.toLowerCase().includes(queryLower)) {
score = 60;
matchReason = '名称包含匹配';
}
// 描述匹配
else if (skill.description.toLowerCase().includes(queryLower)) {
score = 40;
matchReason = '描述匹配';
}
// 关键词匹配
else if (
skill.keywords?.some((k) => k.toLowerCase().includes(queryLower))
) {
score = 30;
matchReason = '关键词匹配';
}
// 分类匹配
else if (skill.category?.toLowerCase().includes(queryLower)) {
score = 20;
matchReason = '分类匹配';
}
if (score > 0) {
results.push({ skill, score, matchReason });
}
}
// 按分数降序排序
results.sort((a, b) => b.score - a.score);
return results.slice(0, limit);
}
/**
* 渲染 Skill 提示模板
*/
renderPrompt(
skill: Skill,
params: Record<string, unknown>,
context?: SkillContext
): SkillExecutionResult {
try {
// 验证必需参数
if (skill.parameters) {
for (const [name, param] of Object.entries(skill.parameters)) {
if (param.required && !(name in params)) {
return {
success: false,
error: `缺少必需参数: ${name}`,
};
}
}
}
// 构建变量映射
const variables: Record<string, string> = {};
// 添加参数值
for (const [key, value] of Object.entries(params)) {
variables[key] = String(value);
}
// 添加上下文变量
if (context?.variables) {
for (const [key, value] of Object.entries(context.variables)) {
if (!(key in variables)) {
variables[key] = value;
}
}
}
// 添加默认值
if (skill.parameters) {
for (const [name, param] of Object.entries(skill.parameters)) {
if (!(name in variables) && param.default !== undefined) {
variables[name] = String(param.default);
}
}
}
// 渲染模板
let prompt = skill.promptTemplate;
// 替换 {{variable}} 格式的变量
prompt = prompt.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
if (varName in variables) {
return variables[varName];
}
// 保留未匹配的变量(可能是用户意图保留)
return match;
});
return {
success: true,
prompt,
};
} catch (error) {
return {
success: false,
error: `渲染 Skill 提示失败: ${error instanceof Error ? error.message : String(error)}`,
};
}
}
/**
* 执行 Skill(渲染模板并返回提示)
*/
execute(
name: string,
params: Record<string, unknown>,
context?: SkillContext
): SkillExecutionResult {
const skill = this.get(name);
if (!skill) {
return {
success: false,
error: `Skill 不存在: ${name}`,
};
}
if (skill.enabled === false) {
return {
success: false,
error: `Skill 已禁用: ${name}`,
};
}
return this.renderPrompt(skill, params, context);
}
/**
* 重新加载 Skills
*/
async reload(workdir: string = process.cwd()): Promise<void> {
this.skills.clear();
this.initialized = false;
await this.initialize(workdir);
}
/**
* 获取 Skill 统计信息
*/
getStats(): {
total: number;
enabled: number;
bySource: Record<string, number>;
byCategory: Record<string, number>;
} {
const skills = this.getAll();
const enabled = this.getEnabled();
const bySource: Record<string, number> = {};
const byCategory: Record<string, number> = {};
for (const skill of skills) {
bySource[skill.source] = (bySource[skill.source] || 0) + 1;
if (skill.category) {
byCategory[skill.category] = (byCategory[skill.category] || 0) + 1;
}
}
return {
total: skills.length,
enabled: enabled.length,
bySource,
byCategory,
};
}
}
/**
* 全局 Skill 注册表实例
*/
let skillRegistryInstance: SkillRegistry | null = null;
/**
* 获取全局 Skill 注册表
*/
export function getSkillRegistry(): SkillRegistry {
if (!skillRegistryInstance) {
skillRegistryInstance = new SkillRegistry();
}
return skillRegistryInstance;
}
/**
* 重置全局 Skill 注册表(用于测试)
*/
export function resetSkillRegistry(): void {
skillRegistryInstance = null;
}
+109
View File
@@ -0,0 +1,109 @@
/**
* Skill 系统类型定义
*
* Skill 是可复用的提示模板,类似于 Claude Code 的 Skills 功能。
* 与 Agent 不同,Skill 不是独立的执行单元,而是预定义的提示模板,
* 可以被 Agent 调用来执行特定任务。
*/
/**
* Skill 参数定义
*/
export interface SkillParameter {
/** 参数类型 */
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
/** 参数描述 */
description: string;
/** 是否必需 */
required?: boolean;
/** 默认值 */
default?: unknown;
/** 枚举值(仅 string 类型) */
enum?: string[];
}
/**
* Skill 定义
*/
export interface Skill {
/** Skill 唯一标识 */
name: string;
/** Skill 显示名称 */
displayName?: string;
/** Skill 描述 */
description: string;
/** Skill 分类 */
category?: string;
/** 提示模板(支持变量插值 {{variable}} */
promptTemplate: string;
/** Skill 参数定义 */
parameters?: Record<string, SkillParameter>;
/** 关键词(用于搜索) */
keywords?: string[];
/** 来源(内置/用户定义/项目) */
source: 'builtin' | 'user' | 'project';
/** 来源路径(用户定义或项目 Skill 的文件路径) */
sourcePath?: string;
/** 是否启用 */
enabled?: boolean;
/** 版本 */
version?: string;
/** 作者 */
author?: string;
}
/**
* Skill 执行上下文
*/
export interface SkillContext {
/** 当前工作目录 */
workdir: string;
/** 额外的上下文变量 */
variables?: Record<string, string>;
}
/**
* Skill 执行结果
*/
export interface SkillExecutionResult {
/** 是否成功 */
success: boolean;
/** 渲染后的提示 */
prompt?: string;
/** 错误信息 */
error?: string;
}
/**
* Skill 文件格式(YAML/JSON
*/
export interface SkillFile {
/** 文件版本 */
version?: string;
/** Skill 定义 */
skill: Omit<Skill, 'source' | 'sourcePath'>;
}
/**
* Skill 搜索结果
*/
export interface SkillSearchResult {
/** Skill */
skill: Skill;
/** 匹配分数 */
score: number;
/** 匹配原因 */
matchReason: string;
}
/**
* Skill 注册表配置
*/
export interface SkillRegistryConfig {
/** 用户 Skills 目录 */
userSkillsDir?: string;
/** 项目 Skills 目录 */
projectSkillsDir?: string;
/** 是否自动加载 */
autoLoad?: boolean;
}