diff --git a/src/agent/cli/autocomplete.ts b/src/agent/cli/autocomplete.ts index e12ac87a..1564983a 100644 --- a/src/agent/cli/autocomplete.ts +++ b/src/agent/cli/autocomplete.ts @@ -3,6 +3,8 @@ * * Real-time dropdown autocomplete for terminal input * No external dependencies - uses raw terminal control + * + * Falls back to simple readline when terminal doesn't support advanced features */ import * as readline from "readline"; @@ -30,10 +32,7 @@ const CURSOR_TO_COL = (n: number) => `${ESC}[${n}G`; const RESET = `${ESC}[0m`; const INVERSE = `${ESC}[7m`; const SHOW_CURSOR = `${ESC}[?25h`; -const SAVE_CURSOR = `${ESC}[s`; -const RESTORE_CURSOR = `${ESC}[u`; const CLEAR_TO_END = `${ESC}[J`; -const CURSOR_DOWN = (n: number) => (n > 0 ? `${ESC}[${n}B` : ""); // Strip ANSI escape codes to get visual length const ANSI_REGEX = /\x1b\[[0-9;]*m/g; @@ -41,10 +40,99 @@ function stripAnsi(str: string): string { return str.replace(ANSI_REGEX, ""); } +/** + * Get the visual width of a string in terminal columns + * Full-width characters (CJK, etc.) take 2 columns + */ +function getStringWidth(str: string): number { + let width = 0; + for (const char of str) { + const code = char.codePointAt(0); + if (code === undefined) continue; + + // Check for full-width characters: + // - CJK Unified Ideographs (Chinese, Japanese Kanji, Korean Hanja) + // - CJK Symbols and Punctuation + // - Hiragana, Katakana + // - Hangul + // - Full-width ASCII and symbols + if ( + (code >= 0x1100 && code <= 0x115f) || // Hangul Jamo + (code >= 0x2e80 && code <= 0x9fff) || // CJK + (code >= 0xac00 && code <= 0xd7a3) || // Hangul Syllables + (code >= 0xf900 && code <= 0xfaff) || // CJK Compatibility Ideographs + (code >= 0xfe10 && code <= 0xfe1f) || // Vertical forms + (code >= 0xfe30 && code <= 0xfe6f) || // CJK Compatibility Forms + (code >= 0xff00 && code <= 0xff60) || // Full-width ASCII + (code >= 0xffe0 && code <= 0xffe6) || // Full-width symbols + (code >= 0x20000 && code <= 0x2ffff) // CJK Extension B and beyond + ) { + width += 2; + } else { + width += 1; + } + } + return width; +} + +/** + * Check if terminal supports advanced cursor control + */ +function isTerminalSupported(): boolean { + // Check TERM environment variable + const term = process.env.TERM; + if (!term) { + return false; + } + + // Check if running in known unsupported environments + const unsupportedTerms = ["dumb", "emacs"]; + if (unsupportedTerms.includes(term.toLowerCase())) { + return false; + } + + // Check if stdout is a TTY + if (!process.stdout.isTTY) { + return false; + } + + return true; +} + +/** + * Simple readline input (fallback for unsupported terminals) + */ +function simpleInput(config: AutocompleteConfig): Promise { + return new Promise((resolve) => { + const { prompt = "> " } = config; + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: true, + }); + + rl.question(prompt, (answer) => { + rl.close(); + resolve(answer); + }); + + rl.on("close", () => { + resolve(""); + }); + }); +} + /** * Read a line with real-time autocomplete dropdown + * Falls back to simple readline on unsupported terminals */ export function autocompleteInput(config: AutocompleteConfig): Promise { + // Fall back to simple input if terminal doesn't support advanced features + if (!isTerminalSupported()) { + return simpleInput(config); + } + return new Promise((resolve) => { const { getSuggestions, prompt = "> ", maxSuggestions = 5 } = config; @@ -55,12 +143,14 @@ export function autocompleteInput(config: AutocompleteConfig): Promise { let cursorPos = 0; let suggestions: AutocompleteOption[] = []; let selectedIndex = -1; - let initialized = false; + let lastRenderedLines = 0; // Track how many lines we rendered (for cleanup) // Enable raw mode if (stdin.isTTY) { stdin.setRawMode(true); } + + // Set up keypress events readline.emitKeypressEvents(stdin); const cleanup = () => { @@ -71,43 +161,40 @@ export function autocompleteInput(config: AutocompleteConfig): Promise { stdin.removeListener("keypress", onKeypress); }; - const render = () => { - if (!initialized) { - // First render - save cursor position as anchor - stdout.write(SAVE_CURSOR); - initialized = true; - } else { - // Restore to anchor and clear everything after it - stdout.write(RESTORE_CURSOR); + const clearDisplay = () => { + // Move to beginning of current line + stdout.write("\r"); + // Clear current line + stdout.write(CLEAR_LINE); + // Clear any suggestion lines below + if (lastRenderedLines > 0) { stdout.write(CLEAR_TO_END); - // Re-save in case terminal scrolled - stdout.write(SAVE_CURSOR); } + }; + + const render = () => { + clearDisplay(); // Write prompt and input stdout.write(`${prompt}${input}`); - // Calculate cursor position accounting for line wrapping + // Calculate cursor position accounting for line wrapping and wide characters const termWidth = stdout.columns || 80; - const promptVisualLen = stripAnsi(prompt).length; - const cursorOffset = promptVisualLen + cursorPos; + const promptVisualWidth = getStringWidth(stripAnsi(prompt)); + // Calculate visual width of input up to cursor position + const inputBeforeCursor = input.slice(0, cursorPos); + const inputVisualWidth = getStringWidth(inputBeforeCursor); + const cursorOffset = promptVisualWidth + inputVisualWidth; // Handle edge case: when cursor is exactly at line boundary, // show it at end of current line, not start of next line - let cursorRow: number; let cursorCol: number; if (cursorOffset > 0 && cursorOffset % termWidth === 0) { - cursorRow = cursorOffset / termWidth - 1; cursorCol = termWidth; } else { - cursorRow = Math.floor(cursorOffset / termWidth); cursorCol = (cursorOffset % termWidth) + 1; } - // Calculate total lines for suggestions positioning - const totalLength = promptVisualLen + input.length; - const totalLines = Math.ceil(totalLength / termWidth) || 1; - // Get and display suggestions if input starts with / if (input.startsWith("/") && input.length > 1) { suggestions = getSuggestions(input).slice(0, maxSuggestions); @@ -118,8 +205,7 @@ export function autocompleteInput(config: AutocompleteConfig): Promise { selectedIndex = suggestions.length - 1; } - // Move to end of input text before showing suggestions - // Cursor is currently at end of text, just go to new line + // Move to new line for suggestions stdout.write("\n"); for (let i = 0; i < suggestions.length; i++) { @@ -137,30 +223,26 @@ export function autocompleteInput(config: AutocompleteConfig): Promise { } } - // Move cursor back up to input line (accounting for wrapped lines) - const linesFromEnd = totalLines - 1 - cursorRow; - stdout.write(CURSOR_UP(suggestions.length + linesFromEnd)); + lastRenderedLines = suggestions.length; + + // Move cursor back up to input line + stdout.write(CURSOR_UP(suggestions.length)); stdout.write(CURSOR_TO_COL(cursorCol)); + } else { + lastRenderedLines = 0; } } else { suggestions = []; selectedIndex = -1; + lastRenderedLines = 0; } - // Position cursor for wrapped text - // After writing, cursor is at end of text. Move to correct position. - // Go back to start of input block, then move to target row/col - const endRow = totalLines - 1; - if (endRow > cursorRow) { - stdout.write(CURSOR_UP(endRow - cursorRow)); - } + // Position cursor correctly within input stdout.write(CURSOR_TO_COL(cursorCol)); }; const submit = (value: string) => { - // Clear suggestions before submitting - stdout.write(RESTORE_CURSOR); - stdout.write(CLEAR_TO_END); + clearDisplay(); stdout.write(`${prompt}${value}\n`); cleanup(); resolve(value); @@ -171,12 +253,14 @@ export function autocompleteInput(config: AutocompleteConfig): Promise { // Handle Ctrl+C if (key.ctrl && key.name === "c") { + clearDisplay(); cleanup(); process.exit(0); } // Handle Ctrl+D (EOF) if (key.ctrl && key.name === "d") { + clearDisplay(); cleanup(); stdout.write("\n"); resolve(""); diff --git a/src/agent/cli/commands/skills.ts b/src/agent/cli/commands/skills.ts index 9ec6126f..34cea252 100644 --- a/src/agent/cli/commands/skills.ts +++ b/src/agent/cli/commands/skills.ts @@ -29,6 +29,7 @@ interface ParsedArgs { args: string[]; verbose: boolean; force: boolean; + profile?: string; } function printHelp() { @@ -46,6 +47,7 @@ ${cyan("Commands:")} ${cyan("Options:")} ${yellow("-v, --verbose")} Show more details ${yellow("-f, --force")} Force overwrite existing skill + ${yellow("-p, --profile")} Install to specific profile's skills directory ${cyan("Source Formats:")} ${dim("(for add command)")} owner/repo Clone entire repository @@ -68,6 +70,9 @@ ${cyan("Examples:")} ${dim("# Remove a skill")} multica skills remove agent-skills + + ${dim("# Add skill to a specific profile")} + multica skills add owner/repo --profile my-agent `); } @@ -75,6 +80,7 @@ function parseArgs(argv: string[]): ParsedArgs { const args = [...argv]; let verbose = false; let force = false; + let profile: string | undefined; const positional: string[] = []; while (args.length > 0) { @@ -91,8 +97,13 @@ function parseArgs(argv: string[]): ParsedArgs { continue; } + if (arg === "--profile" || arg === "-p") { + profile = args.shift(); + continue; + } + if (arg === "--help" || arg === "-h") { - return { command: "help", args: [], verbose, force }; + return { command: "help", args: [], verbose, force, profile }; } positional.push(arg); @@ -101,7 +112,7 @@ function parseArgs(argv: string[]): ParsedArgs { const command = (positional[0] ?? "help") as Command; const commandArgs = positional.slice(1); - return { command, args: commandArgs, verbose, force }; + return { command, args: commandArgs, verbose, force, profile }; } function cmdList(manager: SkillManager, verbose: boolean): void { @@ -399,12 +410,14 @@ async function cmdInstall(manager: SkillManager, skillId: string, installId?: st } } -async function cmdAdd(source: string, force: boolean): Promise { - console.log(`\nAdding skill from '${source}'...`); +async function cmdAdd(source: string, force: boolean, profileId?: string): Promise { + const destination = profileId ? `profile '${profileId}'` : "global skills"; + console.log(`\nAdding skill from '${source}' to ${destination}...`); const result = await addSkill({ source, force, + profileId, }); if (result.ok) { @@ -454,7 +467,7 @@ async function cmdListInstalled(): Promise { } export async function skillsCommand(args: string[]): Promise { - const { command, args: cmdArgs, verbose, force } = parseArgs(args); + const { command, args: cmdArgs, verbose, force, profile } = parseArgs(args); if (command === "help") { printHelp(); @@ -464,14 +477,17 @@ export async function skillsCommand(args: string[]): Promise { switch (command) { case "add": if (!cmdArgs[0]) { - console.error("Usage: multica skills add [--force]"); + console.error("Usage: multica skills add [--force] [--profile ]"); console.error("\nSource formats:"); console.error(" owner/repo Clone entire repository"); console.error(" owner/repo/skill-name Clone single skill directory"); console.error(" owner/repo@branch Clone specific branch/tag"); + console.error("\nOptions:"); + console.error(" --force, -f Overwrite existing skill"); + console.error(" --profile, -p Install to profile's skills directory"); process.exit(1); } - await cmdAdd(cmdArgs[0], force); + await cmdAdd(cmdArgs[0], force, profile); return; case "remove": diff --git a/src/agent/skills/README.md b/src/agent/skills/README.md index 5a8e51f0..213ee4e8 100644 --- a/src/agent/skills/README.md +++ b/src/agent/skills/README.md @@ -203,6 +203,40 @@ Higher priority sources override skills with the same ID. On first run, bundled skills are automatically copied to the managed directory (`~/.super-multica/skills/`). This makes them editable and allows users to customize or remove them. +### Adding Profile-Specific Skills + +You can install skills directly to a profile using the `--profile` option: + +```bash +# Install skill to a specific profile +multica skills add owner/repo --profile my-agent + +# Install with force overwrite +multica skills add owner/repo/skill-name --profile my-agent --force +``` + +Alternatively, create them manually: + +```bash +# Create profile skills directory +mkdir -p ~/.super-multica/agent-profiles//skills/ + +# Create the SKILL.md file +cat > ~/.super-multica/agent-profiles//skills//SKILL.md << 'EOF' +--- +name: My Profile Skill +version: 1.0.0 +description: A skill specific to this profile +--- + +# Instructions + +Your skill instructions here... +EOF +``` + +Profile skills automatically override managed skills with the same ID, allowing per-profile customization. + ### Eligibility Filtering After loading, skills are filtered by: @@ -219,13 +253,15 @@ Only skills passing all checks are marked as eligible. ## CLI Commands +All commands use the unified `multica` CLI (or `pnpm multica` during development). + ### List Skills ```bash -pnpm skills:cli list # List all skills -pnpm skills:cli list -v # Verbose mode -pnpm skills:cli status # Summary status -pnpm skills:cli status # Specific skill status +multica skills list # List all skills +multica skills list -v # Verbose mode +multica skills status # Summary status +multica skills status # Specific skill status ``` ### Install from GitHub @@ -247,32 +283,32 @@ anthropics/skills/ Install the entire repository (all 16 skills): ```bash -pnpm skills:cli add anthropics/skills +multica skills add anthropics/skills # Installs to: ~/.super-multica/skills/skills/ # All skills available: algorithmic-art, brand-guidelines, pdf, etc. ``` Install a single skill only: ```bash -pnpm skills:cli add anthropics/skills/skills/pdf +multica skills add anthropics/skills/skills/pdf # Installs to: ~/.super-multica/skills/pdf/ # Only the pdf skill is installed ``` Install from a specific branch or tag: ```bash -pnpm skills:cli add anthropics/skills@main +multica skills add anthropics/skills@main ``` Using full URL: ```bash -pnpm skills:cli add https://github.com/anthropics/skills -pnpm skills:cli add https://github.com/anthropics/skills/tree/main/skills/pdf +multica skills add https://github.com/anthropics/skills +multica skills add https://github.com/anthropics/skills/tree/main/skills/pdf ``` Force overwrite existing: ```bash -pnpm skills:cli add anthropics/skills --force +multica skills add anthropics/skills --force ``` **Supported formats:** @@ -288,15 +324,15 @@ pnpm skills:cli add anthropics/skills --force ### Remove Skills ```bash -pnpm skills:cli remove # Remove installed skill -pnpm skills:cli remove # List installed skills +multica skills remove # Remove installed skill +multica skills remove # List installed skills ``` ### Install Dependencies ```bash -pnpm skills:cli install # Install skill dependencies -pnpm skills:cli install # Specific install option +multica skills install # Install skill dependencies +multica skills install # Specific install option ``` --- @@ -308,8 +344,8 @@ The `status` command provides detailed diagnostics for understanding why skills ### Summary Status ```bash -pnpm skills:cli status # Show summary with grouping by issue type -pnpm skills:cli status -v # Verbose mode with hints +multica skills status # Show summary with grouping by issue type +multica skills status -v # Verbose mode with hints ``` Output shows: @@ -319,7 +355,7 @@ Output shows: ### Detailed Skill Status ```bash -pnpm skills:cli status +multica skills status ``` Output includes: diff --git a/src/agent/skills/README.zh-CN.md b/src/agent/skills/README.zh-CN.md index fd72617e..39aa5501 100644 --- a/src/agent/skills/README.zh-CN.md +++ b/src/agent/skills/README.zh-CN.md @@ -203,6 +203,40 @@ Skills 从两个来源加载,优先级从低到高: 首次运行时,内置 skills 会自动复制到 managed 目录(`~/.super-multica/skills/`)。这使得用户可以编辑或删除它们。 +### 添加 Profile 专属 Skills + +可以使用 `--profile` 选项直接安装 skills 到特定 profile: + +```bash +# 安装 skill 到特定 profile +multica skills add owner/repo --profile my-agent + +# 强制覆盖安装 +multica skills add owner/repo/skill-name --profile my-agent --force +``` + +也可以手动创建: + +```bash +# 创建 profile skills 目录 +mkdir -p ~/.super-multica/agent-profiles//skills/ + +# 创建 SKILL.md 文件 +cat > ~/.super-multica/agent-profiles//skills//SKILL.md << 'EOF' +--- +name: My Profile Skill +version: 1.0.0 +description: 此 profile 专属的 skill +--- + +# 说明 + +你的 skill 说明内容... +EOF +``` + +Profile skills 会自动覆盖同 ID 的 managed skills,允许按 profile 自定义。 + ### 资格过滤 加载后,skills 会按以下条件过滤: @@ -219,13 +253,15 @@ Skills 从两个来源加载,优先级从低到高: ## CLI 命令 +所有命令使用统一的 `multica` CLI(开发时使用 `pnpm multica`)。 + ### 列出 Skills ```bash -pnpm skills:cli list # 列出所有 skills -pnpm skills:cli list -v # 详细模式 -pnpm skills:cli status # 汇总状态 -pnpm skills:cli status # 特定 skill 状态 +multica skills list # 列出所有 skills +multica skills list -v # 详细模式 +multica skills status # 汇总状态 +multica skills status # 特定 skill 状态 ``` ### 从 GitHub 安装 @@ -247,32 +283,32 @@ anthropics/skills/ 安装整个仓库(所有 16 个 skills): ```bash -pnpm skills:cli add anthropics/skills +multica skills add anthropics/skills # 安装到:~/.super-multica/skills/skills/ # 所有 skills 可用:algorithmic-art、brand-guidelines、pdf 等 ``` 只安装单个 skill: ```bash -pnpm skills:cli add anthropics/skills/skills/pdf +multica skills add anthropics/skills/skills/pdf # 安装到:~/.super-multica/skills/pdf/ # 只安装 pdf skill ``` 从特定分支或标签安装: ```bash -pnpm skills:cli add anthropics/skills@main +multica skills add anthropics/skills@main ``` 使用完整 URL: ```bash -pnpm skills:cli add https://github.com/anthropics/skills -pnpm skills:cli add https://github.com/anthropics/skills/tree/main/skills/pdf +multica skills add https://github.com/anthropics/skills +multica skills add https://github.com/anthropics/skills/tree/main/skills/pdf ``` 强制覆盖现有: ```bash -pnpm skills:cli add anthropics/skills --force +multica skills add anthropics/skills --force ``` **支持的格式:** @@ -288,15 +324,15 @@ pnpm skills:cli add anthropics/skills --force ### 移除 Skills ```bash -pnpm skills:cli remove # 移除已安装的 skill -pnpm skills:cli remove # 列出已安装的 skills +multica skills remove # 移除已安装的 skill +multica skills remove # 列出已安装的 skills ``` ### 安装依赖 ```bash -pnpm skills:cli install # 安装 skill 依赖 -pnpm skills:cli install # 特定安装选项 +multica skills install # 安装 skill 依赖 +multica skills install # 特定安装选项 ``` --- @@ -308,8 +344,8 @@ pnpm skills:cli install # 特定安装选项 ### 汇总状态 ```bash -pnpm skills:cli status # 显示按问题类型分组的汇总 -pnpm skills:cli status -v # 详细模式带提示 +multica skills status # 显示按问题类型分组的汇总 +multica skills status -v # 详细模式带提示 ``` 输出显示: @@ -319,7 +355,7 @@ pnpm skills:cli status -v # 详细模式带提示 ### 详细 Skill 状态 ```bash -pnpm skills:cli status +multica skills status ``` 输出包括: @@ -387,7 +423,7 @@ import { **Skill 未显示为符合条件?** -运行 `pnpm skills:cli status ` 查看详细诊断及可操作的提示。 +运行 `multica skills status ` 查看详细诊断及可操作的提示。 **覆盖内置 skill?** diff --git a/src/agent/skills/add.ts b/src/agent/skills/add.ts index c3b9bb8e..05ec6adf 100644 --- a/src/agent/skills/add.ts +++ b/src/agent/skills/add.ts @@ -32,6 +32,8 @@ export interface SkillAddRequest { force?: boolean | undefined; /** Timeout in milliseconds (default: 60000) */ timeoutMs?: number | undefined; + /** Profile ID to install to (installs to profile's skills directory instead of global) */ + profileId?: string | undefined; } export interface SkillAddResult { @@ -66,6 +68,19 @@ const DEFAULT_TIMEOUT_MS = 60_000; /** Skills directory: ~/.super-multica/skills */ const SKILLS_DIR = join(DATA_DIR, "skills"); +/** Profile base directory: ~/.super-multica/agent-profiles */ +const PROFILE_BASE_DIR = join(DATA_DIR, "agent-profiles"); + +/** + * Get the skills directory for a given profile or global + */ +function getSkillsDir(profileId?: string): string { + if (profileId) { + return join(PROFILE_BASE_DIR, profileId, "skills"); + } + return SKILLS_DIR; +} + // ============================================================================ // Source Parsing // ============================================================================ @@ -396,9 +411,12 @@ async function addSkillInternal(request: SkillAddRequest): Promise 0 ? skillNames : [targetName], }; diff --git a/src/agent/tools/README.md b/src/agent/tools/README.md index 7a3c548f..80087453 100644 --- a/src/agent/tools/README.md +++ b/src/agent/tools/README.md @@ -99,18 +99,20 @@ Profiles are predefined tool sets for common use cases: ### CLI Usage +All commands use the unified `multica` CLI (or `pnpm multica` during development). + ```bash # Use a specific profile -pnpm agent:cli --tools-profile coding "list files" +multica run --tools-profile coding "list files" # Minimal profile with specific tools allowed -pnpm agent:cli --tools-profile minimal --tools-allow exec "run ls" +multica run --tools-profile minimal --tools-allow exec "run ls" # Deny specific tools -pnpm agent:cli --tools-deny exec,process "read file.txt" +multica run --tools-deny exec,process "read file.txt" # Use tool groups -pnpm agent:cli --tools-allow group:fs "read config.json" +multica run --tools-allow group:fs "read config.json" ``` ### Programmatic Usage @@ -146,19 +148,19 @@ Use the tools CLI to inspect and test configurations: ```bash # List all available tools -pnpm tools:cli list +multica tools list # List tools after applying a profile -pnpm tools:cli list --profile coding +multica tools list --profile coding # List tools with deny rules -pnpm tools:cli list --profile coding --deny exec +multica tools list --profile coding --deny exec # Show all tool groups -pnpm tools:cli groups +multica tools groups # Show all profiles -pnpm tools:cli profiles +multica tools profiles ``` ## Policy System Details @@ -261,7 +263,7 @@ export const TOOL_GROUPS: Record = { Run the policy system tests: ```bash -npx tsx src/agent/tools/policy.test.ts +pnpm test src/agent/tools/policy.test.ts ``` ## Agent Profile Integration @@ -315,7 +317,7 @@ When both Profile config and CLI options are provided: # Profile has tools.profile = "coding" # CLI adds --tools-deny exec # Result: coding profile without exec tool -pnpm agent:cli --profile my-agent --tools-deny exec "list files" +multica run --profile my-agent --tools-deny exec "list files" ``` ## Future Tools diff --git a/src/agent/tools/README.zh-CN.md b/src/agent/tools/README.zh-CN.md index dea6c2ce..5ac1a99e 100644 --- a/src/agent/tools/README.zh-CN.md +++ b/src/agent/tools/README.zh-CN.md @@ -99,18 +99,20 @@ ### CLI 使用 +所有命令使用统一的 `multica` CLI(开发时使用 `pnpm multica`)。 + ```bash # 使用特定配置文件 -pnpm agent:cli --tools-profile coding "list files" +multica run --tools-profile coding "list files" # 最小配置文件 + 允许特定工具 -pnpm agent:cli --tools-profile minimal --tools-allow exec "run ls" +multica run --tools-profile minimal --tools-allow exec "run ls" # 禁止特定工具 -pnpm agent:cli --tools-deny exec,process "read file.txt" +multica run --tools-deny exec,process "read file.txt" # 使用工具组 -pnpm agent:cli --tools-allow group:fs "read config.json" +multica run --tools-allow group:fs "read config.json" ``` ### 编程使用 @@ -146,19 +148,19 @@ const agent = new Agent({ ```bash # 列出所有可用工具 -pnpm tools:cli list +multica tools list # 列出应用配置文件后的工具 -pnpm tools:cli list --profile coding +multica tools list --profile coding # 列出带有禁止规则的工具 -pnpm tools:cli list --profile coding --deny exec +multica tools list --profile coding --deny exec # 显示所有工具组 -pnpm tools:cli groups +multica tools groups # 显示所有配置文件 -pnpm tools:cli profiles +multica tools profiles ``` ## 策略系统详情 @@ -261,7 +263,7 @@ export const TOOL_GROUPS: Record = { 运行策略系统测试: ```bash -npx tsx src/agent/tools/policy.test.ts +pnpm test src/agent/tools/policy.test.ts ``` ## Agent Profile 集成 @@ -315,7 +317,7 @@ npx tsx src/agent/tools/policy.test.ts # Profile 有 tools.profile = "coding" # CLI 添加 --tools-deny exec # 结果: coding 配置文件但没有 exec 工具 -pnpm agent:cli --profile my-agent --tools-deny exec "list files" +multica run --profile my-agent --tools-deny exec "list files" ``` ## 未来工具