()
| 48 | } |
| 49 | |
| 50 | function loadCustomModes(): CCBMode[] { |
| 51 | if (customModes !== null) return customModes |
| 52 | customModes = [] |
| 53 | try { |
| 54 | const modesDir = join(getClaudeConfigHomeDir(), 'modes') |
| 55 | if (!existsSync(modesDir)) { |
| 56 | mkdirSync(modesDir, { recursive: true }) |
| 57 | } |
| 58 | const files = readdirSync(modesDir).filter( |
| 59 | f => f.endsWith('.yaml') || f.endsWith('.yml') || f.endsWith('.md'), |
| 60 | ) |
| 61 | for (const file of files) { |
| 62 | try { |
| 63 | const raw = readFileSync(join(modesDir, file), 'utf-8') |
| 64 | let data: Record<string, unknown> |
| 65 | if (file.endsWith('.md')) { |
| 66 | const { frontmatter, body } = parseMarkdownFrontmatter(raw) |
| 67 | data = { ...frontmatter, system_prompt: body } |
| 68 | if (!data.slug) { |
| 69 | data.slug = data.name ? kebabCase(String(data.name)) : '' |
| 70 | } |
| 71 | data.icon = data.icon || '🤖' |
| 72 | } else { |
| 73 | data = parseYaml(raw) as Record<string, unknown> |
| 74 | } |
| 75 | if (!data.slug || !data.name) continue |
| 76 | customModes.push({ |
| 77 | name: String(data.name), |
| 78 | slug: String(data.slug), |
| 79 | description: String(data.description || ''), |
| 80 | icon: String(data.icon || '🔧'), |
| 81 | systemPrompt: String(data.system_prompt || ''), |
| 82 | model: data.model ? String(data.model) : undefined, |
| 83 | ui: { |
| 84 | accentColor: String( |
| 85 | (data.ui as Record<string, unknown>)?.accent_color || '#00D4AA', |
| 86 | ), |
| 87 | promptPrefix: String( |
| 88 | (data.ui as Record<string, unknown>)?.prompt_prefix || '', |
| 89 | ), |
| 90 | }, |
| 91 | permissions: { |
| 92 | defaultMode: |
| 93 | ((data.permissions as Record<string, unknown>) |
| 94 | ?.default_mode as CCBMode['permissions']['defaultMode']) || |
| 95 | 'default', |
| 96 | memoryExtract: Boolean( |
| 97 | (data.permissions as Record<string, unknown>)?.memory_extract ?? |
| 98 | true, |
| 99 | ), |
| 100 | }, |
| 101 | responseStyle: { |
| 102 | verbosity: |
| 103 | ((data.response_style as Record<string, unknown>) |
| 104 | ?.verbosity as CCBMode['responseStyle']['verbosity']) || |
| 105 | 'normal', |
| 106 | }, |
| 107 | }) |
no test coverage detected