Zlafa: a bowl that fills as the context window fills, with warnings in Moroccan Darija

Claude Code mods that speak Moroccan Darija, edged like a zrabi (Berber rug).
Claude kaykhddem, w nta katchrb atay. 🫖
◇─◆─◇─◆─◇─◆─◇─◆─◇─◆─◇─◆─◇─◆─◇─◆─◇─◆─◇
🫖 4:32 l-brrad kaytbokh... 🫖
Atay wjed? Khwi lik kas tani, ana mazal khddam
◆─◇─◆─◇─◆─◇─◆─◇─◆─◇─◆─◇─◆─◇─◆ [Khbbi]
claude plugin marketplace add Mohamedballouch/darija-mods
claude plugin install darija-narrator@darija-mods
claude plugin install zlafa@darija-mods
claude plugin install hajib@darija-mods
claude plugin install brrad@darija-mods
Install all four, or only the ones you like.
| Mod | What it does | Example | |
|---|---|---|---|
| darija-narrator | Narrates every step Claude takes, live above the prompt | 🔍 Khllini nchouf ach kayn f app.ts | |
| zlafa (الزلافة) | A bowl that fills as the context window fills; warns at 80% and 95% | 🥣 [██████████░░] 82% L-zlafa 9rbat t3mer, fkker f /compact | |
| hajib (الحاجب) | Blocks catastrophic commands (wiping /, formatting a disk…) and asks before risky ones (git push --force, sudo, `curl \ | sh, editing .env`…) | 🛑 Force push! Ghadi ybddel l-historique li f remote. N-khllih ydouz? |
| brrad (البراد) | When a task runs long, tells you to step away for tea; calls you back when it's done and counts your cups for the day | Hadchi ghadi ytwl chwiya... khud lik pause w chi brrad d atay 🫖 |
Hajib only ever makes approvals stricter: it never auto-approves anything.
In the plugin config menu:
90; later nudges (4 min, 8 min, 15 min) scale from itDarija is written in Latin letters with the usual Arabizi numbers: 3 = ع · 7 = ح · 9 = ق · gh = غ · kh = خ · ch = ش
Every band sits between two rows of rug fringe (◇─◆─◇) in terracotta #B5452A. The text keeps your terminal's own colours, so it reads the same on light and dark themes; only warnings turn red.
Each mod is a folder with .claude-plugin/plugin.json and a hooks module under hooks/.
claude plugin validate ./brrad
claude plugin test ./brrad
claude --plugin-dir ./brrad
Better Darija wording is very welcome: the phrases live in darija-narrator/hooks/darija.ts, zlafa/hooks/zlafa.ts, hajib/hooks/hajib.ts and brrad/hooks/brrad.ts.
MIT
hooks/register.tsx 69 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import { level, line, message } from './zlafa'
5
6import { ALERT, TERRACOTTA, fringe } from './zrabi'
7
8const percent = atom({ plugin: 'zlafa', key: 'percent' } as const, null as number | null)
9const lastLevel = atom({ plugin: 'zlafa', key: 'level' } as const, 0)
10const isHidden = atom({ plugin: 'zlafa', key: 'isHidden' } as const, false)
11
12async function refresh($: EngineInterface) {
13 const { context } = await $.session.usage()
14
15 if (context.percent === undefined) {
16 return
17 }
18
19 const p = context.percent
20 const before = await read($, lastLevel)
21 const now = level(p)
22
23 await update($, percent, () => p)
24 await update($, lastLevel, () => now)
25
26 if (now > before && now >= 2) {
27 $.ui.toast(`🥣 ${message(p)}`)
28 }
29}
30
31export const register: Register = on => {
32 on('tool.call', async ($, e, next) => {
33 const ran = await next(e)
34 await refresh($).catch(() => undefined)
35
36 return ran
37 })
38
39 on('turn.complete', async ($, e, next) => {
40 await refresh($).catch(() => undefined)
41
42 return next(e)
43 })
44
45 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
46 const p = await read($, percent)
47
48 if (e.props.hasSurvey || p === null || (await read($, isHidden))) {
49 return next(e)
50 }
51
52 const { Box, Button, Text } = $.ui.resolve(e)
53 const width = e.props.bodyColumns - 2
54
55 return (
56 <Box flexDirection="column">
57 <Text color={TERRACOTTA}>{fringe(width)}</Text>
58 <Box paddingX={2}>
59 <Text color={level(p) >= 2 ? ALERT : undefined}>{line(p)}</Text>
60 </Box>
61 <Box>
62 <Text color={TERRACOTTA}>{fringe(width - 10, 2)} </Text>
63 <Button key="hide" label="Khbbi" onPress={() => update($, isHidden, () => true)} />
64 </Box>
65 </Box>
66 )
67 })
68}
69hooks/zlafa.ts 22 lines1export const bar = (percent: number, width = 12): string => {
2 const p = Math.max(0, Math.min(100, Math.round(percent)))
3 const filled = Math.round((p / 100) * width)
4
5 return '█'.repeat(filled) + '░'.repeat(width - filled)
6}
7
8// 0 = fargha, 1 = kat3mar, 2 = kadi3, 3 = tfid
9export const level = (percent: number): 0 | 1 | 2 | 3 =>
10 percent >= 95 ? 3 : percent >= 80 ? 2 : percent >= 50 ? 1 : 0
11
12export const message = (percent: number): string =>
13 [
14 'Zlafa mazal khawya, khddem 3la khatrek',
15 'Zlafa bdat kat3mer, walakin kolchi mazal mzyan',
16 'Zlafa 9rbat t3mer, red l-bal w fkker f /compact',
17 'Safi, ghadi tfid! Dir /compact daba serbi',
18 ][level(percent)]!
19
20export const line = (percent: number): string =>
21 `🥣 [${bar(percent)}] ${Math.round(percent)}% ${message(percent)}`
22hooks/zrabi.ts 18 lines1// Zrabi (الزربية): the diamond fringe of a Berber rug, in terracotta.
2
3export const TERRACOTTA = '#B5452A'
4export const ALERT = '#E5534B'
5
6const UNIT = '◇─◆─'
7
8// A fringe `width` cells wide that starts and ends on a diamond; `offset` 2 starts on ◆.
9export const fringe = (width: number, offset = 0): string => {
10 let s = ''
11
12 for (let i = 0; i < Math.max(0, width); i++) {
13 s += UNIT[(i + offset) % UNIT.length]
14 }
15
16 return s.endsWith('─') ? s.slice(0, -1) : s
17}
18types/index.d.ts 8 lines1export type Percent = number | null
2
3declare module 'claude-code' {
4 interface PluginState {
5 zlafa: { percent: Percent; level: number; isHidden: boolean }
6 }
7}
8