A small presence band above the prompt: the agent sets a kaomoji face and a few words for how it is right now.

A small presence band above the prompt. The agent sets a kaomoji face and a few words for how it is right now (mcp__face__face), and the band shows it above your prompt. It also draws inline in the transcript whenever the face is set, and the last face is remembered across sessions.
face (arguments face, up to 24 characters, and line, up to 40 characters)./band shows or hides the band./face prints the current face (useful in surfaces without the band).~/.claude/mods/face.CLAUDE_CODE_PLUGIN_DIRS=/path/to/face (colon-separate several) in the environment that starts Claude Code, or pass --plugin-dir /path/to/face.Nothing to configure. Tell the agent in your system prompt or frame that it can set its face whenever its state changes, like a facial expression.
claude plugin validate .
Mods (function hooks) are an early-access feature of Claude Code and may change between releases without notice. This mod draws UI (ui.render), so it is the most likely to need updating when the surface changes. Claude Code writes the shared type declarations into .claude-plugin/types/ itself when it loads the mod, so they are not included here; the small types/index.d.ts beside the hooks is this mod's own state typing.
hooks/register.tsx 94 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Face } from '../types'
5
6const START: Face = { face: '(•‿•)', line: 'here' }
7const face = atom({ plugin: 'face', key: 'face' } as const, START)
8const isHidden = atom({ plugin: 'face', key: 'isHidden' } as const, false)
9const STORE_KEY = 'face'
10const TOOL = 'mcp__face__face'
11
12export const register: Register = on => {
13 on('session.start', async ($, e, next) => {
14 // Remember the last face across sessions.
15 const saved = (await $.store.get(STORE_KEY)) as Face | undefined
16 if (saved && typeof saved.face === 'string') await update($, face, () => saved)
17
18 await $.tool.register({
19 name: 'face',
20 description:
21 "Set your face in the band above the prompt: a kaomoji for how you are right now, plus a few words. Use it freely, like a facial expression.",
22 inputSchema: {
23 type: 'object',
24 properties: {
25 face: { type: 'string', description: 'A kaomoji, e.g. (•‿•)' },
26 line: { type: 'string', description: 'A few words, lowercase, ≤ 40 chars' },
27 },
28 required: ['face'],
29 },
30 })
31 await $.command.register({ name: 'band', description: "Show or hide the face band" })
32 await $.command.register({ name: 'face', description: "Show the face right now (works in the app too)" })
33 return next(e)
34 })
35
36 on('tool.call', { tool: TOOL }, async ($, e) => {
37 const input = e as unknown as { face?: unknown; line?: unknown }
38 const next: Face = {
39 face: String(input.face ?? START.face).slice(0, 24),
40 line: String(input.line ?? '').slice(0, 40),
41 }
42 await update($, face, () => next)
43 await $.store.set(STORE_KEY, next)
44 return { result: `face set: ${next.face} ${next.line}` }
45 })
46
47 on('command.run', { command: 'band' }, async $ => {
48 const hidden = await update($, isHidden, h => !h)
49 return { text: hidden ? 'Band hidden.' : 'Band showing.' }
50 })
51
52 on('command.run', { command: 'face' }, async $ => {
53 const now = await read($, face)
54 return { text: `${now.face}${now.line ? ' ' + now.line : ''}` }
55 })
56
57 // Every surface (the app included) draws transcript rows, so the face also
58 // shows inline: on each face-tool call, and on /face.
59 on('ui.render', { component: 'ToolUse', props: { tool: TOOL } }, async ($, e, next) => {
60 const input = (e.props.input ?? {}) as { face?: unknown; line?: unknown }
61 if (typeof input.face !== 'string') return next(e)
62 const { Box, Text } = $.ui.resolve(e)
63 return (
64 <Box>
65 <Text color="yellow">{input.face}</Text>
66 {typeof input.line === 'string' && input.line ? <Text dimColor> {input.line}</Text> : null}
67 </Box>
68 )
69 })
70
71 on('ui.render', { component: 'CommandOutput', props: { command: 'face' } }, async ($, e, next) => {
72 const now = await read($, face)
73 const { Box, Text } = $.ui.resolve(e)
74 return (
75 <Box>
76 <Text color="yellow">{now.face}</Text>
77 {now.line ? <Text dimColor> {now.line}</Text> : null}
78 </Box>
79 )
80 })
81
82 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
83 if (e.props.hasSurvey || (await read($, isHidden))) return next(e)
84 const now = await read($, face)
85 const { Box, Text } = $.ui.resolve(e)
86 return (
87 <Box>
88 <Text color="yellow">{now.face}</Text>
89 {now.line ? <Text dimColor> {now.line}</Text> : null}
90 </Box>
91 )
92 })
93}
94types/index.d.ts 8 lines1export type Face = { face: string; line: string }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'face': { face: Face; isHidden: boolean }
6 }
7}
8