feat(commands): 实现命令 CRUD 完整功能
Core: - 新增 CommandManager 类,支持创建、更新、删除命令 - 验证命令名称防止路径遍历攻击 - 自动生成 Markdown 文件(含 YAML frontmatter) - 内置命令保护(不可修改/删除) Server: - POST /api/commands - 创建命令 - GET /api/commands/:name/content - 获取命令完整内容 - PUT /api/commands/:name - 更新命令 - DELETE /api/commands/:name - 删除命令 UI: - 新增 createCommand、updateCommand、deleteCommand、getCommandContent 函数 - 新增 CreateCommandInput、UpdateCommandInput、CommandContent 类型
This commit is contained in:
@@ -20,10 +20,30 @@ const SearchCommandInputSchema = z.object({
|
||||
|
||||
export const commandsRouter = new Hono();
|
||||
|
||||
// Zod schemas for CRUD
|
||||
const CreateCommandInputSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
description: z.string().optional(),
|
||||
template: z.string().min(1),
|
||||
agent: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
subtask: z.boolean().optional(),
|
||||
scope: z.enum(['user', 'project']),
|
||||
});
|
||||
|
||||
const UpdateCommandInputSchema = z.object({
|
||||
description: z.string().optional(),
|
||||
template: z.string().optional(),
|
||||
agent: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
subtask: z.boolean().optional(),
|
||||
});
|
||||
|
||||
// Core 模块类型
|
||||
interface CommandModule {
|
||||
getCommandRegistry: () => CommandRegistry;
|
||||
createCommandExecutor: (workdir: string) => CommandExecutor;
|
||||
createCommandManager: (workdir: string) => CommandManager;
|
||||
}
|
||||
|
||||
interface CommandRegistry {
|
||||
@@ -67,6 +87,43 @@ interface CommandExecutionResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface CommandManager {
|
||||
create(input: {
|
||||
name: string;
|
||||
description?: string;
|
||||
template: string;
|
||||
agent?: string;
|
||||
model?: string;
|
||||
subtask?: boolean;
|
||||
scope: 'user' | 'project';
|
||||
}): Promise<{ success: boolean; path?: string; error?: string }>;
|
||||
update(
|
||||
name: string,
|
||||
input: {
|
||||
description?: string;
|
||||
template?: string;
|
||||
agent?: string;
|
||||
model?: string;
|
||||
subtask?: boolean;
|
||||
}
|
||||
): Promise<{ success: boolean; path?: string; error?: string }>;
|
||||
delete(name: string): Promise<{ success: boolean; path?: string; error?: string }>;
|
||||
getContent(name: string): Promise<{
|
||||
success: boolean;
|
||||
data?: {
|
||||
name: string;
|
||||
description?: string;
|
||||
template: string;
|
||||
agent?: string;
|
||||
model?: string;
|
||||
subtask?: boolean;
|
||||
source: string;
|
||||
sourcePath?: string;
|
||||
};
|
||||
error?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// Core 模块缓存
|
||||
let commandModule: CommandModule | null = null;
|
||||
|
||||
@@ -82,7 +139,8 @@ async function initCommandModule(): Promise<CommandModule | null> {
|
||||
|
||||
if (
|
||||
typeof core.getCommandRegistry !== 'function' ||
|
||||
typeof core.createCommandExecutor !== 'function'
|
||||
typeof core.createCommandExecutor !== 'function' ||
|
||||
typeof core.createCommandManager !== 'function'
|
||||
) {
|
||||
console.warn('[Commands] Core module missing command exports');
|
||||
return null;
|
||||
@@ -91,6 +149,7 @@ async function initCommandModule(): Promise<CommandModule | null> {
|
||||
commandModule = {
|
||||
getCommandRegistry: core.getCommandRegistry as () => CommandRegistry,
|
||||
createCommandExecutor: core.createCommandExecutor as (workdir: string) => CommandExecutor,
|
||||
createCommandManager: core.createCommandManager as (workdir: string) => CommandManager,
|
||||
};
|
||||
|
||||
// Initialize registry with server workdir
|
||||
@@ -321,3 +380,189 @@ commandsRouter.post('/:name{.+}/execute', async (c) => {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// CRUD 操作
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* POST /commands - 创建命令
|
||||
*/
|
||||
commandsRouter.post('/', async (c) => {
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Command module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await c.req.json();
|
||||
const input = CreateCommandInputSchema.parse(body);
|
||||
|
||||
const config = getConfig();
|
||||
const manager = module.createCommandManager(config.workdir);
|
||||
const result = await manager.create(input);
|
||||
|
||||
if (result.success) {
|
||||
return c.json({
|
||||
success: true,
|
||||
data: {
|
||||
name: input.name,
|
||||
path: result.path,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: result.error,
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Invalid input',
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /commands/:name/content - 获取命令完整内容(包含 template)
|
||||
*/
|
||||
commandsRouter.get('/:name{.+}/content', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Command module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const manager = module.createCommandManager(config.workdir);
|
||||
const result = await manager.getContent(name);
|
||||
|
||||
if (result.success) {
|
||||
return c.json({
|
||||
success: true,
|
||||
data: result.data,
|
||||
});
|
||||
} else {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: result.error,
|
||||
},
|
||||
404
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PUT /commands/:name - 更新命令
|
||||
*/
|
||||
commandsRouter.put('/:name{.+}', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Command module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await c.req.json();
|
||||
const input = UpdateCommandInputSchema.parse(body);
|
||||
|
||||
const config = getConfig();
|
||||
const manager = module.createCommandManager(config.workdir);
|
||||
const result = await manager.update(name, input);
|
||||
|
||||
if (result.success) {
|
||||
return c.json({
|
||||
success: true,
|
||||
data: {
|
||||
name,
|
||||
path: result.path,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: result.error,
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Invalid input',
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /commands/:name - 删除命令
|
||||
*/
|
||||
commandsRouter.delete('/:name{.+}', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Command module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const manager = module.createCommandManager(config.workdir);
|
||||
const result = await manager.delete(name);
|
||||
|
||||
if (result.success) {
|
||||
return c.json({
|
||||
success: true,
|
||||
data: {
|
||||
name,
|
||||
path: result.path,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: result.error,
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user