feat: add mode system with 6 AI personality presets (#1255)

* docs: update contributors

* docs: update contributors

* feat: add mode system with 6 AI personality presets

Add a /mode command that lets users switch between 6 interaction
modes, each with distinct system prompts, UI themes, permission
defaults, and response verbosity:

- Default () — balanced, everyday development
- Gentle (🌸) — patient explanations for learning
- Dr. Sharp (🔍) — strict 3-phase code review workflow
- Workhorse (🐴) — auto-execute, minimal confirmations
- Token Saver (💰) — minimal replies to save tokens
- Super AI (🧠) — deep analysis, proactive suggestions

Custom modes can be defined via YAML files in ~/.claude/modes/.

New files:
- src/modes/types.ts — CCBMode interface
- src/modes/defaults.ts — 6 built-in mode presets
- src/modes/store.ts — mode state management with useSyncExternalStore
- src/commands/mode/index.ts — command registration
- src/commands/mode/mode.tsx — mode picker UI

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
YYMa
2026-06-05 21:01:02 +08:00
committed by GitHub
parent 6b205f5798
commit 9947ae75da
6 changed files with 429 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
import type { Command } from '../../commands.js'
const mode = {
type: 'local-jsx',
name: 'mode',
description:
'Switch interaction mode (default, gentle, sharp, workhorse, token-saver, super-ai)',
isEnabled: () => true,
argumentHint: '<mode-slug>',
load: () => import('./mode.js'),
} satisfies Command
export default mode

View File

@@ -0,0 +1,79 @@
import { useMemo } from 'react';
import { Box, Text } from '@anthropic/ink';
import { Select } from '../../components/CustomSelect/select.js';
import type { LocalJSXCommandCall, LocalJSXCommandOnDone } from '../../types/command.js';
import { getCurrentModeSlug, listModes, setCurrentMode } from '../../modes/store.js';
function ModePicker({ onDone }: { onDone: LocalJSXCommandOnDone }) {
const modes = listModes();
const currentSlug = getCurrentModeSlug();
const options = useMemo(
() =>
modes.map(m => ({
label: (
<Text>
{m.icon} {m.name}{' '}
<Text dimColor>
({m.slug}) {m.description}
</Text>
</Text>
),
value: m.slug,
})),
[modes],
);
function handleSelect(slug: string) {
setCurrentMode(slug);
const target = modes.find(m => m.slug === slug);
onDone(`${target?.icon} Mode switched to: ${target?.name} (${target?.slug}) — ${target?.description}`, {
display: 'system',
});
}
function handleCancel() {
onDone('Mode selection cancelled.', { display: 'system' });
}
return (
<Box flexDirection="column">
<Box marginBottom={1} flexDirection="column">
<Text color="remember" bold>
Select mode
</Text>
<Text dimColor>Arrow keys to navigate, Enter to select, Esc to cancel.</Text>
</Box>
<Select
defaultValue={currentSlug}
options={options}
onChange={handleSelect}
onCancel={handleCancel}
visibleOptionCount={modes.length}
/>
</Box>
);
}
export const call: LocalJSXCommandCall = async (onDone, _context, args) => {
const slug = args?.trim().toLowerCase();
if (slug) {
const modes = listModes();
const target = modes.find(m => m.slug === slug);
if (!target) {
const available = modes.map(m => `${m.icon} ${m.slug}${m.description}`).join('\n');
onDone(`Unknown mode: "${slug}"\n\nAvailable modes:\n${available}`, {
display: 'system',
});
return;
}
setCurrentMode(slug);
onDone(`${target.icon} Mode switched to: ${target.name} (${target.slug}) — ${target.description}`, {
display: 'system',
});
return;
}
return <ModePicker onDone={onDone} />;
};