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:
@@ -26,5 +26,15 @@ export {
|
||||
// 执行器
|
||||
export { CommandExecutor, createCommandExecutor } from './executor.js';
|
||||
|
||||
// 管理器
|
||||
export {
|
||||
CommandManager,
|
||||
createCommandManager,
|
||||
type CreateCommandInput,
|
||||
type UpdateCommandInput,
|
||||
type CommandContent,
|
||||
type CommandOperationResult,
|
||||
} from './manager.js';
|
||||
|
||||
// 内置 Commands
|
||||
export { builtinCommands } from './builtin/index.js';
|
||||
|
||||
@@ -0,0 +1,430 @@
|
||||
/**
|
||||
* Command 管理器
|
||||
*
|
||||
* 负责命令的 CRUD 操作(创建、更新、删除)
|
||||
* 命令存储在文件系统的 Markdown 文件中
|
||||
*/
|
||||
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import * as yaml from 'yaml';
|
||||
import type { Command, CommandFrontmatter } from './types.js';
|
||||
import { commandLoader } from './loader.js';
|
||||
import { getCommandRegistry } from './registry.js';
|
||||
|
||||
/**
|
||||
* 创建命令的输入参数
|
||||
*/
|
||||
export interface CreateCommandInput {
|
||||
/** 命令名称(支持嵌套如 deploy/staging) */
|
||||
name: string;
|
||||
/** 命令描述 */
|
||||
description?: string;
|
||||
/** 提示词模板 */
|
||||
template: string;
|
||||
/** 指定 Agent */
|
||||
agent?: string;
|
||||
/** 指定模型 */
|
||||
model?: string;
|
||||
/** 是否作为子任务执行 */
|
||||
subtask?: boolean;
|
||||
/** 存储位置 */
|
||||
scope: 'user' | 'project';
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新命令的输入参数
|
||||
*/
|
||||
export interface UpdateCommandInput {
|
||||
/** 命令描述 */
|
||||
description?: string;
|
||||
/** 提示词模板 */
|
||||
template?: string;
|
||||
/** 指定 Agent */
|
||||
agent?: string;
|
||||
/** 指定模型 */
|
||||
model?: string;
|
||||
/** 是否作为子任务执行 */
|
||||
subtask?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 命令完整内容(包含 template)
|
||||
*/
|
||||
export interface CommandContent {
|
||||
name: string;
|
||||
description?: string;
|
||||
template: string;
|
||||
agent?: string;
|
||||
model?: string;
|
||||
subtask?: boolean;
|
||||
source: string;
|
||||
sourcePath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作结果
|
||||
*/
|
||||
export interface CommandOperationResult {
|
||||
success: boolean;
|
||||
path?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 命令管理器
|
||||
*/
|
||||
export class CommandManager {
|
||||
private workdir: string;
|
||||
|
||||
constructor(workdir: string = process.cwd()) {
|
||||
this.workdir = workdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证命令名称是否合法
|
||||
* 防止路径遍历攻击
|
||||
*/
|
||||
private validateCommandName(name: string): boolean {
|
||||
// 不能为空
|
||||
if (!name || name.trim() === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 不能包含 .. 或绝对路径
|
||||
if (name.includes('..') || path.isAbsolute(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 只允许字母、数字、连字符、下划线和斜杠
|
||||
if (!/^[a-zA-Z0-9_\-/]+$/.test(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 不能以斜杠开头或结尾
|
||||
if (name.startsWith('/') || name.endsWith('/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 不能有连续的斜杠
|
||||
if (name.includes('//')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Markdown 内容
|
||||
*/
|
||||
private generateMarkdownContent(input: {
|
||||
description?: string;
|
||||
template: string;
|
||||
agent?: string;
|
||||
model?: string;
|
||||
subtask?: boolean;
|
||||
}): string {
|
||||
const frontmatter: CommandFrontmatter = {};
|
||||
|
||||
if (input.description) {
|
||||
frontmatter.description = input.description;
|
||||
}
|
||||
if (input.agent) {
|
||||
frontmatter.agent = input.agent;
|
||||
}
|
||||
if (input.model) {
|
||||
frontmatter.model = input.model;
|
||||
}
|
||||
if (input.subtask !== undefined) {
|
||||
frontmatter.subtask = input.subtask;
|
||||
}
|
||||
|
||||
// 如果有 frontmatter,生成 YAML
|
||||
if (Object.keys(frontmatter).length > 0) {
|
||||
const yamlStr = yaml.stringify(frontmatter).trim();
|
||||
return `---\n${yamlStr}\n---\n\n${input.template}`;
|
||||
}
|
||||
|
||||
// 没有 frontmatter,直接返回模板
|
||||
return input.template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令文件路径
|
||||
*/
|
||||
getCommandFilePath(name: string, scope: 'user' | 'project'): string {
|
||||
const baseDir =
|
||||
scope === 'user'
|
||||
? commandLoader.getUserCommandsDir()
|
||||
: commandLoader.getProjectCommandsDir(this.workdir);
|
||||
|
||||
return path.join(baseDir, `${name}.md`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建命令
|
||||
*/
|
||||
async create(input: CreateCommandInput): Promise<CommandOperationResult> {
|
||||
// 验证命令名称
|
||||
if (!this.validateCommandName(input.name)) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Invalid command name: ${input.name}. Name can only contain letters, numbers, hyphens, underscores, and forward slashes.`,
|
||||
};
|
||||
}
|
||||
|
||||
// 验证模板不为空
|
||||
if (!input.template || input.template.trim() === '') {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Template cannot be empty',
|
||||
};
|
||||
}
|
||||
|
||||
// 检查是否已存在同名命令
|
||||
const registry = getCommandRegistry();
|
||||
const existing = registry.get(input.name);
|
||||
if (existing) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Command already exists: ${input.name} (source: ${existing.source})`,
|
||||
};
|
||||
}
|
||||
|
||||
// 生成文件路径
|
||||
const filePath = this.getCommandFilePath(input.name, input.scope);
|
||||
|
||||
// 确保目录存在
|
||||
const dir = path.dirname(filePath);
|
||||
try {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to create directory: ${dir}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 生成 Markdown 内容
|
||||
const content = this.generateMarkdownContent({
|
||||
description: input.description,
|
||||
template: input.template,
|
||||
agent: input.agent,
|
||||
model: input.model,
|
||||
subtask: input.subtask,
|
||||
});
|
||||
|
||||
// 写入文件
|
||||
try {
|
||||
await fs.writeFile(filePath, content, 'utf-8');
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to write file: ${filePath}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 重新加载命令注册表
|
||||
await registry.reload(this.workdir);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
path: filePath,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新命令
|
||||
*/
|
||||
async update(
|
||||
name: string,
|
||||
input: UpdateCommandInput
|
||||
): Promise<CommandOperationResult> {
|
||||
// 验证命令名称
|
||||
if (!this.validateCommandName(name)) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Invalid command name: ${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 获取现有命令
|
||||
const registry = getCommandRegistry();
|
||||
const existing = registry.get(name);
|
||||
|
||||
if (!existing) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Command not found: ${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 内置命令不可修改
|
||||
if (existing.source === 'builtin') {
|
||||
return {
|
||||
success: false,
|
||||
error: `Cannot modify builtin command: ${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 获取文件路径
|
||||
const filePath = existing.sourcePath;
|
||||
if (!filePath) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Command source path not found: ${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 合并更新
|
||||
const updatedCommand = {
|
||||
description: input.description ?? existing.description,
|
||||
template: input.template ?? existing.template,
|
||||
agent: input.agent ?? existing.agent,
|
||||
model: input.model ?? existing.model,
|
||||
subtask: input.subtask ?? existing.subtask,
|
||||
};
|
||||
|
||||
// 生成新的 Markdown 内容
|
||||
const content = this.generateMarkdownContent(updatedCommand);
|
||||
|
||||
// 写入文件
|
||||
try {
|
||||
await fs.writeFile(filePath, content, 'utf-8');
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to write file: ${filePath}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 重新加载命令注册表
|
||||
await registry.reload(this.workdir);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
path: filePath,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除命令
|
||||
*/
|
||||
async delete(name: string): Promise<CommandOperationResult> {
|
||||
// 验证命令名称
|
||||
if (!this.validateCommandName(name)) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Invalid command name: ${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 获取现有命令
|
||||
const registry = getCommandRegistry();
|
||||
const existing = registry.get(name);
|
||||
|
||||
if (!existing) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Command not found: ${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 内置命令不可删除
|
||||
if (existing.source === 'builtin') {
|
||||
return {
|
||||
success: false,
|
||||
error: `Cannot delete builtin command: ${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 获取文件路径
|
||||
const filePath = existing.sourcePath;
|
||||
if (!filePath) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Command source path not found: ${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
try {
|
||||
await fs.unlink(filePath);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to delete file: ${filePath}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 尝试删除空的父目录
|
||||
try {
|
||||
const dir = path.dirname(filePath);
|
||||
const files = await fs.readdir(dir);
|
||||
if (files.length === 0) {
|
||||
await fs.rmdir(dir);
|
||||
}
|
||||
} catch {
|
||||
// 忽略目录删除失败
|
||||
}
|
||||
|
||||
// 重新加载命令注册表
|
||||
await registry.reload(this.workdir);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
path: filePath,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令完整内容(包含 template)
|
||||
*/
|
||||
async getContent(name: string): Promise<{
|
||||
success: boolean;
|
||||
data?: CommandContent;
|
||||
error?: string;
|
||||
}> {
|
||||
// 验证命令名称
|
||||
if (!this.validateCommandName(name)) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Invalid command name: ${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 获取命令
|
||||
const registry = getCommandRegistry();
|
||||
const command = registry.get(name);
|
||||
|
||||
if (!command) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Command not found: ${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
name: command.name,
|
||||
description: command.description,
|
||||
template: command.template,
|
||||
agent: command.agent,
|
||||
model: command.model,
|
||||
subtask: command.subtask,
|
||||
source: command.source,
|
||||
sourcePath: command.sourcePath,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建命令管理器实例
|
||||
*/
|
||||
export function createCommandManager(
|
||||
workdir: string = process.cwd()
|
||||
): CommandManager {
|
||||
return new CommandManager(workdir);
|
||||
}
|
||||
@@ -33,8 +33,16 @@ export { SessionStorage } from './session/storage.js';
|
||||
export type { SessionData, SessionSummary } from './session/types.js';
|
||||
|
||||
// Commands
|
||||
export { getCommandRegistry, createCommandExecutor } from './commands/index.js';
|
||||
export type { Command, CommandInput, CommandExecutionResult } from './commands/index.js';
|
||||
export { getCommandRegistry, createCommandExecutor, createCommandManager } from './commands/index.js';
|
||||
export type {
|
||||
Command,
|
||||
CommandInput,
|
||||
CommandExecutionResult,
|
||||
CreateCommandInput,
|
||||
UpdateCommandInput,
|
||||
CommandContent,
|
||||
CommandOperationResult,
|
||||
} from './commands/index.js';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,6 +14,9 @@ import type {
|
||||
CommandSearchResult,
|
||||
CommandExecuteResult,
|
||||
CommandListResponse,
|
||||
CreateCommandInput,
|
||||
UpdateCommandInput,
|
||||
CommandContent,
|
||||
} from './types.js';
|
||||
|
||||
// Re-export types
|
||||
@@ -31,6 +34,9 @@ export type {
|
||||
CommandSearchResult,
|
||||
CommandExecuteResult,
|
||||
CommandListResponse,
|
||||
CreateCommandInput,
|
||||
UpdateCommandInput,
|
||||
CommandContent,
|
||||
} from './types.js';
|
||||
|
||||
// API Configuration
|
||||
@@ -203,3 +209,29 @@ export async function reloadCommands(): Promise<{
|
||||
}> {
|
||||
return request('POST', '/commands/reload');
|
||||
}
|
||||
|
||||
// Commands CRUD
|
||||
export async function createCommand(
|
||||
input: CreateCommandInput
|
||||
): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> {
|
||||
return request('POST', '/commands', input);
|
||||
}
|
||||
|
||||
export async function getCommandContent(
|
||||
name: string
|
||||
): Promise<{ success: boolean; data?: CommandContent; error?: string }> {
|
||||
return request('GET', `/commands/${encodeURIComponent(name)}/content`);
|
||||
}
|
||||
|
||||
export async function updateCommand(
|
||||
name: string,
|
||||
input: UpdateCommandInput
|
||||
): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> {
|
||||
return request('PUT', `/commands/${encodeURIComponent(name)}`, input);
|
||||
}
|
||||
|
||||
export async function deleteCommand(
|
||||
name: string
|
||||
): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> {
|
||||
return request('DELETE', `/commands/${encodeURIComponent(name)}`);
|
||||
}
|
||||
|
||||
@@ -128,3 +128,46 @@ export interface CommandListResponse {
|
||||
bySource: Record<string, number>;
|
||||
};
|
||||
}
|
||||
|
||||
// ============ Command CRUD 相关 ============
|
||||
|
||||
export interface CreateCommandInput {
|
||||
/** 命令名称(支持嵌套如 deploy/staging) */
|
||||
name: string;
|
||||
/** 命令描述 */
|
||||
description?: string;
|
||||
/** 提示词模板 */
|
||||
template: string;
|
||||
/** 指定 Agent */
|
||||
agent?: string;
|
||||
/** 指定模型 */
|
||||
model?: string;
|
||||
/** 是否作为子任务执行 */
|
||||
subtask?: boolean;
|
||||
/** 存储位置 */
|
||||
scope: 'user' | 'project';
|
||||
}
|
||||
|
||||
export interface UpdateCommandInput {
|
||||
/** 命令描述 */
|
||||
description?: string;
|
||||
/** 提示词模板 */
|
||||
template?: string;
|
||||
/** 指定 Agent */
|
||||
agent?: string;
|
||||
/** 指定模型 */
|
||||
model?: string;
|
||||
/** 是否作为子任务执行 */
|
||||
subtask?: boolean;
|
||||
}
|
||||
|
||||
export interface CommandContent {
|
||||
name: string;
|
||||
description?: string;
|
||||
template: string;
|
||||
agent?: string;
|
||||
model?: string;
|
||||
subtask?: boolean;
|
||||
source: string;
|
||||
sourcePath?: string;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,11 @@ export {
|
||||
executeCommand,
|
||||
searchCommands,
|
||||
reloadCommands,
|
||||
// Commands CRUD
|
||||
createCommand,
|
||||
getCommandContent,
|
||||
updateCommand,
|
||||
deleteCommand,
|
||||
} from './api/client.js';
|
||||
|
||||
// Types
|
||||
@@ -45,6 +50,10 @@ export type {
|
||||
CommandSearchResult,
|
||||
CommandExecuteResult,
|
||||
CommandListResponse,
|
||||
// Command CRUD types
|
||||
CreateCommandInput,
|
||||
UpdateCommandInput,
|
||||
CommandContent,
|
||||
} from './api/client.js';
|
||||
|
||||
// Primitives (shadcn/ui style)
|
||||
|
||||
Reference in New Issue
Block a user