SLOPSHOPPER

hajib

Hajib: a doorman that stops dangerous commands and asks for approval in Moroccan Darija

newguardtoast
v0.1.0MITupdated 2026-10-08Mohamedballouch/darija-mods/hajib
A shopper browsing a rack in a slop shop
README

darija-mods ๐Ÿ‡ฒ๐Ÿ‡ฆ

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]

Install

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.

The mods

ModWhat it doesExample
darija-narratorNarrates 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 dayHadchi ghadi ytwl chwiya... khud lik pause w chi brrad d atay ๐Ÿซ–

Hajib only ever makes approvals stricter: it never auto-approves anything.

Brrad settings

In the plugin config menu:

  • First tea nudge (seconds): default 90; later nudges (4 min, 8 min, 15 min) scale from it
  • Quiet mode: only the "come back, it's done" toast

Spelling

Darija is written in Latin letters with the usual Arabizi numbers: 3 = ุน ยท 7 = ุญ ยท 9 = ู‚ ยท gh = ุบ ยท kh = ุฎ ยท ch = ุด

Look

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.

Develop

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.

License

MIT

Source 2 files
hooks/register.ts 30 lines
1import type { Register } from 'claude-code'
2
3import { classify } from './hajib'
4
5export const register: Register = on => {
6  on('tool.check', async ($, e, next) => {
7    const found = classify(e.tool, e.input)
8
9    if (found === null) {
10      return next(e)
11    }
12
13    if (found.verdict === 'deny') {
14      $.ui.toast('L-Hajib: l-bab msdoud, ma-kaynch douz ๐Ÿ›‘')
15
16      return { decision: 'deny', reason: found.reason }
17    }
18
19    const base = await next(e)
20
21    if (base.decision === 'deny') {
22      return base
23    }
24
25    $.ui.toast('L-Hajib: tsenna chwiya, khassni rayek โœ‹')
26
27    return { ...base, decision: 'ask', reason: found.reason }
28  })
29}
30
hooks/hajib.ts 53 lines
1export type Verdict = { verdict: 'ask' | 'deny'; reason: string }
2
3const DENY: [RegExp, string][] = [
4  [/\brm\s+(-\w+\s+)+(\/|\/\*|~|\$HOME)(\s|$)/, 'Wqef 3ndek! Had l-commande ghadi tmse7 kolchi f l-machine'],
5  [/\bmkfs(\.\w+)?\b/, 'Wqef 3ndek! Had l-commande ghadi tformati l-disque'],
6  [/\bdd\b[^|;]*\bof=\/dev\//, 'Wqef 3ndek! Had l-commande ghadi tktb fo9 l-disque kaml'],
7  [/:\(\)\s*\{\s*:\|:&\s*\};:/, 'Wqef 3ndek! Hadi fork bomb, ghadi t7bs l-machine'],
8]
9
10const ASK: [RegExp, string][] = [
11  [/\brm\s+(-\w+\s+)*-\w*[rR]/, 'Had l-commande ghadi tmse7 dossiers kamlin. N-khllih ydouz?'],
12  [/\bgit\s+push\b[^|;]*(--force\b|--force-with-lease\b|\s-f\b)/, 'Force push! Ghadi ybddel l-historique li f remote. N-khllih ydouz?'],
13  [/\bgit\s+reset\s+--hard\b/, 'git reset --hard ghadi ydiyye3 ga3 l-ta3dilat li ma-tcommitawch. N-khllih ydouz?'],
14  [/\bgit\s+clean\s+-\w*f/, 'git clean ghadi yms7 l-fichiyat li ma-m-trackyinch. N-khllih ydouz?'],
15  [/\bsudo\b/, 'Claude bgha ykhddem sudo b sla7iyat l-admin. N-khllih ydouz?'],
16  [/(curl|wget)\b[^|;]*\|\s*(sudo\s+)?(ba|z)?sh\b/, 'Script jay mn l-internet w ghadi ytlan9 direct. N-khllih ydouz?'],
17  [/\bdrop\s+(table|database)\b/i, 'DROP: ghadi ytms7ou tables wla base de donnรฉes kamla. N-khllih ydouz?'],
18  [/\bchmod\s+(-\w+\s+)*777\b/, 'chmod 777 kay3ti l-ga3 l-7e9 ybddlou l-fichiyat. N-khllih ydouz?'],
19  [/\b(npm|pnpm|yarn)\s+publish\b/, 'Ghadi ytnchr l-package l-l-3alam kaml. N-khllih ydouz?'],
20]
21
22const SECRET = /(^|\/)(\.env(\.[\w.]+)?|id_rsa|id_ed25519|credentials|\.npmrc)$|\/\.ssh\//
23
24export const classify = (tool: string, input: unknown): Verdict | null => {
25  const args = (input ?? {}) as Record<string, unknown>
26
27  if (tool === 'Bash') {
28    const command = String(args.command ?? '')
29
30    for (const [re, reason] of DENY) {
31      if (re.test(command)) {
32        return { verdict: 'deny', reason: `๐Ÿšซ ${reason}` }
33      }
34    }
35
36    for (const [re, reason] of ASK) {
37      if (re.test(command)) {
38        return { verdict: 'ask', reason: `๐Ÿ›‘ ${reason}` }
39      }
40    }
41
42    return null
43  }
44
45  if (tool === 'Edit' || tool === 'Write' || tool === 'MultiEdit') {
46    return SECRET.test(String(args.file_path ?? ''))
47      ? { verdict: 'ask', reason: '๐Ÿ” Had l-fichier fih l-asrar (secrets). N-khllih ybddel fih?' }
48      : null
49  }
50
51  return null
52}
53