Prayer times, the Hijri date, adhkar, a daily ayah, sunnah fasting, Ramadan, Jumu'ah and tasbih in Claude Code, each switched on or off from one dashboard…

One plugin that brings prayer times, the Hijri date, the morning and evening adhkar, a verse of the Qur'an, sunnah fasting, Ramadan, Jumu'ah and a tasbih for when your usage limit runs out into Claude Code, so a long coding session doesn't run straight past Asr. Each feature is switched on or off from one dashboard, /muslim.
| Feature | What it does | Command |
|---|---|---|
| Prayer times | Counts down to the next prayer under the prompt, reminds you before it and tells you when its time begins. | /prayer-times |
| Hijri date | Shows the Hijri date under the prompt, turning over at Maghrib, and announces Ramadan, the Eids, Arafah, Ashura and the other days that matter. | /hijri |
| Adhkar | Reminds you of the morning and evening adhkar and opens a pane to read them, with a counter. | /adhkar |
| Sunnah fasting | Reminds you the evening before Monday, Thursday, the white days, Arafah and Ashura, then gives suhur and iftar reminders on the days you fast. | /fasting |
| Ramadan | In Ramadan, counts down to imsak and iftar under the prompt, with suhur, imsak, iftar and Tarawih reminders and the odd nights of the last ten. | /ramadan |
| Jumu'ah | From Thursday's Maghrib, salawat and Al-Kahf; on Friday, a reminder to get ready for the prayer and the last hour after Asr. | /jumuah |
| Daily ayah | Shows a verse of the Qur'an in a band above the prompt, a new one each session. | /ayah |
| Tasbih | When your 5-hour or weekly limit runs out, invites you to SubhanAllah, Alhamdulillah and Allahu akbar, with a counter. | /tasbih |
Getting started: see Install. It is a Claude Code plugin built on function hooks and needs Claude Code 2.1.288 or newer; the plugin API is in early access and may change between releases.
/muslim
┌ Muslim mods ───────────────────────────────────────────────────┐
│ [● On ] 1 Prayer times │
│ 🕌 Asr 14:40 · in 1:10:00 │
│ [● On ] 2 Hijri date │
│ 📅 27 Rabi' al-Akhir 1448 │
│ [○ Off] 3 Adhkar │
│ Morning and evening adhkar; /adhkar │
│ ... │
│ Settings: /config, under muslim-mods [ Close ] │
└────────────────────────────────────────────────────────────────┘
/muslim opens the dashboard: each feature with what it shows right now. Keys 1 to 8 switch one on or off; x closes./muslim off ayah, /muslim on fasting do the same from the prompt (prayer, hijri, adhkar, fasting, ramadan, jumuah, ayah, tasbih); /muslim list shows which are on.All features are on to start with. Every option below is in /config under muslim-mods.
? for shortcuts 🕌 Asr 14:49 · in 1:12:05 · 🤲 Morning adhkar · /adhkar ← hint line under the prompt
? for shortcuts 🕌 Dhuhr 11:43 · in 2:10:05 · ☀️ Dhuha time ← while Dhuha or Tahajud lasts
🕌 Asr in 10m, at 14:49 ← toast, 10 minutes before
🕌 It's time for Asr (14:49). Time to pray. ← toast, when the time begins
☀️ Dhuha has begun (06:00), until shortly before Dhuhr. ← toast, for Dhuha and Tahajud
/prayer-times prints today's schedule:
Prayer times today (Semarang, Indonesia · Asia/Jakarta · Kemenag · detected from your IP)
Tahajud 00:36
Fajr 04:07
Sunrise 05:20
Dhuha 05:46
Dhuhr 11:29 ← next
Asr 14:36
Maghrib 17:35
Isha 18:44
It works anywhere in the world. It finds your location in this order:
city option, if you set it. Either a city name (Istanbul, Kuala Lumpur), which is looked up with the Open-Meteo geocoding API, or coordinates (41.01, 28.98), whose time zone is looked up once with the Open-Meteo forecast API (offline, your machine's time zone is used until the lookup succeeds). With coordinates, method: Auto uses MWL; set method to your country's.city is blank (the default). It asks ipwho.is, and ipapi.co if that fails. The lookup is repeated once a day so the times follow you when you travel.The countdown leads the hint line under the prompt and ticks every second; the other features add their parts after it.
The place found is cached, so later sessions start without a lookup and keep working offline. If it can't find you, the hint line says so and asks for a city.
To set your city from inside Claude Code:
/prayer-times Kuala Lumpur
Privacy: an IP lookup sends your IP address to ipwho.is (or ipapi.co). If you'd rather not, set city to your coordinates: the only request is one time-zone lookup to Open-Meteo, which carries the coordinates but not your IP's location.
Times are computed locally with the PrayTimes.org astronomical method. They were checked against the Aladhan API for Semarang, London (at midsummer), Istanbul, New York and Makkah, and agree to within a minute.
Europe/London, America/New_York, …), so daylight saving time is handled.method is Auto:| Method | Fajr / Isha | Used for |
|---|---|---|
| Kemenag | 20° / 18° | Indonesia |
| JAKIM | 20° / 18° | Malaysia, Singapore, Brunei |
| Makkah (Umm al-Qura) | 18.5° / 90 min after Maghrib | Saudi Arabia and the Gulf |
| Egypt | 19.5° / 17.5° | Egypt, the Levant, North Africa |
| Karachi | 18° / 18° | Pakistan, India, Bangladesh, Afghanistan |
| ISNA | 15° / 15° | United States, Canada |
| Diyanet | 18° / 17°, with Diyanet's own minute corrections | Turkey |
| MWL | 18° / 17° | everywhere else |
☀️ Dhuha time or 🌙 Tahajud time after the countdown. Dhuha time ends 10 minutes before Dhuhr, clear of the sun's zenith; Tahajud time ends at Fajr. The countdown itself only ever points at the five obligatory prayers.| Option | Default | |
|---|---|---|
city | blank | A city, or lat, lng. Blank detects it from your IP. |
method | Auto | Auto, Kemenag, JAKIM, MWL, ISNA, Egypt, Makkah, Karachi, Diyanet |
asr | Standard | Standard (Shafi'i, Maliki, Hanbali) or Hanafi |
ihtiyatMinutes | -1 | Minutes added to each time; -1 uses the method's own. |
reminderMinutes | 10 | How early to remind you; 0 turns the early reminder off. |
sunnahReminders | true | A toast when the time for Dhuha, and for Tahajud, begins, and a note on the hint line while it lasts. |
The 24 morning and evening adhkar from Hisnul Muslim (Fortress of the Muslim, chapter 27), each with its Arabic text, transliteration, English translation and how many times it is said.
🤲 Evening adhkar · /adhkar after the prayer countdown under the prompt until you finish./adhkar opens a pane on the adhkar for this time of day; /adhkar morning or /adhkar evening picks one.Evening adhkar · 2 of 21
Recite Surah al-Ikhlas, al-Falaq and an-Nas (112, 113, 114).
Al-Ikhlas (112): Say, He is Allah, [who is] One, …
[ 1 / 3 ] [ Previous ] [ Next ] [ Finish ] [ Close ]
| Key | |
|---|---|
c | Count one repetition. When a dhikr reaches its number, the pane moves on to the next. |
n / p | Next / previous. |
f | Finish: marks today's session done and closes the pane. |
x / Esc | Close the pane; your place is kept. /adhkar close does the same. |
Your place and counts are saved, so after closing Claude Code, /adhkar reopens at the same dhikr the same day. A new day starts from the top.
Where the evening wording differs ("amsayna" for "asbahna"), the pane shows the evening text or a note. Three are listed in the morning only (two of them, said 100 times, are once a day) and one in the evening only.
| Option | Default | |
|---|---|---|
reminderDelayMinutes | 15 | How long after Fajr and Asr begin to remind you. |
showArabic | false | Show the Arabic text in the pane. Off by default: most terminals draw Arabic as unjoined letters, left to right. Turn it on if yours shapes Arabic properly. |
🌙 The night of Jumu'ah has begun: send salawat on the Prophet often (Abu Dawud 1047), and read Al-Kahf before tomorrow's Maghrib.
? for shortcuts 🕌 Dhuhr 11:30 · in 1:00:00 · 📅 28 Rabi' al-Akhir 1448 · 📖 Al-Kahf · /jumuah read
🕌 The Friday prayer is at Dhuhr, 11:30, in 45m: time to get ready and go early.
🤲 Friday after Asr: the last hour of Jumu'ah, when dua is answered (Abu Dawud 1048).
📖 Al-Kahf stays on the hint line until you run /jumuah read./jumuah lists the sunnah of the day with a ✓ on Al-Kahf once read; midweek, when the next Jumu'ah is.| Option | Default | |
|---|---|---|
jumuahReminderMinutes | 45 | For those who attend the Friday prayer: how early to remind you; 0 turns it off. |
kahf | true | Al-Kahf on the hint line until you've read it. |
📖 "So remember Me; I will remember you. And be grateful to Me and do not deny Me."
— Al-Baqara (The Cow) 2:152 ↻ ×
/clear, from 59 verses that read well on their own (2:152, 13:28, 39:53, 65:3, 94:5, …)./ayah prints the current verse with its Arabic text and brings the band back.The Arabic (Uthmani script) and the Sahih International translation were taken from the AlQuran Cloud API and are bundled, so it needs no network.
| Option | Default | |
|---|---|---|
showArabic | false | Also show the Arabic in the band. Off by default because many terminals draw right-to-left text poorly; /ayah always includes it. |
? for shortcuts 🕌 Asr 14:40 · in 0:40:00 · 📅 12 Rabi' al-Akhir 1448 ← hint line, after the prayer countdown
? for shortcuts 🕌 Isha 18:45 · in 0:50:00 · 🌙 13 Rabi' al-Akhir 1448 · White day ← after Maghrib
🌙 Tonight begins 15 Sha'ban 1448, Nisf Sha'ban. ← toast, the evening before
/hijri prints today's date and what is coming:
27 Rabi' al-Akhir 1448 AH (Umm al-Qura)
Thu 8 Oct 2026
Coming up (each begins the evening before):
Sat 23 Jan 2027 15 Sha'ban 1448 Nisf Sha'ban · in 107 days
Mon 8 Feb 2027 1 Ramadan 1448 1st of Ramadan · in 123 days
Sun 28 Feb 2027 21 Ramadan 1448 Last ten nights · in 143 days
Tue 9 Mar 2027 1 Shawwal 1448 Eid al-Fitr · in 152 days
...
/hijri 2027-03-10 converts a Gregorian date.
The dates are calculated, not sighted. Countries that begin the month by moon sighting (Indonesia, Malaysia, Pakistan and others) can be a day off Umm al-Qura; set adjustDays to match your country's announcement, especially around Ramadan and the Eids.
| Option | Default | |
|---|---|---|
calendar | Umm al-Qura | Umm al-Qura (Saudi Arabia's) or Tabular (arithmetical). |
adjustDays | 0 | Days to add or subtract, e.g. -1 or 1, to follow local sighting. |
announceDays | true | The toasts for the days above. |
🌙 Tomorrow is Thursday and a white day: fasting is sunnah. /fasting on to fast it, with suhur and iftar reminders.
🍽 Suhur: Fajr is at 04:10, in 45m. ← toast, on a day you fast
? for shortcuts 🕌 Asr 14:40 · in 1:10:00 · 📅 13 Rabi' al-Akhir 1448 · White day · 🍽 Fasting · iftar 17:35
🍽 It's Maghrib: time to break your fast. Dhahaba al-zama'u ... ← toast, at Maghrib
/fasting on says you'll fast (outside Ramadan; in Ramadan, Ramadan reminds you every day): today until Maghrib, tomorrow after it (or /fasting on tomorrow, /fasting on 2026-10-15). On that day you get a suhur toast before Fajr, the iftar time on the hint line and a toast at Maghrib with the iftar dua. /fasting off cancels it./fasting lists the sunnah fasts in the next two weeks, with a ✓ on the days you said you'd fast.The Hijri dates come from Hijri date and the times from Prayer times, so it follows their settings (adjustDays included).
| Option | Default | |
|---|---|---|
mondayThursday | true | Remind you before every Monday and Thursday. |
whiteDays | true | Remind you before the white days. |
suhurMinutes | 45 | How long before Fajr the suhur reminder comes, here and in Ramadan; 0 turns it off. |
? for shortcuts 🕌 Fajr 04:10 · in 0:40:00 · 📅 1 Ramadan 1448 · 🍽 Imsak 04:00 · in 0:30:00 ← before Fajr
? for shortcuts 🕌 Asr 14:40 · in 1:10:00 · 📅 1 Ramadan 1448 · 🍽 Iftar 17:35 · in 4:05:00 ← the day
🍽 Suhur, day 1 of Ramadan: imsak at 04:00, Fajr at 04:10. ← toast, 45 minutes before Fajr
🍽 Imsak (04:00): finish your suhur, Fajr is at 04:10.
🍽 It's Maghrib: time to break your fast. Dhahaba al-zama'u ... ← toast, at Maghrib
🌙 Isha: Tarawih tonight, night 21 of Ramadan. ✨ An odd night of the last ten: seek Laylat al-Qadr. ...
It does nothing outside Ramadan. In it, every day, with nothing to turn on:
✨ Night 23 · seek Laylat al-Qadr after Maghrib./ramadan prints today's imsak, Fajr, iftar and Isha and which night it is tonight; before Ramadan, how many days are left (from 30 days out).The days come from Hijri date, so its adjustDays decides when Ramadan begins and ends for you; the start of Ramadan, the last ten nights and Eid are announced by Hijri date itself.
| Option | Default | |
|---|---|---|
suhurMinutes | 45 | The same option as sunnah fasting's. |
imsakMinutes | 10 | When imsak is, before Fajr; 0 turns the imsak reminder off. |
tarawih | true | The toast at Isha. |
📿 5-hour limit reached · back in 1h 42m · /tasbih
/tasbih)./tasbih opens the pane any time; /tasbih demo shows what a used-up 5-hour limit looks like, and /tasbih close ends it.The limits are read from what Claude Code reports, so this works on a Claude subscription (Pro, Max); with an API key there are no such limits and the tasbih stays quiet.
| Option | Default | |
|---|---|---|
showArabic | false | Also show the Arabic in the pane (one option for adhkar, the daily ayah and the tasbih). |
The plugin is built on function hooks, a Claude Code plugin API in early access. You need Claude Code 2.1.288 or newer (claude --version).
This repository is also a plugin marketplace. Inside Claude Code:
/plugin install muslim-mods --marketplace ashafizullah/claude-code-muslim-mods
Or from the terminal:
claude plugin marketplace add ashafizullah/claude-code-muslim-mods
claude plugin install muslim-mods@muslim-mods
Installed this way, the plugin is muslim-mods@muslim-mods: use that name with /plugin configure, and update with claude plugin marketplace update muslim-mods then claude plugin update muslim-mods@muslim-mods. Uninstall with claude plugin uninstall muslim-mods@muslim-mods.
The steps below load the plugin from a clone instead, which a git pull updates.
git clone https://github.com/ashafizullah/claude-code-muslim-mods.git ~/claude-code-muslim-mods
Every session (recommended). Add the muslim-mods folder to CLAUDE_CODE_PLUGIN_DIRS in the env block of your user settings, ~/.claude/settings.json:
{
"env": {
"CLAUDE_CODE_PLUGIN_DIRS": "~/claude-code-muslim-mods/muslim-mods"
}
}
: on macOS and Linux, ; on Windows. Absolute paths and ~ both work..claude/settings.json is ignored.env block rather than replacing the file.This also works where no command-line flag can be passed, such as sessions started by the Claude desktop app or the Agent SDK.
Or from your shell. The same variable set in the environment works too, for example in ~/.zshrc or ~/.bashrc:
export CLAUDE_CODE_PLUGIN_DIRS="$HOME/claude-code-muslim-mods/muslim-mods"
One session only:
claude --plugin-dir ~/claude-code-muslim-mods/muslim-mods
Coming from the separate mods (before 1.0)? Replace their paths (.../prayer-times:.../adhkar:...) with the one muslim-mods path. Options set for the old mods don't carry over, so set them again under muslim-mods in /config. They keep their names, except Jumu'ah's prayerReminderMinutes, now jumuahReminderMinutes; showArabic and suhurMinutes are now one option each for every feature that used them, and Hijri date's hintLine is the Hijri date switch itself.
claude plugin list
It is listed under Session-only plugins as muslim-mods@inline, ✔ loaded. In a session, /muslim opens the dashboard and the prayer countdown appears under the prompt.
Inside Claude Code, open /config (the fields are listed under muslim-mods) or run /plugin configure muslim-mods@inline.
From the terminal:
claude plugin configure muslim-mods@inline # show the options and which are set
echo '{"city": "Istanbul"}' | claude plugin configure muslim-mods@inline --values-stdin
Values are saved under pluginConfigs in ~/.claude/settings.json, and changing one reloads the plugin.
What changed in each version is in CHANGELOG.md.
git -C ~/claude-code-muslim-mods pull
An interactive session watches the folder and reloads the plugin when its files change, so a pull takes effect without a restart.
CLAUDE_CODE_PLUGIN_DIRS, the variable is in a project settings file instead of ~/.claude/settings.json, or Claude Code wasn't restarted after the change.claude plugin list: check the path, then run claude plugin validate ~/claude-code-muslim-mods/muslim-mods./muslim. If it is and still misbehaves, start Claude Code with claude --debug. Every hook that failed and every module that didn't load is logged with the reason.hooks/register.tsx 204 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, PluginOptions, Register } from 'claude-code'
3
4import { adhkar } from './adhkar'
5import { dailyAyah } from './daily-ayah'
6import { hijriDate } from './hijri-date'
7import { jumuah } from './jumuah'
8import { prayer } from './prayer-times'
9import { ramadan } from './ramadan'
10import { sunnahFasting } from './sunnah-fasting'
11import { tasbih } from './tasbih'
12
13const PANE = 'muslim'
14const PANE_COLUMNS = 84
15
16// Each feature's part of the hint line under the prompt, as it publishes it.
17const prayerLine = atom({ plugin: 'muslim-mods', key: 'prayerLine' } as const, null)
18const hijriLine = atom({ plugin: 'muslim-mods', key: 'hijriLine' } as const, null)
19const adhkarLine = atom({ plugin: 'muslim-mods', key: 'adhkarLine' } as const, null)
20const fastingLine = atom({ plugin: 'muslim-mods', key: 'fastingLine' } as const, null)
21const ramadanLine = atom({ plugin: 'muslim-mods', key: 'ramadanLine' } as const, null)
22const jumuahLine = atom({ plugin: 'muslim-mods', key: 'jumuahLine' } as const, null)
23const tasbihLine = atom({ plugin: 'muslim-mods', key: 'tasbihLine' } as const, null)
24// What prayer times and the Hijri date publish for the others; state outlives the reload a switch makes.
25const prayerToday = atom({ plugin: 'muslim-mods', key: 'prayerToday' } as const, null)
26const hijriToday = atom({ plugin: 'muslim-mods', key: 'hijriToday' } as const, null)
27const hijriAhead = atom({ plugin: 'muslim-mods', key: 'hijriAhead' } as const, [])
28
29export type Entry = {
30 /** Its on/off option in the manifest, `muslim-mods.<id>`. */
31 id: string
32 /** What /muslim on|off takes besides the id. */
33 alias: string
34 title: string
35 about: string
36 /** The features it reads from; without them it works only in part. */
37 uses?: string[]
38}
39
40/** In the order their parts appear on the hint line. */
41export const FEATURES: Entry[] = [
42 { id: 'prayerTimes', alias: 'prayer', title: 'Prayer times', about: 'Countdown to the next prayer; /prayer-times' },
43 {
44 id: 'hijriDate',
45 alias: 'hijri',
46 title: 'Hijri date',
47 about: 'The Hijri date and the days that matter; /hijri',
48 uses: ['prayerTimes'],
49 },
50 { id: 'adhkar', alias: 'adhkar', title: 'Adhkar', about: 'Morning and evening adhkar; /adhkar', uses: ['prayerTimes'] },
51 {
52 id: 'sunnahFasting',
53 alias: 'fasting',
54 title: 'Sunnah fasting',
55 about: 'Monday, Thursday, the white days; /fasting',
56 uses: ['prayerTimes', 'hijriDate'],
57 },
58 {
59 id: 'ramadan',
60 alias: 'ramadan',
61 title: 'Ramadan',
62 about: 'Imsak and iftar countdown in Ramadan; /ramadan',
63 uses: ['prayerTimes', 'hijriDate'],
64 },
65 { id: 'jumuah', alias: 'jumuah', title: "Jumu'ah", about: 'Al-Kahf, salawat and the Friday prayer; /jumuah', uses: ['prayerTimes'] },
66 { id: 'dailyAyah', alias: 'ayah', title: 'Daily ayah', about: "A verse of the Qur'an above the prompt; /ayah" },
67 { id: 'tasbih', alias: 'tasbih', title: 'Tasbih', about: 'Dhikr when the usage limit runs out; /tasbih' },
68]
69
70export const isOn = (options: PluginOptions, id: string) => options[id] !== false
71
72/** The feature `name` names: its id, its alias or its title, in any case. */
73export function find(name: string) {
74 const n = name.toLowerCase().replace(/[^a-z]/g, '')
75 return FEATURES.find(f => f.id.toLowerCase() === n || f.alias === n || f.title.toLowerCase().replace(/[^a-z]/g, '') === n)
76}
77
78/** Each feature's part of the hint line, by id, while it is on. */
79async function lines($: EngineInterface, options: PluginOptions): Promise<Record<string, string | null>> {
80 const on = (id: string, text: string | null) => (isOn(options, id) ? text : null)
81 return {
82 prayerTimes: on('prayerTimes', await read($, prayerLine)),
83 hijriDate: on('hijriDate', await read($, hijriLine)),
84 adhkar: on('adhkar', await read($, adhkarLine)),
85 sunnahFasting: on('sunnahFasting', await read($, fastingLine)),
86 ramadan: on('ramadan', await read($, ramadanLine)),
87 jumuah: on('jumuah', await read($, jumuahLine)),
88 tasbih: on('tasbih', await read($, tasbihLine)),
89 }
90}
91
92/** Switches a feature on or off; the change reloads the plugin with only the features that are on. */
93async function toggle($: EngineInterface, f: Entry, value: boolean) {
94 const set = await $.config.set({ key: `muslim-mods.${f.id}`, value })
95 return set.deny ? `Could not switch ${f.title} ${value ? 'on' : 'off'}: ${set.deny}` : `${f.title} is ${value ? 'on' : 'off'}.`
96}
97
98/** The same from the dashboard, where only a refusal needs saying. */
99async function toggleFromPane($: EngineInterface, f: Entry, value: boolean) {
100 const text = await toggle($, f, value)
101 if (text.startsWith('Could not')) $.ui.toast(text, { timeoutMs: 10e3 })
102}
103
104export const register: Register = (on, options) => {
105 // First, so what a switched-off feature left is cleared before the others start reading it.
106 on('session.start', async ($, e, next) => {
107 await $.command.register({
108 name: 'muslim',
109 description: 'The Muslim mods dashboard: switch each one on or off. /muslim on|off <feature>',
110 })
111 // A feature switched off leaves what it published; the others shouldn't keep reading it.
112 // Never at the cost of the features' own start, beneath this hook.
113 try {
114 if (!isOn(options, 'prayerTimes') && (await read($, prayerToday))) await update($, prayerToday, () => null)
115 if (!isOn(options, 'hijriDate') && (await read($, hijriAhead)).length) {
116 await update($, hijriToday, () => null)
117 await update($, hijriAhead, () => [])
118 }
119 } catch {
120 // Left as it was: the features fall back as they would without it.
121 }
122 return next(e)
123 })
124
125 // Only the features that are on are hooked; switching one reloads the plugin.
126 if (isOn(options, 'prayerTimes')) prayer(on, options)
127 if (isOn(options, 'hijriDate')) hijriDate(on, options)
128 if (isOn(options, 'adhkar')) adhkar(on, options)
129 if (isOn(options, 'sunnahFasting')) sunnahFasting(on, options)
130 if (isOn(options, 'ramadan')) ramadan(on, options)
131 if (isOn(options, 'jumuah')) jumuah(on, options)
132 if (isOn(options, 'dailyAyah')) dailyAyah(on, options)
133 if (isOn(options, 'tasbih')) tasbih(on, options)
134
135 // One line under the prompt, each feature's part in a fixed order, no labels.
136 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
137 const live = await lines($, options)
138 const parts = FEATURES.map(f => live[f.id]).filter(Boolean)
139 if (!parts.length) return next(e)
140 return next({ ...e, props: { ...e.props, tail: [e.props.tail, ...parts].filter(Boolean).join(' · ') } })
141 })
142
143 on('command.run', { command: 'muslim' }, async ($, e) => {
144 const [verb = '', ...rest] = e.args.trim().split(/\s+/)
145 if (verb === 'on' || verb === 'off') {
146 const f = find(rest.join(''))
147 if (!f) return { text: `Which one? ${FEATURES.map(x => x.alias).join(', ')}` }
148 return { text: await toggle($, f, verb === 'on') }
149 }
150 if (verb === 'list') {
151 return { text: FEATURES.map(f => `${isOn(options, f.id) ? '●' : '○'} ${f.title.padEnd(15)} ${f.alias}`).join('\n') }
152 }
153 if (verb !== '') return { text: 'Usage: /muslim, /muslim list, or /muslim on|off <feature>' }
154 await $.ui.open({
155 id: PANE,
156 title: 'Muslim mods',
157 focus: true,
158 closeOnEscape: true,
159 // Two lines a feature, a note cut to one, and the footer.
160 rows: FEATURES.length * 2 + 3,
161 columns: PANE_COLUMNS,
162 })
163 return { text: 'Muslim mods opened. Keys 1–8 switch each one on or off; x closes.' }
164 })
165
166 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
167 const { Box, Button, Text } = $.ui.resolve(e)
168 const live = await lines($, options)
169 const title = (id: string) => FEATURES.find(f => f.id === id)?.title ?? id
170 return (
171 <Box flexDirection="column" width={e.props.bodyColumns}>
172 {FEATURES.map((f, i) => {
173 const isOnNow = isOn(options, f.id)
174 const missing = isOnNow ? (f.uses ?? []).filter(id => !isOn(options, id)).map(title) : []
175 const note = missing.length ? `works in part without ${missing.join(' and ')}` : (live[f.id] ?? f.about)
176 return (
177 <Box key={`row-${f.id}`} flexDirection="row" gap={2}>
178 <Button
179 key={`toggle-${f.id}`}
180 hotkey={String(i + 1)}
181 variant={isOnNow ? 'primary' : undefined}
182 label={isOnNow ? '● On ' : '○ Off'}
183 onPress={() => toggleFromPane($, f, !isOnNow)}
184 />
185 <Box flexDirection="column">
186 <Text bold={isOnNow} dimColor={!isOnNow}>
187 {i + 1} {f.title}
188 </Text>
189 <Text dimColor wrap="truncate-end">
190 {note}
191 </Text>
192 </Box>
193 </Box>
194 )
195 })}
196 <Box flexDirection="row" gap={2}>
197 <Text dimColor>Settings: /config, under muslim-mods</Text>
198 <Button key="close" hotkey="x" role="dismiss" label="Close" onPress={() => $.ui.close({ id: PANE })} />
199 </Box>
200 </Box>
201 )
202 })
203}
204hooks/adhkar/index.tsx 257 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface } from 'claude-code'
3
4import type { AdhkarMode, PrayerTimesDay } from '../../types'
5import { ADHKAR } from './adhkar'
6import type { Dhikr } from './adhkar'
7import type { Feature } from '../feature'
8
9const PANE = 'adhkar'
10const PANE_COLUMNS = 90
11const TICK_MS = 30e3
12const DONE_KEY = 'adhkar.done'
13const PROGRESS_KEY = 'adhkar.progress'
14
15const date = atom({ plugin: 'muslim-mods', key: 'adhkarDate' } as const, '')
16const mode = atom({ plugin: 'muslim-mods', key: 'adhkarMode' } as const, 'morning')
17const index = atom({ plugin: 'muslim-mods', key: 'adhkarIndex' } as const, 0)
18const counts = atom({ plugin: 'muslim-mods', key: 'adhkarCounts' } as const, {})
19const done = atom({ plugin: 'muslim-mods', key: 'adhkarDone' } as const, [])
20const line = atom({ plugin: 'muslim-mods', key: 'adhkarLine' } as const, null)
21const prayerDay = { plugin: 'muslim-mods', key: 'prayerToday' } as const
22
23const TITLE: Record<AdhkarMode, string> = { morning: 'Morning adhkar', evening: 'Evening adhkar' }
24
25export const listFor = (m: AdhkarMode) => ADHKAR.filter(d => d.when === 'both' || d.when === m)
26
27/** The wording for this time of day. */
28export const wordingFor = (d: Dhikr, m: AdhkarMode) =>
29 m === 'evening' && d.evening ? d.evening : { transliteration: d.transliteration, english: d.english }
30
31const zones = new Map<string, Intl.DateTimeFormat>()
32
33/** The date and hour at `now` in `timeZone`, this machine's when not given. */
34function localNow(now: number, timeZone?: string) {
35 let zone = zones.get(timeZone ?? '')
36 if (!zone) {
37 zone = new Intl.DateTimeFormat('en-US', { timeZone, timeZoneName: 'longOffset' })
38 zones.set(timeZone ?? '', zone)
39 }
40 const name = zone.formatToParts(now).find(p => p.type === 'timeZoneName')?.value ?? 'GMT'
41 const m = /GMT([+-])(\d{2}):(\d{2})/.exec(name)
42 const offset = m ? (m[1] === '-' ? -1 : 1) * (Number(m[2]) + Number(m[3]) / 60) : 0
43 const local = new Date(now + offset * 3600e3)
44 return { date: local.toISOString().slice(0, 10), hours: local.getUTCHours() + local.getUTCMinutes() / 60, offset }
45}
46
47export type Window = { mode: AdhkarMode; opens: number; closes: number }
48
49/**
50 * Morning adhkar run from Fajr until Dhuhr, evening ones from Asr until Isha, read from
51 * prayer-times; without it, 04:00 to 11:00 and 15:00 to 19:00 on this machine's clock.
52 */
53export function windowsFor(now: number, day: PrayerTimesDay | null | undefined): Window[] {
54 const { date: today, offset } = localNow(now, day?.timeZone)
55 const at = (name: string) => day?.slots.find(s => s.name === name)?.at
56 const [fajr, dhuhr, asr, isha] = ['Fajr', 'Dhuhr', 'Asr', 'Isha'].map(at)
57 if (day?.date === today && fajr && dhuhr && asr && isha) {
58 return [
59 { mode: 'morning', opens: fajr, closes: dhuhr },
60 { mode: 'evening', opens: asr, closes: isha },
61 ]
62 }
63 const midnight = Date.parse(`${today}T00:00:00Z`) - offset * 3600e3
64 const h = (hours: number) => midnight + hours * 3600e3
65 return [
66 { mode: 'morning', opens: h(4), closes: h(11) },
67 { mode: 'evening', opens: h(15), closes: h(19) },
68 ]
69}
70
71/** Rows the pane needs for the longest dhikr of the list at this width, so the whole of each fits. */
72export function rowsFor(m: AdhkarMode, columns: number, showArabic: boolean) {
73 const width = Math.max(20, columns - 4)
74 const lines = (text: string) => text.split('\n').reduce((n, part) => n + Math.max(1, Math.ceil(part.length / width)), 0)
75 const tallest = Math.max(
76 ...listFor(m).map(d => {
77 const words = wordingFor(d, m)
78 const parts = [words.transliteration, words.english, d.note ?? '', showArabic ? d.arabic : ''].filter(Boolean)
79 return parts.reduce((n, part) => n + lines(part) + 1, 0)
80 }),
81 )
82 // The title and the buttons, each with the gap below it.
83 return tallest + 4
84}
85
86/** `waitsForPrayer`: prayer times is on, so the day's date waits for its time zone. */
87type Watch = { delayMs: number; lastTick: number; waitsForPrayer: boolean }
88
89/** Where the reading got to, kept across sessions so a closed Claude Code picks up at the same dhikr. */
90type Progress = { date: string; mode: AdhkarMode; index: number; counts: Record<string, number> }
91
92async function saveProgress($: EngineInterface) {
93 const progress: Progress = {
94 date: await read($, date),
95 mode: await read($, mode),
96 index: await read($, index),
97 counts: await read($, counts),
98 }
99 await $.store.set(PROGRESS_KEY, progress)
100}
101
102async function loadProgress($: EngineInterface) {
103 const saved = (await $.store.get(PROGRESS_KEY)) as Progress | undefined
104 if (!saved) return
105 await update($, date, () => saved.date)
106 await update($, mode, () => saved.mode)
107 await update($, index, () => saved.index)
108 await update($, counts, () => saved.counts)
109}
110
111async function tick($: EngineInterface, w: Watch) {
112 const now = await $.clock.now()
113 const { value: day } = await $.state.get(prayerDay)
114 // The day turns over at midnight where the prayer times are, so it matches their windows. Before prayer
115 // times has published (a fresh start), this machine's date may not be that one: keep the saved progress.
116 const today = localNow(now, day?.timeZone).date
117 if ((await read($, date)) !== today && (day || !w.waitsForPrayer)) {
118 await update($, date, () => today)
119 await update($, counts, () => ({}))
120 await update($, index, () => 0)
121 await saveProgress($)
122 }
123
124 const finished = await read($, done)
125 let open: AdhkarMode | undefined
126 for (const win of windowsFor(now, day)) {
127 const isDone = finished.includes(`${today}:${win.mode}`)
128 const remindAt = win.opens + w.delayMs
129 if (!isDone && w.lastTick < remindAt && remindAt <= now && now < win.closes) {
130 $.ui.toast(`🤲 Time for the ${win.mode} adhkar. Run /adhkar to read them.`, { timeoutMs: 15e3 })
131 }
132 if (!isDone && win.opens <= now && now < win.closes) open = win.mode
133 }
134 const text = open ? `🤲 ${TITLE[open]} · /adhkar` : null
135 if ((await read($, line)) !== text) await update($, line, () => text)
136 w.lastTick = now
137}
138
139/** The mode a bare /adhkar opens: the open window's, else by the hour. */
140async function currentMode($: EngineInterface): Promise<AdhkarMode> {
141 const now = await $.clock.now()
142 const { value: day } = await $.state.get(prayerDay)
143 const open = windowsFor(now, day).find(w => w.opens <= now && now < w.closes)
144 return open?.mode ?? (localNow(now, day?.timeZone).hours < 12 ? 'morning' : 'evening')
145}
146
147async function finish($: EngineInterface) {
148 const m = await read($, mode)
149 const key = `${await read($, date)}:${m}`
150 if ((await read($, line))?.includes(TITLE[m])) await update($, line, () => null)
151 const list = await update($, done, d => (d.includes(key) ? d : [...d, key].slice(-30)))
152 await $.store.set(DONE_KEY, list)
153 await $.ui.close({ id: PANE })
154 $.ui.toast('🤲 Done. May Allah accept it from you.', { timeoutMs: 8e3 })
155}
156
157async function step($: EngineInterface, by: number) {
158 const size = listFor(await read($, mode)).length
159 await update($, index, i => Math.min(size - 1, Math.max(0, i + by)))
160 await saveProgress($)
161}
162
163/** One more repetition; a dhikr said its full number of times moves on to the next. */
164async function count($: EngineInterface) {
165 const m = await read($, mode)
166 const list = listFor(m)
167 const i = await read($, index)
168 const d = list[i]
169 if (!d) return
170 const key = `${await read($, date)}:${m}:${d.id}`
171 const said = (await update($, counts, c => ({ ...c, [key]: Math.min(d.repeat, (c[key] ?? 0) + 1) })))[key] ?? 0
172 if (said >= d.repeat && i < list.length - 1) await update($, index, () => i + 1)
173 await saveProgress($)
174 if (said >= d.repeat && i === list.length - 1) await finish($)
175}
176
177export const adhkar: Feature = (on, options) => {
178 const showArabic = options.showArabic === true
179 const watch: Watch = { delayMs: Number(options.reminderDelayMinutes ?? 15) * 60e3, lastTick: 0, waitsForPrayer: options.prayerTimes !== false }
180
181 on('command.run', { command: 'adhkar' }, async ($, e) => {
182 const asked = e.args.trim().toLowerCase()
183 if (asked === 'close') {
184 await $.ui.close({ id: PANE })
185 return { text: 'Adhkar closed.' }
186 }
187 const chosen: AdhkarMode =
188 asked === 'morning' || asked === 'evening' ? asked : await currentMode($)
189 if ((await read($, mode)) !== chosen) {
190 await update($, mode, () => chosen)
191 await update($, index, () => 0)
192 await saveProgress($)
193 }
194 await tick($, watch)
195 await $.ui.open({
196 id: PANE,
197 title: TITLE[chosen],
198 focus: true,
199 closeOnEscape: true,
200 // The pane is never wider than PANE_COLUMNS, so the text wraps at that width at most.
201 rows: rowsFor(chosen, Math.min(PANE_COLUMNS, e.presentation.columns), showArabic),
202 columns: PANE_COLUMNS,
203 })
204
205 return { text: `${TITLE[chosen]} opened. Keys: c count, n next, p previous, f finish, x close.` }
206 })
207
208 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
209 const { Box, Button, Text } = $.ui.resolve(e)
210 const m = await read($, mode)
211 const list = listFor(m)
212 const i = Math.min(await read($, index), list.length - 1)
213 const d = list[i]
214 if (!d) return <Text dimColor>Nothing to read.</Text>
215 const said = (await read($, counts))[`${await read($, date)}:${m}:${d.id}`] ?? 0
216 const words = wordingFor(d, m)
217 const isDone = (await read($, done)).includes(`${await read($, date)}:${m}`)
218
219 return (
220 <Box flexDirection="column" width={e.props.bodyColumns} gap={1}>
221 <Text dimColor>
222 {TITLE[m]} · {i + 1} of {list.length}
223 {isDone ? ' · finished today' : ''}
224 </Text>
225 <Box flexDirection="row" gap={2}>
226 <Button key="count" variant="primary" hotkey="c" label={`${said} / ${d.repeat}`} onPress={() => count($)} />
227 <Button key="prev" hotkey="p" label="Previous" onPress={() => step($, -1)} />
228 <Button key="next" hotkey="n" label="Next" onPress={() => step($, 1)} />
229 <Button key="finish" hotkey="f" label="Finish" onPress={() => finish($)} />
230 <Button key="close" hotkey="x" role="dismiss" label="Close" onPress={() => $.ui.close({ id: PANE })} />
231 </Box>
232 {showArabic && <Text>{d.arabic}</Text>}
233 <Text italic>{words.transliteration}</Text>
234 <Text>{words.english}</Text>
235 {d.note && <Text dimColor>{d.note}</Text>}
236 </Box>
237 )
238 })
239
240
241 on('session.start', { isInteractive: true }, async ($, e, next) => {
242 await $.command.register({
243 name: 'adhkar',
244 description: 'Read the morning or evening adhkar: /adhkar [morning|evening|close]',
245 })
246 // Versions before 0.4 pinned a status line, which outlives a reload; this one lives on the hint line.
247 $.ui.status(undefined)
248 const saved = (await $.store.get(DONE_KEY)) as string[] | undefined
249 if (saved) await update($, done, () => saved)
250 await loadProgress($)
251 watch.lastTick = await $.clock.now()
252 await tick($, watch)
253 $.clock.every(TICK_MS, () => void tick($, watch))
254 return next(e)
255 })
256}
257hooks/daily-ayah/index.tsx 80 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface } from 'claude-code'
3
4import { VERSES } from './verses'
5import type { Verse } from './verses'
6import type { Feature } from '../feature'
7
8const seed = atom({ plugin: 'muslim-mods', key: 'ayahSeed' } as const, -1)
9const skip = atom({ plugin: 'muslim-mods', key: 'ayahSkip' } as const, 0)
10const isHidden = atom({ plugin: 'muslim-mods', key: 'ayahHidden' } as const, false)
11
12/** The session's verse; each ↻ steps by a stride coprime to the list's length, so the next is unrelated. */
13export function verseFor(seeded: number, skipped: number): Verse {
14 const index = (seeded + skipped * 37) % VERSES.length
15 return VERSES[index] ?? VERSES[0]!
16}
17
18const cite = (v: Verse) => `${v.surah} (${v.meaning}) ${v.ref}`
19
20/** A fresh verse: when the session starts and on /clear, which ends it without a new session.start. */
21async function newVerse($: EngineInterface) {
22 const now = await $.clock.now()
23 await update($, seed, () => now % VERSES.length)
24 await update($, skip, () => 0)
25}
26
27export const dailyAyah: Feature = (on, options) => {
28 const showArabic = options.showArabic === true
29
30 on('command.run', { command: 'ayah' }, async $ => {
31 const v = verseFor(await read($, seed), await read($, skip))
32 await update($, isHidden, () => false)
33
34 return { text: `${v.arabic}\n\n"${v.english}"\n— ${cite(v)}, Sahih International` }
35 })
36
37 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
38 if (e.props.hasSurvey || (await read($, isHidden))) {
39 return next(e)
40 }
41 const seeded = await read($, seed)
42 if (seeded < 0) {
43 return next(e)
44 }
45
46 const v = verseFor(seeded, await read($, skip))
47 const { Box, Button, Text } = $.ui.resolve(e)
48
49 return (
50 <Box flexDirection="column" width={e.props.bodyColumns}>
51 {showArabic && <Text>{v.arabic}</Text>}
52 <Text italic>📖 "{v.english}"</Text>
53 <Box flexDirection="row">
54 <Text dimColor>— {cite(v)} </Text>
55 <Button key="another" plain label="↻" onPress={() => update($, skip, n => n + 1)} />
56 <Text> </Text>
57 <Button key="hide" plain role="dismiss" label="×" onPress={() => update($, isHidden, () => true)} />
58 </Box>
59 </Box>
60 )
61 })
62
63
64 on('session.start', { isInteractive: true }, async ($, e, next) => {
65 await $.command.register({
66 name: 'ayah',
67 description: "Show a verse of the Qur'an",
68 })
69 // A reload (switching a feature, changing an option) starts the session's hooks again; keep its verse.
70 if ((await read($, seed)) < 0) await newVerse($)
71 return next(e)
72 })
73
74 on('session.end', { reason: 'clear' }, async ($, e, next) => {
75 await newVerse($)
76 await update($, isHidden, () => false)
77 return next(e)
78 })
79}
80hooks/hijri-date/index.ts 136 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface } from 'claude-code'
3
4import type { HijriDate, HijriDay, PrayerTimesDay } from '../../types'
5import {
6 CALENDARS,
7 MONTHS,
8 addDays,
9 announce,
10 daysBetween,
11 format,
12 gregorianLabel,
13 hijriOf,
14 isAnnounced,
15 localDate,
16 occasionOf,
17 shortName,
18 upcomingOccasions,
19} from './hijri'
20import type { Calendar } from './hijri'
21import type { Feature } from '../feature'
22
23const TICK_MS = 30e3
24// How many days ahead are published, for sunnah fasting and Ramadan.
25const AHEAD_DAYS = 30
26// The toasts already given, kept across sessions so a new session doesn't repeat them.
27const ANNOUNCED_KEY = 'hijri.announced'
28
29const today = atom({ plugin: 'muslim-mods', key: 'hijriToday' } as const, null)
30const line = atom({ plugin: 'muslim-mods', key: 'hijriLine' } as const, null)
31const ahead = atom({ plugin: 'muslim-mods', key: 'hijriAhead' } as const, [])
32const prayerDay = { plugin: 'muslim-mods', key: 'prayerToday' } as const
33
34type Settings = { calendar: Calendar; adjustDays: number; announceDays: boolean }
35
36/**
37 * The Hijri date now. It turns over at Maghrib, read from prayer-times; without it
38 * (no place found yet), at midnight on this machine's clock.
39 */
40export function hijriNow(now: number, day: PrayerTimesDay | null | undefined, s: Settings): HijriDay {
41 const gregorian = localDate(now, day?.timeZone)
42 const maghrib = day?.date === gregorian ? day.slots.find(slot => slot.name === 'Maghrib')?.at : undefined
43 const isEve = maghrib !== undefined && now >= maghrib
44 const h = hijriOf(isEve ? addDays(gregorian, 1) : gregorian, s.calendar, s.adjustDays)
45 return { ...h, monthName: MONTHS[h.month - 1] ?? '', gregorian, isEve }
46}
47
48async function tick($: EngineInterface, s: Settings) {
49 const now = await $.clock.now()
50 const { value: day } = await $.state.get(prayerDay)
51 const h = hijriNow(now, day, s)
52 const current = await read($, today)
53 if (!current || current.day !== h.day || current.month !== h.month || current.year !== h.year || current.isEve !== h.isEve) {
54 await update($, today, () => h)
55 }
56
57 // Rebuilt at midnight, and when calendar or adjustDays changed (state outlives the reload that follows).
58 const first = (await read($, ahead))[0]
59 const expected = hijriOf(h.gregorian, s.calendar, s.adjustDays)
60 if (first?.gregorian !== h.gregorian || first.day !== expected.day || first.month !== expected.month || first.year !== expected.year) {
61 const days: HijriDate[] = Array.from({ length: AHEAD_DAYS }, (_, i) => {
62 const gregorian = addDays(h.gregorian, i)
63 return { gregorian, ...hijriOf(gregorian, s.calendar, s.adjustDays) }
64 })
65 await update($, ahead, () => days)
66 }
67
68 const occasion = occasionOf(h)
69 {
70 const text = `${h.isEve ? '🌙' : '📅'} ${format(h)}${occasion ? ` · ${shortName(occasion)}` : ''}`
71 if ((await read($, line)) !== text) await update($, line, () => text)
72 }
73
74 if (s.announceDays && occasion && isAnnounced(occasion, h.isEve)) {
75 const key = `${h.year}-${h.month}-${h.day}:${h.isEve ? 'eve' : 'day'}`
76 const announced = ((await $.store.get(ANNOUNCED_KEY)) as string[] | undefined) ?? []
77 if (!announced.includes(key)) {
78 $.ui.toast(announce(h, occasion, h.isEve), { timeoutMs: 20e3 })
79 await $.store.set(ANNOUNCED_KEY, [...announced.slice(-20), key])
80 }
81 }
82}
83
84export const hijriDate: Feature = (on, options) => {
85 const calendar = String(options.calendar ?? 'Umm al-Qura')
86 const settings: Settings = {
87 calendar: (CALENDARS as readonly string[]).includes(calendar) ? (calendar as Calendar) : 'Umm al-Qura',
88 adjustDays: Math.trunc(Number(options.adjustDays ?? 0)) || 0,
89 announceDays: options.announceDays !== false,
90 }
91
92 on('command.run', { command: 'hijri' }, async ($, e) => {
93 const arg = e.args.trim()
94 if (arg !== '') {
95 if (!/^\d{4}-\d{2}-\d{2}$/.test(arg) || Number.isNaN(Date.parse(`${arg}T00:00:00Z`)) || addDays(arg, 0) !== arg) {
96 return { text: 'Give a Gregorian date as YYYY-MM-DD, e.g. /hijri 2027-03-10.' }
97 }
98 const h = hijriOf(arg, settings.calendar, settings.adjustDays)
99 const o = occasionOf(h)
100 return { text: `${gregorianLabel(arg)} is ${format(h)} AH${o ? `, ${o.name}` : ''} (from the evening before).` }
101 }
102
103 const now = await $.clock.now()
104 const { value: day } = await $.state.get(prayerDay)
105 const h = hijriNow(now, day, settings)
106 const o = occasionOf(h)
107 const adjust = settings.adjustDays ? ` ${settings.adjustDays > 0 ? '+' : ''}${settings.adjustDays} day${Math.abs(settings.adjustDays) === 1 ? '' : 's'}` : ''
108 const lines = [
109 `${format(h)} AH${o ? `, ${o.name}` : ''} (${settings.calendar}${adjust})`,
110 h.isEve
111 ? ` Since Maghrib; the daytime of this date is ${gregorianLabel(addDays(h.gregorian, 1))}.`
112 : ` ${gregorianLabel(h.gregorian)}`,
113 '',
114 'Coming up (each begins the evening before):',
115 ]
116 for (const next of upcomingOccasions(addDays(h.gregorian, h.isEve ? 2 : 1), settings.calendar, settings.adjustDays, 6)) {
117 const days = daysBetween(h.gregorian, next.gregorian)
118 lines.push(
119 ` ${gregorianLabel(next.gregorian).padEnd(16)} ${format(next.hijri).padEnd(24)} ${shortName(next.occasion)} · in ${days} day${days === 1 ? '' : 's'}`,
120 )
121 }
122 return { text: lines.join('\n') }
123 })
124
125
126 on('session.start', { isInteractive: true }, async ($, e, next) => {
127 await $.command.register({
128 name: 'hijri',
129 description: "Today's Hijri date and the days ahead, or convert a date: /hijri 2027-03-10",
130 })
131 await tick($, settings)
132 $.clock.every(TICK_MS, () => void tick($, settings))
133 return next(e)
134 })
135}
136hooks/jumuah/index.ts 186 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface } from 'claude-code'
3
4import type { PrayerTimesDay } from '../../types'
5import type { Feature } from '../feature'
6
7const TICK_MS = 10e3
8// A crossing older than this (the laptop slept through it) is not announced.
9const STALE_MS = 5 * 60e3
10// Without prayer times, Thursday's evening (the night of Jumu'ah) begins at this hour, and Friday's morning at this one.
11const EVENING_HOUR = 18
12const MORNING_HOUR = 5
13// The Fridays whose Al-Kahf was read, and the toasts given, as `<kind>:<Friday's date>`; kept across sessions.
14const DONE_KEY = 'jumuah.done'
15
16const NIGHT =
17 "🌙 The night of Jumu'ah has begun: send salawat on the Prophet often (Abu Dawud 1047), and read Al-Kahf before tomorrow's Maghrib."
18const MORNING =
19 "🕌 Jumu'ah Mubarak. Ghusl, clean clothes and perfume, and go early; read Al-Kahf (/jumuah read when done) and send salawat on the Prophet often."
20const LAST_HOUR = "🤲 Friday after Asr: the last hour of Jumu'ah, when dua is answered (Abu Dawud 1048)."
21
22const line = atom({ plugin: 'muslim-mods', key: 'jumuahLine' } as const, null)
23const prayerDay = { plugin: 'muslim-mods', key: 'prayerToday' } as const
24
25const DAY_MS = 864e5
26export const addDays = (ymd: string, n: number) =>
27 new Date(Date.parse(`${ymd}T00:00:00Z`) + n * DAY_MS).toISOString().slice(0, 10)
28const weekday = (ymd: string) => new Date(`${ymd}T12:00:00Z`).getUTCDay()
29const FRIDAY = 5
30const THURSDAY = 4
31
32const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
33/** A Friday as `Fri 9 Oct`. */
34const fridayLabel = (ymd: string) => {
35 const d = new Date(`${ymd}T12:00:00Z`)
36 return `Fri ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}`
37}
38
39const zones = new Map<string, Intl.DateTimeFormat>()
40function parts(now: number, timeZone?: string) {
41 let zone = zones.get(timeZone ?? '')
42 if (!zone) {
43 zone = new Intl.DateTimeFormat('en-CA', {
44 timeZone,
45 year: 'numeric',
46 month: '2-digit',
47 day: '2-digit',
48 hour: '2-digit',
49 minute: '2-digit',
50 hourCycle: 'h23',
51 })
52 zones.set(timeZone ?? '', zone)
53 }
54 const all = zone.formatToParts(now)
55 return (type: string) => all.find(p => p.type === type)?.value ?? ''
56}
57const localDate = (now: number, timeZone?: string) => {
58 const part = parts(now, timeZone)
59 return `${part('year')}-${part('month')}-${part('day')}`
60}
61const clock = (now: number, timeZone?: string) => {
62 const part = parts(now, timeZone)
63 return `${part('hour')}:${part('minute')}`
64}
65
66type Settings = { prayerMs: number; kahf: boolean }
67type Watch = { settings: Settings; lastTick: number }
68
69/**
70 * Where the week stands at `now`. Jumu'ah runs from Thursday's Maghrib to Friday's; `friday`
71 * is its date while it does, else undefined.
72 */
73async function context($: EngineInterface, now: number) {
74 const { value: day } = (await $.state.get(prayerDay)) as { value: PrayerTimesDay }
75 const timeZone = day?.timeZone
76 const today = localDate(now, timeZone)
77 const slot = (name: string) => (day?.date === today ? day.slots.find(s => s.name === name)?.at : undefined)
78 const fajr = slot('Fajr')
79 const dhuhr = slot('Dhuhr')
80 const asr = slot('Asr')
81 const maghrib = slot('Maghrib')
82 const isEvening = maghrib !== undefined ? now >= maghrib : Number(parts(now, timeZone)('hour')) >= EVENING_HOUR
83 const w = weekday(today)
84 const friday =
85 w === THURSDAY && isEvening ? addDays(today, 1)
86 : w === FRIDAY && !isEvening ? today
87 : undefined
88 const isFridayDay = w === FRIDAY && !isEvening
89 // Yesterday's times, for the moment after midnight (or a wake) before prayer times publishes today's.
90 const isStale = !!day && day.date !== today
91 // Without prayer times at all, the morning begins at this hour.
92 const isMorning = fajr !== undefined ? now >= fajr : !day && Number(parts(now, timeZone)('hour')) >= MORNING_HOUR
93 return { timeZone, today, fajr, dhuhr, asr, maghrib, friday, isFridayDay, isStale, isMorning }
94}
95
96const doneList = async ($: EngineInterface) => ((await $.store.get(DONE_KEY)) as string[] | undefined) ?? []
97
98async function markDone($: EngineInterface, key: string) {
99 const done = await doneList($)
100 if (!done.includes(key)) await $.store.set(DONE_KEY, [...done.slice(-20), key])
101}
102
103async function tick($: EngineInterface, w: Watch) {
104 const s = w.settings
105 const now = await $.clock.now()
106 const c = await context($, now)
107 const crossed = (at: number | undefined) => at !== undefined && w.lastTick < at && at <= now && now - at < STALE_MS
108 const done = await doneList($)
109
110 if (c.friday && !c.isStale) {
111 // Once per Friday, even across sessions: the night's toast, then the morning's from Fajr.
112 const kind = c.isFridayDay ? (c.isMorning ? 'morning' : undefined) : 'night'
113 if (kind && !done.includes(`${kind}:${c.friday}`)) {
114 $.ui.toast(kind === 'night' ? NIGHT : MORNING, { timeoutMs: 20e3 })
115 await markDone($, `${kind}:${c.friday}`)
116 }
117 }
118 if (c.isFridayDay && c.dhuhr !== undefined && s.prayerMs > 0 && crossed(c.dhuhr - s.prayerMs)) {
119 $.ui.toast(
120 `🕌 The Friday prayer is at Dhuhr, ${clock(c.dhuhr, c.timeZone)}, in ${Math.round(s.prayerMs / 60e3)}m: time to get ready and go early.`,
121 { timeoutMs: 20e3 },
122 )
123 }
124 if (c.isFridayDay && crossed(c.asr)) $.ui.toast(LAST_HOUR, { timeoutMs: 20e3 })
125
126 const text = s.kahf && c.friday && !done.includes(`kahf:${c.friday}`) ? '📖 Al-Kahf · /jumuah read' : null
127 if ((await read($, line)) !== text) await update($, line, () => text)
128 w.lastTick = now
129}
130
131export const jumuah: Feature = (on, options) => {
132 const watch: Watch = {
133 settings: {
134 prayerMs: Math.max(0, Number(options.jumuahReminderMinutes ?? 45)) * 60e3,
135 kahf: options.kahf !== false,
136 },
137 lastTick: 0,
138 }
139
140 on('command.run', { command: 'jumuah' }, async ($, e) => {
141 const now = await $.clock.now()
142 const c = await context($, now)
143 const arg = e.args.trim().toLowerCase()
144
145 if (arg === 'read') {
146 if (!c.friday) return { text: "Al-Kahf is read from Thursday's Maghrib until Friday's Maghrib." }
147 await markDone($, `kahf:${c.friday}`)
148 await tick($, watch)
149 return { text: 'Al-Kahf read: may it be a light for you until the next Friday.' }
150 }
151 if (arg !== '') return { text: 'Usage: /jumuah, or /jumuah read once you have read Al-Kahf.' }
152
153 if (!c.friday) {
154 const days = (FRIDAY - weekday(c.today) + 7) % 7 || 7
155 const next = addDays(c.today, days)
156 return {
157 text: `The next Jumu'ah is ${fridayLabel(next)}, in ${days} day${days === 1 ? '' : 's'}; it begins at Thursday's Maghrib.`,
158 }
159 }
160 const done = await doneList($)
161 const mark = (isDone: boolean) => (isDone ? '✓' : '·')
162 const dhuhr = c.isFridayDay && c.dhuhr !== undefined ? ` The Friday prayer is at Dhuhr, ${clock(c.dhuhr, c.timeZone)}.` : ''
163 return {
164 text: [
165 `Jumu'ah, ${fridayLabel(c.friday)}.${dhuhr}`,
166 ` ${mark(done.includes(`kahf:${c.friday}`))} Read Surah Al-Kahf (/jumuah read)`,
167 ' · Send salawat on the Prophet often',
168 ' · Ghusl, clean clothes and perfume, and go early to the prayer',
169 ' · Dua in the last hour, after Asr',
170 ].join('\n'),
171 }
172 })
173
174
175 on('session.start', { isInteractive: true }, async ($, e, next) => {
176 await $.command.register({
177 name: 'jumuah',
178 description: "The sunnah of Friday and what's done; /jumuah read once you've read Al-Kahf",
179 })
180 watch.lastTick = await $.clock.now()
181 await tick($, watch)
182 $.clock.every(TICK_MS, () => void tick($, watch))
183 return next(e)
184 })
185}
186hooks/prayer-times/index.ts 249 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface } from 'claude-code'
3
4import type { PrayerTimesDay } from '../../types'
5
6import { IP_URLS, geocodeUrl, parseCoordinates, parseGeocode, parseIpLookup, parseTimeZone, timeZoneUrl } from './location'
7import type { Place } from './location'
8import { METHOD_NAMES, PRAYERS, clock, countdown, isTimeZone, localDate, methodFor, prayerTimes, schedule, span, upcoming } from './praytimes'
9import type { Asr, Entry, Method, Settings } from './praytimes'
10import type { Feature } from '../feature'
11
12// Every second, so the countdown in the status line counts down.
13const TICK_MS = 1e3
14// A crossing older than this (the laptop slept through it) is not announced.
15const STALE_MS = 5 * 60e3
16// An IP lookup is redone daily, so the times follow a traveller.
17const IP_CACHE_MS = 24 * 3600e3
18// Until a place is found, look again this often; once found, hourly (the cache keeps that offline).
19const RETRY_MS = 60e3
20const RELOCATE_MS = 3600e3
21// How long the session start waits for the place before going on without it.
22const START_WAIT_MS = 3e3
23const PLACE_KEY = 'prayer.place'
24
25const today = atom({ plugin: 'muslim-mods', key: 'prayerToday' } as const, null)
26const line = atom({ plugin: 'muslim-mods', key: 'prayerLine' } as const, null)
27
28const isPrayer = (slot: Entry) => (PRAYERS as readonly string[]).includes(slot.name)
29
30// Said when a sunnah prayer's time begins; no early reminder, since neither is missed by minutes.
31const SUNNAH_TOASTS: Partial<Record<Entry['name'], (at: string) => string>> = {
32 Tahajud: at => `🌙 The last third of the night has begun (${at}): time for Tahajud, until Fajr.`,
33 Dhuha: at => `☀️ Dhuha has begun (${at}), until shortly before Dhuhr.`,
34}
35// While a sunnah prayer's time lasts, the hint line says so after the countdown.
36// Dhuha stops 10 minutes before Dhuhr, clear of the sun's zenith (istiwa); Tahajud runs to Fajr.
37const SUNNAH_WINDOWS: Partial<Record<Entry['name'], { until: Entry['name']; marginMs: number; label: string }>> = {
38 Tahajud: { until: 'Fajr', marginMs: 0, label: '🌙 Tahajud time' },
39 Dhuha: { until: 'Dhuhr', marginMs: 10 * 60e3, label: '☀️ Dhuha time' },
40}
41
42/** The sunnah prayer whose time it is now, if any. */
43function sunnahNow(all: Entry[], now: number) {
44 for (const [i, slot] of all.entries()) {
45 const window = SUNNAH_WINDOWS[slot.name]
46 if (!window || slot.at > now) continue
47 const end = all.slice(i + 1).find(e => e.name === window.until)
48 if (end && now < end.at - window.marginMs) return window.label
49 }
50 return undefined
51}
52
53type Cached = { query: string; at: number; place: Place }
54
55type Watch = {
56 query: string
57 method: string
58 asr: Asr
59 ihtiyatMinutes: number
60 reminderMs: number
61 sunnah: boolean
62 place?: Place
63 settings?: Settings
64 lastTick: number
65 published?: string
66 settling?: Promise<void>
67 lastSettle: number
68}
69
70async function fetchJson($: EngineInterface, url: string) {
71 try {
72 const res = await $.http.fetch(url, { headers: { accept: 'application/json' } })
73 return res.ok ? res.text : undefined
74 } catch {
75 return undefined
76 }
77}
78
79/** Where the person is: the coordinates or city they set, else a lookup of their IP; cached in the store. */
80async function locate($: EngineInterface, query: string): Promise<Place | undefined> {
81 const now = await $.clock.now()
82 const cached = (await $.store.get(PLACE_KEY)) as Cached | undefined
83 const isFresh = cached?.query === query && (query !== '' || now - cached.at < IP_CACHE_MS)
84 if (cached && isFresh) return cached.place
85
86 const systemZone = Intl.DateTimeFormat().resolvedOptions().timeZone
87 let place: Place | undefined
88 // A guess (a time zone not looked up) is used but not cached, so the next lookup tries again.
89 let isGuess = false
90 const coordinates = parseCoordinates(query)
91 if (coordinates) {
92 // The coordinates' own time zone, which may not be this machine's; offline, this machine's.
93 const text = await fetchJson($, timeZoneUrl(coordinates.latitude, coordinates.longitude))
94 const timeZone = text ? parseTimeZone(text) : undefined
95 isGuess = !timeZone
96 place = { name: `${coordinates.latitude}, ${coordinates.longitude}`, timeZone: timeZone ?? systemZone, ...coordinates }
97 } else if (query !== '') {
98 const text = await fetchJson($, geocodeUrl(query))
99 place = text ? parseGeocode(text) : undefined
100 } else {
101 for (const url of IP_URLS) {
102 const text = await fetchJson($, url)
103 place = text ? parseIpLookup(text) : undefined
104 if (place) break
105 }
106 }
107
108 if (!place) return cached?.query === query ? cached.place : undefined
109 if (!isTimeZone(place.timeZone)) place = { ...place, timeZone: systemZone }
110 if (isGuess) return place
111 await $.store.set(PLACE_KEY, { query, at: now, place } satisfies Cached)
112 return place
113}
114
115/** Puts today's times in this mod's state, where other mods (adhkar) read them. */
116async function publish($: EngineInterface, w: Watch, now: number, s: Settings) {
117 const slots = prayerTimes(now, s)
118 const date = localDate(now, s.timeZone)
119 const key = `${date} ${s.latitude} ${s.longitude} ${s.method} ${s.asr} ${s.ihtiyatMinutes}`
120 if (w.published === key) return
121 w.published = key
122 const day: PrayerTimesDay = { date, timeZone: s.timeZone, slots }
123 await update($, today, () => day)
124}
125
126async function tick($: EngineInterface, w: Watch) {
127 const now = await $.clock.now()
128 relocate($, w, now)
129 const s = w.settings
130 if (!s) {
131 const text = w.settling ? '🕌 Finding your location…' : '🕌 Location unknown: /prayer-times <your city>'
132 await update($, line, () => text)
133 w.lastTick = now
134 return
135 }
136 await publish($, w, now, s)
137 const all = upcoming(now, s)
138 const prayers = all.filter(isPrayer)
139 const crossed = (at: number) => w.lastTick < at && at <= now && now - at < STALE_MS
140
141 if (w.sunnah) {
142 for (const slot of all) {
143 const say = SUNNAH_TOASTS[slot.name]
144 if (say && crossed(slot.at)) $.ui.toast(say(clock(slot.at, s.timeZone)), { timeoutMs: 20e3 })
145 }
146 }
147
148 for (const p of prayers) {
149 if (crossed(p.at)) {
150 $.ui.toast(`🕌 It's time for ${p.name} (${clock(p.at, s.timeZone)}). Time to pray.`, {
151 timeoutMs: 20e3,
152 })
153 } else if (w.reminderMs > 0 && crossed(p.at - w.reminderMs)) {
154 $.ui.toast(`🕌 ${p.name} in ${span(p.at - now)}, at ${clock(p.at, s.timeZone)}`, {
155 timeoutMs: 12e3,
156 })
157 }
158 }
159
160 const next = prayers.find(p => p.at > now)
161 if (next) {
162 const sunnah = w.sunnah ? sunnahNow(all, now) : undefined
163 const text = `🕌 ${next.name} ${clock(next.at, s.timeZone)} · in ${countdown(next.at - now)}${sunnah ? ` · ${sunnah}` : ''}`
164 await update($, line, () => text)
165 }
166 w.lastTick = now
167}
168
169async function settle($: EngineInterface, w: Watch) {
170 w.place = await locate($, w.query)
171 if (!w.place) return
172 const method = (METHOD_NAMES as readonly string[]).includes(w.method)
173 ? (w.method as Method)
174 : methodFor(w.place.countryCode)
175 w.settings = {
176 latitude: w.place.latitude,
177 longitude: w.place.longitude,
178 timeZone: w.place.timeZone,
179 method,
180 asr: w.asr,
181 ihtiyatMinutes: w.ihtiyatMinutes,
182 }
183}
184
185/** Looks the place up again when due, in the background, so a missed network or a move is caught up. */
186function relocate($: EngineInterface, w: Watch, now: number) {
187 if (w.settling || now - w.lastSettle < (w.settings ? RELOCATE_MS : RETRY_MS)) return w.settling
188 w.lastSettle = now
189 w.settling = settle($, w)
190 .catch(() => undefined)
191 .finally(() => {
192 w.settling = undefined
193 })
194 return w.settling
195}
196
197export const prayer: Feature = (on, options) => {
198 const watch: Watch = {
199 query: String(options.city ?? '').trim(),
200 method: String(options.method ?? 'Auto'),
201 asr: String(options.asr ?? 'Standard') as Asr,
202 ihtiyatMinutes: Number(options.ihtiyatMinutes ?? -1),
203 reminderMs: Number(options.reminderMinutes ?? 10) * 60e3,
204 sunnah: options.sunnahReminders !== false,
205 lastTick: 0,
206 lastSettle: -Infinity,
207 }
208
209 on('command.run', { command: 'prayer-times' }, async ($, e) => {
210 const city = e.args.trim()
211 if (city !== '') {
212 const set = await $.config.set({ key: 'muslim-mods.city', value: city })
213 return { text: set.deny ? `Could not save the city: ${set.deny}` : `Prayer times will now follow ${city}.` }
214 }
215
216 const s = watch.settings
217 if (!s || !watch.place) {
218 return { text: 'Location unknown. Set it with /prayer-times <your city>, or "lat, lng".' }
219 }
220 const now = await $.clock.now()
221 const today = schedule(now, s)
222 const next = upcoming(now, s).filter(isPrayer).find(p => p.at > now)
223 const lines = today.map(slot => {
224 const mark = next && slot.at === next.at ? ' ← next' : ''
225 return ` ${slot.name.padEnd(8)} ${clock(slot.at, s.timeZone)}${mark}`
226 })
227 const where = `${watch.place.name} · ${s.timeZone} · ${s.method}${watch.query === '' ? ' · detected from your IP' : ''}`
228
229 return { text: [`Prayer times today (${where})`, ...lines].join('\n') }
230 })
231
232
233 on('session.start', { isInteractive: true }, async ($, e, next) => {
234 await $.command.register({
235 name: 'prayer-times',
236 description: "Show today's prayer times, or set your city: /prayer-times <city>",
237 })
238 // Versions before 0.4 pinned a status line, which outlives a reload; this one lives on the hint line.
239 $.ui.status(undefined)
240 watch.lastTick = await $.clock.now()
241 // Waits a moment for the place; a slow network finishes it in the background.
242 const settling = relocate($, watch, watch.lastTick)
243 if (settling) await Promise.race([settling, $.clock.sleep(START_WAIT_MS)])
244 await tick($, watch)
245 $.clock.every(TICK_MS, () => void tick($, watch))
246 return next(e)
247 })
248}
249hooks/ramadan/index.ts 216 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface } from 'claude-code'
3
4import type { HijriDate, PrayerTimesDay } from '../../types'
5import type { Feature } from '../feature'
6
7// Every second, so the countdown under the prompt counts down.
8const TICK_MS = 1e3
9// A crossing older than this (the laptop slept through it) is not announced.
10const STALE_MS = 5 * 60e3
11// From this day of Ramadan, a reminder of zakat al-fitr, once.
12const ZAKAT_DAY = 27
13const ZAKAT_KEY = 'ramadan.zakat'
14
15const IFTAR_DUA =
16 "Dhahaba al-zama'u wabtallatil-'uruqu wa thabatal-ajru in sha' Allah: the thirst is gone, the veins are moistened and the reward is certain, if Allah wills (Abu Dawud 2357)."
17const QADR_DUA =
18 "Allahumma innaka 'afuwwun tuhibbul-'afwa fa'fu 'anni: O Allah, You are pardoning and love to pardon, so pardon me (Tirmidhi 3513)."
19
20const line = atom({ plugin: 'muslim-mods', key: 'ramadanLine' } as const, null)
21const prayerDay = { plugin: 'muslim-mods', key: 'prayerToday' } as const
22const hijriAhead = { plugin: 'muslim-mods', key: 'hijriAhead' } as const
23
24const DAY_MS = 864e5
25export const addDays = (ymd: string, n: number) =>
26 new Date(Date.parse(`${ymd}T00:00:00Z`) + n * DAY_MS).toISOString().slice(0, 10)
27
28const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
29const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
30/** A Gregorian date as `Mon 8 Feb`. */
31const dateLabel = (ymd: string) => {
32 const d = new Date(`${ymd}T12:00:00Z`)
33 return `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}`
34}
35
36const zones = new Map<string, Intl.DateTimeFormat>()
37function parts(now: number, timeZone?: string) {
38 let zone = zones.get(timeZone ?? '')
39 if (!zone) {
40 zone = new Intl.DateTimeFormat('en-CA', {
41 timeZone,
42 year: 'numeric',
43 month: '2-digit',
44 day: '2-digit',
45 hour: '2-digit',
46 minute: '2-digit',
47 hourCycle: 'h23',
48 })
49 zones.set(timeZone ?? '', zone)
50 }
51 const all = zone.formatToParts(now)
52 return (type: string) => all.find(p => p.type === type)?.value ?? ''
53}
54const localDate = (now: number, timeZone?: string) => {
55 const part = parts(now, timeZone)
56 return `${part('year')}-${part('month')}-${part('day')}`
57}
58const clock = (now: number, timeZone?: string) => {
59 const part = parts(now, timeZone)
60 return `${part('hour')}:${part('minute')}`
61}
62
63/** H:MM:SS, for a countdown that ticks every second. */
64export function countdown(ms: number) {
65 const seconds = Math.max(0, Math.ceil(ms / 1e3))
66 const pad = (n: number) => String(n).padStart(2, '0')
67 return `${Math.floor(seconds / 3600)}:${pad(Math.floor(seconds / 60) % 60)}:${pad(seconds % 60)}`
68}
69
70const isOddNight = (night: number) => night >= 21 && night % 2 === 1
71
72type Settings = { suhurMs: number; imsakMs: number; tarawih: boolean }
73type Watch = { settings: Settings; lastTick: number; zakatSaid: boolean }
74
75/**
76 * Where Ramadan stands at `now`: today's times if known, today's Hijri date by daytime,
77 * and tonight's (the next day's), since a night of Ramadan precedes its day.
78 */
79async function context($: EngineInterface, now: number) {
80 const { value: day } = (await $.state.get(prayerDay)) as { value: PrayerTimesDay }
81 const { value: ahead } = (await $.state.get(hijriAhead)) as { value: HijriDate[] }
82 const timeZone = day?.timeZone
83 const today = localDate(now, timeZone)
84 const slot = (name: string) => (day?.date === today ? day.slots.find(s => s.name === name)?.at : undefined)
85 const hijri = (ymd: string) => ahead?.find(h => h.gregorian === ymd)
86 const fajr = slot('Fajr')
87 const todayH = hijri(today)
88 const tomorrowH = hijri(addDays(today, 1))
89 // Between midnight and Fajr the night in progress is the one before today's fast, not tomorrow's.
90 const isNightBeforeFajr = fajr !== undefined && now < fajr
91 const tonightH = isNightBeforeFajr ? todayH : tomorrowH
92 return {
93 timeZone,
94 today,
95 fajr,
96 maghrib: slot('Maghrib'),
97 isha: slot('Isha'),
98 ahead: ahead ?? [],
99 /** Today is a fasting day of Ramadan: its Ramadan day, else undefined. */
100 fastDay: todayH?.month === 9 ? todayH.day : undefined,
101 /** Tonight is a night of Ramadan: its number, else undefined. */
102 night: tonightH?.month === 9 ? tonightH.day : undefined,
103 isEidEve: !isNightBeforeFajr && tomorrowH?.month === 10 && tomorrowH.day === 1,
104 }
105}
106
107async function tick($: EngineInterface, w: Watch) {
108 const s = w.settings
109 const now = await $.clock.now()
110 const c = await context($, now)
111 const crossed = (at: number | undefined) => at !== undefined && w.lastTick < at && at <= now && now - at < STALE_MS
112 const at = (t: number) => clock(t, c.timeZone)
113
114 if (c.fastDay !== undefined && c.fajr !== undefined) {
115 const imsak = c.fajr - s.imsakMs
116 if (s.suhurMs > 0 && s.suhurMs > s.imsakMs && crossed(c.fajr - s.suhurMs)) {
117 const until = s.imsakMs > 0 ? `imsak at ${at(imsak)}, Fajr at ${at(c.fajr)}` : `Fajr at ${at(c.fajr)}`
118 $.ui.toast(`🍽 Suhur, day ${c.fastDay} of Ramadan: ${until}.`, { timeoutMs: 20e3 })
119 }
120 if (s.imsakMs > 0 && crossed(imsak)) {
121 $.ui.toast(`🍽 Imsak (${at(imsak)}): finish your suhur, Fajr is at ${at(c.fajr)}.`, { timeoutMs: 20e3 })
122 }
123 }
124 if (c.fastDay !== undefined && crossed(c.maghrib)) {
125 $.ui.toast(`🍽 It's Maghrib: time to break your fast. ${IFTAR_DUA}`, { timeoutMs: 30e3 })
126 }
127 if (c.night !== undefined && crossed(c.isha)) {
128 const tarawih = s.tarawih ? `🌙 Isha: Tarawih tonight, night ${c.night} of Ramadan.` : ''
129 const qadr = isOddNight(c.night) ? `✨ An odd night of the last ten: seek Laylat al-Qadr. ${QADR_DUA}` : ''
130 const text = [tarawih, qadr].filter(Boolean).join(' ')
131 if (text) $.ui.toast(text, { timeoutMs: 30e3 })
132 }
133 if (c.fastDay !== undefined && c.fastDay >= ZAKAT_DAY && !w.zakatSaid) {
134 w.zakatSaid = true
135 const year = c.ahead.find(h => h.gregorian === c.today)?.year
136 if ((await $.store.get(ZAKAT_KEY)) !== year) {
137 $.ui.toast('🤲 Ramadan is ending: pay zakat al-fitr before the Eid prayer.', { timeoutMs: 20e3 })
138 await $.store.set(ZAKAT_KEY, year)
139 }
140 }
141
142 let text: string | null = null
143 if (c.fastDay !== undefined && c.fajr !== undefined && c.maghrib !== undefined) {
144 const imsak = c.fajr - s.imsakMs
145 if (now < imsak) text = `🍽 Imsak ${at(imsak)} · in ${countdown(imsak - now)}`
146 else if (now < c.fajr) text = `🍽 Imsak · Fajr ${at(c.fajr)} · in ${countdown(c.fajr - now)}`
147 else if (now < c.maghrib) text = `🍽 Iftar ${at(c.maghrib)} · in ${countdown(c.maghrib - now)}`
148 }
149 if (text === null && c.night !== undefined && isOddNight(c.night) && c.maghrib !== undefined && now >= c.maghrib) {
150 text = `✨ Night ${c.night} · seek Laylat al-Qadr`
151 }
152 if ((await read($, line)) !== text) await update($, line, () => text)
153 w.lastTick = now
154}
155
156export const ramadan: Feature = (on, options) => {
157 const minutes = (value: unknown, fallback: number) => Math.max(0, Number(value ?? fallback)) * 60e3
158 const watch: Watch = {
159 settings: {
160 suhurMs: minutes(options.suhurMinutes, 45),
161 imsakMs: minutes(options.imsakMinutes, 10),
162 tarawih: options.tarawih !== false,
163 },
164 lastTick: 0,
165 zakatSaid: false,
166 }
167
168 on('command.run', { command: 'ramadan' }, async $ => {
169 const s = watch.settings
170 const now = await $.clock.now()
171 const c = await context($, now)
172 const at = (t: number) => clock(t, c.timeZone)
173
174 if (c.fastDay === undefined && c.night === undefined) {
175 if (!c.ahead.length) return { text: "The Hijri dates aren't in yet: is the Hijri date on? /muslim on hijri" }
176 const first = c.ahead.find(h => h.month === 9 && h.day === 1)
177 if (!first) return { text: 'Ramadan is more than 30 days away. /hijri lists when it begins.' }
178 const days = Math.round((Date.parse(first.gregorian) - Date.parse(c.today)) / DAY_MS)
179 return {
180 text: `Ramadan ${first.year} begins in ${days} day${days === 1 ? '' : 's'}: the first fast is ${dateLabel(first.gregorian)}, Tarawih the evening before.`,
181 }
182 }
183
184 const lines: string[] = []
185 if (c.fastDay !== undefined) {
186 lines.push(`Day ${c.fastDay} of Ramadan, ${dateLabel(c.today)}.`)
187 if (c.fajr !== undefined && c.maghrib !== undefined) {
188 const imsak = s.imsakMs > 0 ? `Imsak ${at(c.fajr - s.imsakMs)} · ` : ''
189 lines.push(` ${imsak}Fajr ${at(c.fajr)} · Iftar ${at(c.maghrib)}${c.isha !== undefined ? ` · Isha ${at(c.isha)}` : ''}`)
190 } else {
191 lines.push(' The times are unknown until prayer-times finds your place: /prayer-times <your city>.')
192 }
193 }
194 if (c.night !== undefined) {
195 const qadr = isOddNight(c.night) ? ': an odd night of the last ten, seek Laylat al-Qadr' : ''
196 lines.push(`Tonight is night ${c.night} of Ramadan${qadr}.`)
197 if (c.night < 21) {
198 const nights = 21 - c.night
199 lines.push(`The last ten nights begin in ${nights} night${nights === 1 ? '' : 's'}.`)
200 }
201 } else if (c.isEidEve) {
202 lines.push("Tonight is Eid's eve: takbir, and zakat al-fitr before the Eid prayer.")
203 }
204 return { text: lines.join('\n') }
205 })
206
207
208 on('session.start', { isInteractive: true }, async ($, e, next) => {
209 await $.command.register({ name: 'ramadan', description: "Today's imsak and iftar in Ramadan, or how long until it" })
210 watch.lastTick = await $.clock.now()
211 await tick($, watch)
212 $.clock.every(TICK_MS, () => void tick($, watch))
213 return next(e)
214 })
215}
216hooks/sunnah-fasting/index.ts 205 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface } from 'claude-code'
3
4import type { HijriDate, PrayerTimesDay } from '../../types'
5
6import { addDays, clock, dateLabel, describe, fastOn, isReminded, localDate, localHour, weekday } from './fasts'
7import type { FastDay, Options } from './fasts'
8import type { Feature } from '../feature'
9
10const TICK_MS = 10e3
11// A crossing older than this (the laptop slept through it) is not announced.
12const STALE_MS = 5 * 60e3
13// Without prayer-times, the evening (when tomorrow's reminder comes) begins at this hour.
14const EVENING_HOUR = 18
15const LIST_DAYS = 14
16// The days you said you'd fast, as YYYY-MM-DD, and the evening reminders given; both kept across sessions.
17const INTENT_KEY = 'fasting.intent'
18const ANNOUNCED_KEY = 'fasting.announced'
19
20const IFTAR_DUA =
21 "Dhahaba al-zama'u wabtallatil-'uruqu wa thabatal-ajru in sha' Allah: the thirst is gone, the veins are moistened and the reward is certain, if Allah wills (Abu Dawud 2357)."
22
23const line = atom({ plugin: 'muslim-mods', key: 'fastingLine' } as const, null)
24const prayerDay = { plugin: 'muslim-mods', key: 'prayerToday' } as const
25const hijriAhead = { plugin: 'muslim-mods', key: 'hijriAhead' } as const
26
27type Settings = Options & { suhurMs: number }
28type Watch = { settings: Settings; lastTick: number }
29
30/** Where things stand at `now`: the local date, today's Fajr and Maghrib if known, and whether it is evening. */
31async function context($: EngineInterface, now: number) {
32 const { value: day } = (await $.state.get(prayerDay)) as { value: PrayerTimesDay }
33 const { value: ahead } = (await $.state.get(hijriAhead)) as { value: HijriDate[] }
34 const timeZone = day?.timeZone
35 const today = localDate(now, timeZone)
36 const slot = (name: string) => (day?.date === today ? day.slots.find(s => s.name === name)?.at : undefined)
37 const fajr = slot('Fajr')
38 const maghrib = slot('Maghrib')
39 const isEvening = maghrib !== undefined ? now >= maghrib : localHour(now, timeZone) >= EVENING_HOUR
40 const fastFor = (ymd: string, s: Settings) => fastOn(ymd, ahead?.find(h => h.gregorian === ymd), s)
41 return { timeZone, today, fajr, maghrib, isEvening, hasTimes: !!day, fastFor }
42}
43
44const intents = async ($: EngineInterface) => ((await $.store.get(INTENT_KEY)) as string[] | undefined) ?? []
45
46async function setIntent($: EngineInterface, ymd: string, on: boolean, today: string) {
47 // Days before yesterday are dropped; they're done.
48 const kept = (await intents($)).filter(d => d !== ymd && d >= addDays(today, -1))
49 await $.store.set(INTENT_KEY, on ? [...kept, ymd].sort() : kept)
50}
51
52function eveText(day: string, f: FastDay, isIntended: boolean, suhurAt: string | undefined, isToday: boolean) {
53 const why = f.kind === 'sunnah' ? describe(f.reasons) : ''
54 const when = isToday ? 'today' : 'tomorrow'
55 if (isIntended) {
56 const suhur = suhurAt ? ` Suhur reminder around ${suhurAt}.` : ''
57 return `🌙 You're fasting ${when}, ${dateLabel(day)}${why ? ` (${why})` : ''}.${suhur}`
58 }
59 const until = isToday && suhurAt ? ' There is still time for suhur.' : ''
60 return `🌙 ${isToday ? 'Today' : 'Tomorrow'} is ${why}: fasting is sunnah.${until} /fasting on to fast it, with suhur and iftar reminders.`
61}
62
63async function tick($: EngineInterface, w: Watch) {
64 const s = w.settings
65 const now = await $.clock.now()
66 const c = await context($, now)
67 const crossed = (at: number | undefined) => at !== undefined && w.lastTick < at && at <= now && now - at < STALE_MS
68 const intended = await intents($)
69 const isFasting = intended.includes(c.today)
70
71 // From Maghrib, tomorrow's fast; after midnight until Fajr (a session opened then), today's, while suhur is still possible.
72 const isBeforeFajr = c.fajr !== undefined && now < c.fajr
73 const upcoming = c.isEvening ? addDays(c.today, 1) : isBeforeFajr ? c.today : undefined
74 if (upcoming) {
75 const f = c.fastFor(upcoming, s)
76 const isIntended = intended.includes(upcoming)
77 const key = `eve:${upcoming}`
78 const announced = ((await $.store.get(ANNOUNCED_KEY)) as string[] | undefined) ?? []
79 if ((isReminded(f) || isIntended) && f.kind !== 'forbidden' && !announced.includes(key)) {
80 // Tomorrow's Fajr is close enough to today's for a reminder's "around".
81 const fajr = isBeforeFajr ? c.fajr : c.fajr !== undefined ? c.fajr + 864e5 : undefined
82 const suhurAt = fajr !== undefined && s.suhurMs > 0 ? clock(fajr - s.suhurMs, c.timeZone) : undefined
83 $.ui.toast(eveText(upcoming, f, isIntended, suhurAt, isBeforeFajr), { timeoutMs: 20e3 })
84 await $.store.set(ANNOUNCED_KEY, [...announced.slice(-20), key])
85 }
86 }
87
88 if (isFasting && c.fajr !== undefined && s.suhurMs > 0 && crossed(c.fajr - s.suhurMs)) {
89 $.ui.toast(`🍽 Suhur: Fajr is at ${clock(c.fajr, c.timeZone)}, in ${Math.round(s.suhurMs / 60e3)}m.`, { timeoutMs: 20e3 })
90 }
91 if (isFasting && crossed(c.maghrib)) {
92 $.ui.toast(`🍽 It's Maghrib: time to break your fast. ${IFTAR_DUA}`, { timeoutMs: 30e3 })
93 }
94
95 let text: string | null = null
96 if (isFasting && c.fajr !== undefined && now < c.fajr) text = `🍽 Suhur until ${clock(c.fajr, c.timeZone)}`
97 else if (isFasting && c.maghrib !== undefined && now < c.maghrib) text = `🍽 Fasting · iftar ${clock(c.maghrib, c.timeZone)}`
98 if ((await read($, line)) !== text) await update($, line, () => text)
99 w.lastTick = now
100}
101
102export const sunnahFasting: Feature = (on, options) => {
103 const watch: Watch = {
104 settings: {
105 mondayThursday: options.mondayThursday !== false,
106 whiteDays: options.whiteDays !== false,
107 suhurMs: Math.max(0, Number(options.suhurMinutes ?? 45)) * 60e3,
108 },
109 lastTick: 0,
110 }
111
112 on('command.run', { command: 'fasting' }, async ($, e) => {
113 const s = watch.settings
114 const now = await $.clock.now()
115 const c = await context($, now)
116 const [verb = '', arg = ''] = e.args.trim().toLowerCase().split(/\s+/)
117
118 if (verb === 'on' || verb === 'off') {
119 // Today until Maghrib, tomorrow after it.
120 const target =
121 arg === 'today' ? c.today
122 : arg === 'tomorrow' ? addDays(c.today, 1)
123 : /^\d{4}-\d{2}-\d{2}$/.test(arg) ? arg
124 : arg === '' ? (c.isEvening ? addDays(c.today, 1) : c.today)
125 : undefined
126 if (!target || Number.isNaN(Date.parse(`${target}T00:00:00Z`)) || addDays(target, 0) !== target) {
127 return { text: 'Usage: /fasting on|off [today|tomorrow|YYYY-MM-DD]' }
128 }
129 if (target < c.today) return { text: `${dateLabel(target)} has passed.` }
130 if (verb === 'off') {
131 await setIntent($, target, false, c.today)
132 await tick($, watch)
133 return { text: `Not fasting ${dateLabel(target)}; its reminders are off.` }
134 }
135 const f = c.fastFor(target, s)
136 if (f.kind === 'forbidden') return { text: `${dateLabel(target)} is ${f.why}: fasting is forbidden on it.` }
137 if (f.kind === 'ramadan') {
138 return { text: `${dateLabel(target)} is in Ramadan: Ramadan gives the suhur and iftar reminders every day.` }
139 }
140 await setIntent($, target, true, c.today)
141 await tick($, watch)
142 const why = f.kind === 'sunnah' ? ` (${describe(f.reasons)})` : ''
143 // Singling out Friday is disliked (Bukhari 1985), unless with the day before or after, or for a reason of its own.
144 const all = await intents($)
145 const isFridayAlone =
146 weekday(target) === 5 &&
147 f.kind === 'none' &&
148 !all.includes(addDays(target, -1)) &&
149 !all.includes(addDays(target, 1))
150 const friday = isFridayAlone
151 ? ' Fasting a Friday on its own is disliked (Bukhari 1985): fast Thursday or Saturday with it.'
152 : ''
153 if (!c.hasTimes) {
154 return {
155 text: `Fasting ${dateLabel(target)}${why}. Prayer times are off or have no place yet, so no suhur or iftar reminder will come.${friday}`,
156 }
157 }
158 const isToday = target === c.today
159 const suhur = s.suhurMs > 0 && !(isToday && c.fajr !== undefined && now >= c.fajr)
160 ? `a suhur reminder ${Math.round(s.suhurMs / 60e3)} minutes before Fajr and `
161 : ''
162 const iftar = isToday && c.maghrib !== undefined ? `iftar at ${clock(c.maghrib, c.timeZone)}` : 'one at Maghrib for iftar'
163 return { text: `Fasting ${dateLabel(target)}${why}. You'll get ${suhur}${iftar}.${friday}` }
164 }
165 if (verb !== '') return { text: 'Usage: /fasting, or /fasting on|off [today|tomorrow|YYYY-MM-DD]' }
166
167 const intended = await intents($)
168 const lines: string[] = []
169 const todayFast = c.fastFor(c.today, s)
170 if (intended.includes(c.today)) {
171 lines.push(`Fasting today${c.maghrib !== undefined ? `: iftar at ${clock(c.maghrib, c.timeZone)}` : ''}.`)
172 } else if (todayFast.kind === 'forbidden') {
173 lines.push(`Today is ${todayFast.why}: no fasting.`)
174 } else if (todayFast.kind === 'ramadan') {
175 lines.push('It is Ramadan.')
176 }
177 lines.push('Sunnah fasts in the next two weeks:')
178 let listed = 0
179 for (let i = 0; i < LIST_DAYS; i++) {
180 const ymd = addDays(c.today, i)
181 const f = c.fastFor(ymd, s)
182 if (f.kind !== 'sunnah' && !intended.includes(ymd)) continue
183 const mark = intended.includes(ymd) ? '✓' : ' '
184 const why = f.kind === 'sunnah' ? describe(f.reasons) : f.kind === 'ramadan' ? 'Ramadan' : ''
185 lines.push(` ${mark} ${dateLabel(ymd).padEnd(11)} ${why.replace(/^./, ch => ch.toUpperCase())}`)
186 listed++
187 }
188 if (!listed) lines.push(' None.')
189 lines.push('', "✓ marks the days you said you'd fast. /fasting on to fast today (or tomorrow, after Maghrib).")
190 return { text: lines.join('\n') }
191 })
192
193
194 on('session.start', { isInteractive: true }, async ($, e, next) => {
195 await $.command.register({
196 name: 'fasting',
197 description: 'Sunnah fasts in the next two weeks; /fasting on or off to fast today or tomorrow (or a YYYY-MM-DD)',
198 })
199 watch.lastTick = await $.clock.now()
200 await tick($, watch)
201 $.clock.every(TICK_MS, () => void tick($, watch))
202 return next(e)
203 })
204}
205hooks/tasbih/index.tsx 222 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, SessionRateLimit } from 'claude-code'
3
4import type { ReachedLimit } from '../../types'
5import type { Feature } from '../feature'
6
7const PANE = 'tasbih'
8const PANE_COLUMNS = 80
9const TICK_MS = 60e3
10const INVITED_KEY = 'tasbih.invited'
11
12const reached = atom({ plugin: 'muslim-mods', key: 'tasbihReached' } as const, [])
13const index = atom({ plugin: 'muslim-mods', key: 'tasbihIndex' } as const, 0)
14const counts = atom({ plugin: 'muslim-mods', key: 'tasbihCounts' } as const, {})
15const line = atom({ plugin: 'muslim-mods', key: 'tasbihLine' } as const, null)
16// Set by /tasbih demo, whose made-up limit /tasbih close takes away again; in state, so a reload keeps it.
17const isDemo = atom({ plugin: 'muslim-mods', key: 'tasbihDemo' } as const, false)
18
19export type Phrase = { id: string; arabic: string; transliteration: string; english: string; repeat: number }
20
21/** The tasbih after prayer, completed to a hundred with the tahlil (Sahih Muslim 597). */
22export const PHRASES: Phrase[] = [
23 { id: 'subhanallah', arabic: 'سُبْحَانَ اللّٰهِ', transliteration: 'SubḥānAllāh', english: 'Glory be to Allah.', repeat: 33 },
24 { id: 'alhamdulillah', arabic: 'الْحَمْدُ لِلّٰهِ', transliteration: 'Alḥamdulillāh', english: 'All praise is for Allah.', repeat: 33 },
25 { id: 'allahuakbar', arabic: 'اللّٰهُ أَكْبَرُ', transliteration: 'Allāhu akbar', english: 'Allah is the Greatest.', repeat: 33 },
26 {
27 id: 'tahlil',
28 arabic: 'لَا إِلٰهَ إِلَّا اللّٰهُ وَحْدَهُ لَا شَرِيكَ لَهُ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ وَهُوَ عَلَىٰ كُلِّ شَيْءٍ قَدِيرٌ',
29 transliteration: "Lā ilāha illallāhu waḥdahu lā sharīka lah, lahul-mulku wa lahul-ḥamdu wa huwa ʿalā kulli shay'in qadīr",
30 english: 'None has the right to be worshipped but Allah alone, without partner. His is the dominion and His is the praise, and He is able to do all things.',
31 repeat: 1,
32 },
33]
34
35const NAME: Record<ReachedLimit['kind'], string> = { five_hour: '5-hour limit', seven_day: 'weekly limit' }
36
37/** The 5-hour and weekly windows that are used up; a gateway's spend limit is left out. */
38export function reachedOf(limits: SessionRateLimit[]): ReachedLimit[] {
39 return limits
40 .filter(l => (l.kind === 'five_hour' || l.kind === 'seven_day') && l.percentUsed >= 100)
41 .map(l => ({ kind: l.kind as ReachedLimit['kind'], resetsAt: l.resetsAt }))
42}
43
44/** The windows still used up at `now`: one past its reset time has come back. */
45const stillReached = (list: ReachedLimit[], now: number) =>
46 list.filter(l => !l.resetsAt || Date.parse(l.resetsAt) > now)
47
48/** "2h 14m", "45m", "3d 4h". */
49export function untilText(resetsAt: string | undefined, now: number) {
50 if (!resetsAt) return undefined
51 const minutes = Math.max(1, Math.ceil((Date.parse(resetsAt) - now) / 60e3))
52 const d = Math.floor(minutes / 1440)
53 const h = Math.floor((minutes % 1440) / 60)
54 const m = minutes % 60
55 if (d) return `${d}d ${h}h`
56 return h ? `${h}h ${m}m` : `${m}m`
57}
58
59/** The window that keeps Claude Code waiting longest: the one that resets last. */
60function longest(list: ReachedLimit[]) {
61 return [...list].sort((a, b) => Date.parse(b.resetsAt ?? '') - Date.parse(a.resetsAt ?? ''))[0]
62}
63
64/** "Your weekly limit is used up, back in 3d 4h." */
65export function messageFor(list: ReachedLimit[], now: number) {
66 const top = longest(list)
67 if (!top) return undefined
68 const names = list.length > 1 ? 'Your 5-hour and weekly limits are' : `Your ${NAME[top.kind]} is`
69 const until = untilText(top.resetsAt, now)
70 return `${names} used up${until ? `, back in ${until}` : ''}.`
71}
72
73const keyOf = (l: ReachedLimit) => `${l.kind}:${l.resetsAt ?? ''}`
74
75/** Rows the pane needs for the longest phrase at this width. */
76export function rowsFor(columns: number, showArabic: boolean) {
77 const width = Math.max(20, columns - 4)
78 const lines = (text: string) => Math.max(1, Math.ceil(text.length / width))
79 const tallest = Math.max(
80 ...PHRASES.map(p => [p.transliteration, p.english, showArabic ? p.arabic : ''].filter(Boolean).reduce((n, t) => n + lines(t) + 1, 0)),
81 )
82 // The message, the progress line and the buttons, each with the gap below it.
83 return tallest + 6
84}
85
86async function refresh($: EngineInterface) {
87 const now = await $.clock.now()
88 const list = stillReached(await read($, reached), now)
89 if (list.length !== (await read($, reached)).length) await update($, reached, () => list)
90 const top = longest(list)
91 const until = untilText(top?.resetsAt, now)
92 const text = top ? `📿 ${NAME[top.kind]} reached${until ? ` · back in ${until}` : ''} · /tasbih` : null
93 if ((await read($, line)) !== text) await update($, line, () => text)
94}
95
96async function open($: EngineInterface, columns: number, showArabic: boolean, focus: boolean) {
97 return $.ui.open({
98 id: PANE,
99 title: 'Tasbih',
100 ...(focus ? { focus: true as const } : {}),
101 closeOnEscape: true,
102 rows: rowsFor(Math.min(PANE_COLUMNS, columns), showArabic),
103 columns: PANE_COLUMNS,
104 })
105}
106
107async function finish($: EngineInterface) {
108 await update($, index, () => 0)
109 await update($, counts, () => ({}))
110 await $.ui.close({ id: PANE })
111 $.ui.toast('📿 May Allah accept it from you.', { timeoutMs: 8e3 })
112}
113
114/** One more; a phrase said its full number of times moves on to the next, the last one finishes. */
115async function count($: EngineInterface) {
116 const i = await read($, index)
117 const p = PHRASES[i]
118 if (!p) return
119 const said = (await update($, counts, c => ({ ...c, [p.id]: Math.min(p.repeat, (c[p.id] ?? 0) + 1) })))[p.id] ?? 0
120 if (said < p.repeat) return
121 if (i < PHRASES.length - 1) await update($, index, () => i + 1)
122 else await finish($)
123}
124
125/** The toast and the pane that ask for dhikr while `list` is used up. */
126async function invite($: EngineInterface, list: ReachedLimit[], columns: number, showArabic: boolean, focus: boolean) {
127 $.ui.toast(`📿 ${messageFor(list, await $.clock.now())} A moment for SubhanAllah, Alhamdulillah, Allahu akbar? /tasbih`, { timeoutMs: 20e3 })
128 return open($, columns, showArabic, focus)
129}
130
131export const tasbih: Feature = (on, options) => {
132 const showArabic = options.showArabic === true
133
134 on('session.measure', async ($, e, next) => {
135 if (!e.changed.includes('rateLimits')) return next(e)
136 const list = reachedOf(e.rateLimits)
137 await update($, isDemo, () => false)
138 await update($, reached, () => list)
139 await refresh($)
140
141 // Invite once per window: a new session in the same used-up window keeps only the hint line.
142 const invited = ((await $.store.get(INVITED_KEY)) as string[] | undefined) ?? []
143 const fresh = list.filter(l => !invited.includes(keyOf(l)))
144 if (fresh.length) {
145 await $.store.set(INVITED_KEY, [...invited, ...fresh.map(keyOf)].slice(-20))
146 // Unasked, the pane seats on a wide terminal and waits on a narrow one; /tasbih opens it anywhere.
147 await invite($, list, PANE_COLUMNS, showArabic, false)
148 }
149
150 return next(e)
151 })
152
153 on('command.run', { command: 'tasbih' }, async ($, e) => {
154 const asked = e.args.trim().toLowerCase()
155 if (asked === 'close') {
156 await $.ui.close({ id: PANE })
157 if (await read($, isDemo)) {
158 await update($, isDemo, () => false)
159 await update($, reached, () => [])
160 await refresh($)
161 }
162 return { text: 'Tasbih closed.' }
163 }
164 if (asked === 'demo') {
165 // What a used-up 5-hour limit looks like, without waiting for one; nothing is remembered as invited.
166 await update($, isDemo, () => true)
167 const list: ReachedLimit[] = [{ kind: 'five_hour', resetsAt: new Date((await $.clock.now()) + 102 * 60e3).toISOString() }]
168 await update($, reached, () => list)
169 await refresh($)
170 await invite($, list, e.presentation.columns, showArabic, true)
171 return { text: 'Tasbih demo: a 5-hour limit made up as used up. /tasbih close ends it.' }
172 }
173 await open($, e.presentation.columns, showArabic, true)
174
175 return { text: 'Tasbih opened. Keys: c count, r restart, x close.' }
176 })
177
178 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
179 const { Box, Button, Text } = $.ui.resolve(e)
180 const i = Math.min(await read($, index), PHRASES.length - 1)
181 const p = PHRASES[i]!
182 const said = (await read($, counts))[p.id] ?? 0
183 const total = PHRASES.reduce((n, q, j) => n + (j < i ? q.repeat : 0), said)
184 const message = messageFor(await read($, reached), await $.clock.now())
185
186 return (
187 <Box flexDirection="column" width={e.props.bodyColumns} gap={1}>
188 <Text dimColor>
189 {message ? `${message} ` : ''}Remember Allah while you wait.
190 </Text>
191 <Text dimColor>
192 {i + 1} of {PHRASES.length} · {total} / 100
193 </Text>
194 <Box flexDirection="row" gap={2}>
195 <Button key="count" variant="primary" hotkey="c" label={`${said} / ${p.repeat}`} onPress={() => count($)} />
196 <Button
197 key="restart"
198 hotkey="r"
199 label="Restart"
200 onPress={async () => {
201 await update($, index, () => 0)
202 await update($, counts, () => ({}))
203 }}
204 />
205 <Button key="close" hotkey="x" role="dismiss" label="Close" onPress={() => $.ui.close({ id: PANE })} />
206 </Box>
207 {showArabic && <Text>{p.arabic}</Text>}
208 <Text bold>{p.transliteration}</Text>
209 <Text>{p.english}</Text>
210 </Box>
211 )
212 })
213
214
215 on('session.start', { isInteractive: true }, async ($, e, next) => {
216 await $.command.register({ name: 'tasbih', description: 'Count SubhanAllah, Alhamdulillah, Allahu akbar: /tasbih [close|demo]' })
217 await refresh($)
218 $.clock.every(TICK_MS, () => void refresh($))
219 return next(e)
220 })
221}
222hooks/adhkar/adhkar.ts 226 lines1// Generated from Hisnul Muslim (hisnmuslim.com), chapter 27: words of remembrance for morning and evening.
2
3export type Dhikr = {
4 id: number
5 repeat: number
6 when: 'both' | 'morning' | 'evening'
7 arabic: string
8 transliteration: string
9 english: string
10 /** The evening wording, where it differs. */
11 evening?: { transliteration: string; english: string }
12 note?: string
13}
14
15export const ADHKAR: Dhikr[] = [
16 {
17 "id": 75,
18 "repeat": 1,
19 "when": "both",
20 "arabic": "أَعُوذُ بِاللَّهِ مِنَ الشَّيطَانِ الرَّجِيمِ ﴿اللَّهُ لاَ إِلَهَ إِلاَّ هُوَ الْحَيُّ الْقَيُّومُ لاَ تَأْخُذُهُ سِنَةٌ وَلاَ نَوْمٌ لَّهُ مَا فِي السَّمَوَاتِ وَمَا فِي الأَرْضِ مَن ذَا الَّذِي يَشْفَعُ عِنْدَهُ إِلاَّ بِإِذْنِهِ يَعْلَمُ مَا بَيْنَ أَيْدِيهِمْ وَمَا خَلْفَهُمْ وَلاَ يُحِيطُونَ بِشَيْءٍ مِّنْ عِلْمِهِ إِلاَّ بِمَا شَاء وَسِعَ كُرْسِيُّهُ السَّمَوَاتِ وَالْأَرْضَ وَلاَ يَؤُودُهُ حِفْظُهُمَا وَهُوَ الْعَلِيُّ الْعَظِيمُ﴾",
21 "transliteration": "Ayat al-Kursi (Al-Baqarah 2:255), after seeking refuge in Allah from the accursed devil.",
22 "english": "Allah - there is no deity except Him, the Ever-Living, the Sustainer of [all] existence. Neither drowsiness overtakes Him nor sleep. To Him belongs whatever is in the heavens and whatever is on the earth. Who is it that can intercede with Him except by His permission? He knows what is [presently] before them and what will be after them, and they encompass not a thing of His knowledge except for what He wills. His Kursi extends over the heavens and the earth, and their preservation tires Him not. And He is the Most High, the Most Great."
23 },
24 {
25 "id": 76,
26 "repeat": 3,
27 "when": "both",
28 "arabic": "بسم الله الرحمن الرحيم ﴿قُلْ هُوَ اللَّهُ أَحَدٌ* اللَّهُ الصَّمَدُ* لَمْ يَلِدْ وَلَمْ يُولَدْ* وَلَمْ يَكُن لَّهُ كُفُواً أَحَدٌ﴾ بسم الله الرحمن الرحيم ﴿قُلْ أَعُوذُ بِرَبِّ الْفَلَقِ* مِن شَرِّ مَا خَلَقَ* وَمِن شَرِّ غَاسِقٍ إِذَا وَقَبَ* وَمِن شَرِّ النَّفَّاثَاتِ فِي الْعُقَدِ* وَمِن شَرِّ حَاسِدٍ إِذَا حَسَدَ﴾ بسم الله الرحمن الرحيم ﴿قُلْ أَعُوذُ بِرَبِّ النَّاسِ* مَلِكِ النَّاسِ* إِلَهِ النَّاسِ* مِن شَرِّ الْوَسْوَاسِ الْخَنَّاسِ* الَّذِي يُوَسْوِسُ فِي صُدُورِ النَّاسِ* مِنَ الْجِنَّةِ وَ النَّاسِ﴾",
29 "transliteration": "Recite Surah al-Ikhlas, al-Falaq and an-Nas (112, 113, 114).",
30 "english": "Al-Ikhlas (112): Say, He is Allah, [who is] One, Allah, the Eternal Refuge. He neither begets nor is born, Nor is there to Him any equivalent. \nAl-Falaq (113): Say, I seek refuge in the Lord of daybreak From the evil of that which He created And from the evil of darkness when it settles And from the evil of the blowers in knots And from the evil of an envier when he envies. \nan-Nas (114): Say, I seek refuge in the Lord of mankind, The Sovereign of mankind. The God of mankind, From the evil of the retreating whisperer - Who whispers [evil] into the breasts of mankind - From among the jinn and mankind."
31 },
32 {
33 "id": 77,
34 "repeat": 1,
35 "when": "both",
36 "arabic": "أَصْبَحْنَا وَأَصْبَحَ الْمُلْكُ لِلَّهِ ، وَالْحَمْدُ لِلَّهِ، لاَ إِلَهَ إلاَّ اللَّهُ وَحْدَهُ لاَ شَرِيكَ لَهُ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ، رَبِّ أَسْأَلُكَ خَيْرَ مَا فِي هَذَا الْيَوْمِ وَخَيرَ مَا بَعْدَهُ ، وَأَعُوذُ بِكَ مِنْ شَرِّ مَا فِي هَذَا الْيَوْمِ وَشَرِّ مَا بَعْدَهُ، رَبِّ أَعُوذُ بِكَ مِنَ الْكَسَلِ وَسُوءِ الْكِبَرِ، رَبِّ أَعُوذُ بِكَ مِنْ عَذَابٍ فِي النَّارِ وَعَذَابٍ فِي الْقَبْرِ",
37 "transliteration": "Asbahna wa-asbahal-mulku lillah walhamdu lillah la ilaha illal-lah, wahdahu la shareeka lah, lahul-mulku walahul-hamd, wahuwa ʿala kulli shayin qadeer, rabbi as-aluka khayra ma fee hathal-yawm, wakhayra ma baʿdah, wa-aʿoothu bika min sharri hathal-yawm, washarri ma baʿdah, rabbi aʿoothu bika minal-kasal, wasoo-il kibar, rabbi aʿoothu bika min ʿathabin fin-nar, waʿathabin fil-qabr.",
38 "english": "We have reached the morning and at this very time unto Allah, belongs all sovereignty , and all praise is for Allah. None has the right to be worshipped except Allah, alone, without any partner, to Him belong all sovereignty and praise and He is over all things omnipotent. My Lord, I ask You for the good of this day and the good of what follows it and I take refuge in You from the evil of this day and the evil of what follows it. My Lord, I take refuge in You from laziness and senility. My Lord, I take refuge in You from torment in the Fire and punishment in the grave.",
39 "note": "In the evening, say \"Amsayna wa amsal-mulku lillah\" and ask for the good of \"hadhihil-laylah\" (this night)."
40 },
41 {
42 "id": 78,
43 "repeat": 1,
44 "when": "both",
45 "arabic": "اللَّهُمَّ بِكَ أَصْبَحْنَا، وَبِكَ أَمْسَيْنَا ، وَبِكَ نَحْيَا، وَبِكَ نَمُوتُ وَإِلَيْكَ النُّشُورُ",
46 "transliteration": "Allahumma bika asbahna wabika amsayna, wabika nahya ,wabika namootu wa-ilaykan-nushoor.",
47 "english": "O Allah, by your leave we have reached the morning and by Your leave we have reached the evening, by Your leave we live and die and unto You is our resurrection.",
48 "evening": {
49 "transliteration": "Allahumma bika amsayna, wabika asbahna, wabika nahya wabika namootu wa-ilaykal-maseer.",
50 "english": "O Allah, by Your leave we have reached the evening and by Your leave we have reached the morning, by Your leave we live and die and unto You is our return."
51 }
52 },
53 {
54 "id": 79,
55 "repeat": 1,
56 "when": "both",
57 "arabic": "اللَّهُمَّ أَنْتَ رَبِّي لَا إِلَهَ إِلاَّ أَنْتَ، خَلَقْتَنِي وَأَنَا عَبْدُكَ، وَأَنَا عَلَى عَهْدِكَ وَوَعْدِكَ مَا اسْتَطَعْتُ، أَعُوذُ بِكَ مِنْ شَرِّ مَا صَنَعْتُ، أَبُوءُ لَكَ بِنِعْمَتِكَ عَلَيَّ، وَأَبُوءُ بِذَنْبِي فَاغْفِرْ لِي فَإِنَّهُ لاَ يَغْفِرُ الذُّنوبَ إِلاَّ أَنْتَ",
58 "transliteration": "Allahumma anta rabbee la ilaha illa ant, khalaqtanee wa-ana ʿabduk, wa-ana ʿala ʿahdika wawaʿdika mas-tataʿt, aʿoothu bika min sharri ma sanaʿt, aboo-o laka biniʿmatika ʿalay, wa-aboo-o bithanbee, faghfir lee fa-innahu la yaghfiruth-thunooba illa ant.",
59 "english": "O Allah, You are my Lord, none has the right to be worshipped except You, You created me and I am Your servant and I abide to Your covenant and promise as best I can, I take refuge in You from the evil of which I have committed. I acknowledge Your favour upon me and I acknowledge my sin, so forgive me, for verily none can forgive sin except You."
60 },
61 {
62 "id": 80,
63 "repeat": 4,
64 "when": "both",
65 "arabic": "اللَّهُمَّ إِنِّي أَصْبَحْتُ أُشْهِدُكَ، وَأُشْهِدُ حَمَلَةَ عَرْشِكَ، وَمَلاَئِكَتِكَ، وَجَمِيعَ خَلْقِكَ، أَنَّكَ أَنْتَ اللَّهُ لَا إِلَهَ إِلاَّ أَنْتَ وَحْدَكَ لاَ شَرِيكَ لَكَ، وَأَنَّ مُحَمَّداً عَبْدُكَ وَرَسُولُكَ",
66 "transliteration": "Allahumma innee asbahtu oshhiduk, wa-oshhidu hamalata ʿarshik, wamala-ikatak, wajameeʿa khalqik, annaka antal-lahu la ilaha illa ant, wahdaka la shareeka lak, wa-anna Muhammadan ʿabduka warasooluk. instead of (asbahtu).",
67 "english": "O Allah, verily I have reached the morning and call on You, the bearers of Your throne, Your angels, and all of Your creation to witness that You are Allah, none has the right to be worshipped except You, alone, without partner and that Muhammad is Your Servant and Messenger. instead of (asbahtu).",
68 "note": "In the evening, say \"amsaytu\" instead of \"asbahtu\"."
69 },
70 {
71 "id": 81,
72 "repeat": 1,
73 "when": "both",
74 "arabic": "اللَّهُمَّ مَا أَصْبَحَ بِي مِنْ نِعْمَةٍ أَوْ بِأَحَدٍ مِنْ خَلْقِكَ فَمِنْكَ وَحْدَكَ لاَ شَرِيكَ لَكَ، فَلَكَ الْحَمْدُ وَلَكَ الشُّكْرُ",
75 "transliteration": "Allahumma ma asbaha bee min niʿmatin, aw bi-ahadin min khalqik, faminka wahdaka la shareeka lak, falakal-hamdu walakash-shukr.) instead of (asbaha).",
76 "english": "O Allah, what blessing I or any of Your creation have risen upon, is from You alone, without partner, so for You is all praise and unto You all thanks.",
77 "note": "In the evening, say \"amsa\" instead of \"asbaha\"."
78 },
79 {
80 "id": 82,
81 "repeat": 3,
82 "when": "both",
83 "arabic": "اللَّهُمَّ عَافِنِي فِي بَدَنِي، اللَّهُمَّ عَافِنِي فِي سَمْعِي، اللَّهُمَّ عَافِنِي فِي بَصَرِي، لاَ إِلَهَ إِلاَّ أَنْتَ. اللَّهُمَّ إِنِّي أَعُوذُ بِكَ مِنَ الْكُفْرِ، وَالفَقْرِ، وَأَعُوذُ بِكَ مِنْ عَذَابِ القَبْرِ، لاَ إِلَهَ إِلاَّ أَنْتَ",
84 "transliteration": "Allahumma ʿafinee fee badanee, allahumma ʿafinee fee samʿee, allahumma ʿafinee fee basaree, la ilaha illa ant. Allahumma innee aʿoothu bika minal-kufr, walfaqr, wa-aʿoothu bika min ʿathabil-qabr, la ilaha illa ant.",
85 "english": "O Allah, grant my body health, O Allah, grant my hearing health, O Allah, grant my sight health. None has the right to be worshipped except You. O Allah, I take refuge with You from disbelief and poverty, and I take refuge with You from the punishment of the grave. None has the right to be worshipped except You."
86 },
87 {
88 "id": 83,
89 "repeat": 7,
90 "when": "both",
91 "arabic": "حَسْبِيَ اللَّهُ لاَ إِلَهَ إِلاَّ هُوَ عَلَيهِ تَوَكَّلتُ وَهُوَ رَبُّ الْعَرْشِ الْعَظِيمِ",
92 "transliteration": "Hasbiyal-lahu la ilaha illa huwa, ʿalayhi tawakkalt, wahuwa rabbul-ʿarshil-ʿatheem",
93 "english": "Allah is Sufficient for me, none has the right to be worshipped except Him, upon Him I rely and He is Lord of the exalted throne."
94 },
95 {
96 "id": 84,
97 "repeat": 1,
98 "when": "both",
99 "arabic": "اللَّهُمَّ إِنِّي أَسْأَلُكَ الْعَفْوَ وَالْعَافِيَةَ فِي الدُّنْيَا وَالآخِرَةِ، اللَّهُمَّ إِنِّي أَسْأَلُكَ الْعَفْوَ وَالْعَافِيَةَ: فِي دِينِي وَدُنْيَايَ وَأَهْلِي، وَمَالِي، اللَّهُمَّ اسْتُرْ عَوْرَاتِي، وَآمِنْ رَوْعَاتِي، اللَّهُمَّ احْفَظْنِي مِنْ بَينِ يَدَيَّ، وَمِنْ خَلْفِي، وَعَنْ يَمِينِي، وَعَنْ شِمَالِي، وَمِنْ فَوْقِي، وَأَعُوذُ بِعَظَمَتِكَ أَنْ أُغْتَالَ مِنْ تَحْتِي",
100 "transliteration": "Allahumma innee as-alukal-ʿafwa walʿafiyah, fid-dunya wal-akhirah, allahumma innee as-alukal-ʿafwa walʿafiyah fee deenee, wadunyaya wa-ahlee, wamalee, allahummas-tur ʿawratee, wa-amin rawʿatee, allahummah-fathnee min bayni yaday, wamin khalfee, waʿan yameenee, waʿan shimalee, wamin fawqee, wa-aʿoothu biʿathamatika an oghtala min tahtee.",
101 "english": "O Allah, I ask You for pardon and well-being in this life and the next. O Allah, I ask You for pardon and well-being in my religious and worldly affairs, and my family and my wealth. O Allah, veil my weaknesses and set at ease my dismay. O Allah, preserve me from the front and from behind and on my right and on my left and from above, and I take refuge with You lest I be swallowed up by the earth."
102 },
103 {
104 "id": 85,
105 "repeat": 1,
106 "when": "both",
107 "arabic": "اللَّهُمَّ عَالِمَ الغَيْبِ وَالشَّهَادَةِ فَاطِرَ السَّمَوَاتِ وَالْأَرْضِ، رَبَّ كُلِّ شَيْءٍ وَمَلِيكَهُ، أَشْهَدُ أَنْ لاَ إِلَهَ إِلاَّ أَنْتَ، أَعُوذُ بِكَ مِنْ شَرِّ نَفْسِي، وَمِنْ شَرِّ الشَّيْطانِ وَشَرَكِهِ، وَأَنْ أَقْتَرِفَ عَلَى نَفْسِي سُوءاً، أَوْ أَجُرَّهُ إِلَى مُسْلِمٍ",
108 "transliteration": "Allahumma ʿalimal-ghaybi washshahadah, fatiras-samawati wal-ard, rabba kulli shayin wamaleekah, ashhadu an la ilaha illa ant, aʿoothu bika min sharri nafsee wamin sharrish-shaytani washirkih, waan aqtarifa ʿala nafsee soo-an aw ajurrahu ila muslim.",
109 "english": "O Allah, Knower of the unseen and the seen, Creator of the heavens and the Earth, Lord and Sovereign of all things, I bear witness that none has the right to be worshipped except You. I take refuge in You from the evil of my soul and from the evil and shirk of the devil, and from committing wrong against my soul or bringing such upon another Muslim."
110 },
111 {
112 "id": 86,
113 "repeat": 3,
114 "when": "both",
115 "arabic": "بِسْمِ اللَّهِ الَّذِي لاَ يَضُرُّ مَعَ اسْمِهِ شَيْءٌ فِي الْأَرْضِ وَلاَ فِي السّمَاءِ وَهُوَ السَّمِيعُ الْعَلِيمُ",
116 "transliteration": "Bismil-lahil-lathee la yadurru maʿas-mihi shay-on fil-ardi wala fis-sama-i wahuwas-sameeʿul-ʿaleem.",
117 "english": "In the name of Allah with whose name nothing is harmed on earth nor in the heavens and He is The All-Seeing, The All-Knowing."
118 },
119 {
120 "id": 87,
121 "repeat": 3,
122 "when": "both",
123 "arabic": "رَضِيتُ بِاللَّهِ رَبَّاً، وَبِالْإِسْلاَمِ دِيناً، وَبِمُحَمَّدٍ صلى الله عليه وسلم نَبِيّاً",
124 "transliteration": "Radeetu billahi rabban wabil-islami deenan wabiMuhammadin nabiyya.",
125 "english": "I am pleased with Allah as a Lord, and Islam as a religion and Muhammad as a Prophet."
126 },
127 {
128 "id": 88,
129 "repeat": 1,
130 "when": "both",
131 "arabic": "يَا حَيُّ يَا قَيُّومُ بِرَحْمَتِكَ أَسْتَغيثُ أَصْلِحْ لِي شَأْنِيَ كُلَّهُ وَلاَ تَكِلْنِي إِلَى نَفْسِي طَرْفَةَ عَيْنٍ",
132 "transliteration": "Ya hayyu ya qayyoom, birahmatika astagheeth, aslih lee sha/nee kullah, wala takilnee ila nafsee tarfata ʿayn.",
133 "english": "O Ever Living, O Self-Subsisting and Supporter of all, by Your mercy I seek assistance, rectify for me all of my affairs and do not leave me to myself, even for the blink of an eye."
134 },
135 {
136 "id": 89,
137 "repeat": 1,
138 "when": "both",
139 "arabic": "أَصْبَحْنَا وَأَصْبَحَ الْمُلْكُ لِلَّهِ رَبِّ الْعَالَمِينَ، اللَّهُـمَّ إِنِّي أَسْأَلُكَ خَيْرَ هَذَا الْيَوْمِ:فَتْحَهُ، وَنَصْرَهُ، وَنورَهُ، وَبَرَكَتَهُ، وَهُدَاهُ، وَأَعُوذُ بِكَ مِنْ شَرِّ مَا فِيهِ وَشَرِّ مَا بَعْدَهُ",
140 "transliteration": "Asbahna wa-asbahal-mulku lillahi rabbil-ʿalameen, allahumma innee as-aluka khayra hathal-yawm, fat-hahu, wanasrahu, wanoorahu, wabarakatahu, wahudahu, wa-aʿoothu bika min sharri ma feehi, washarri ma baʿdah.",
141 "english": "We have reached the morning and at this very time all sovereignty belongs to Allah, Lord of the worlds. O Allah, I ask You for the good of this day, its triumphs and its victories, its light and its blessings and its guidance, and I take refuge in You from the evil of this day and the evil that follows it.",
142 "evening": {
143 "transliteration": "Amsayna wa-amsal-mulku lillahi rabbil-ʿalameen, allahumma innee as-aluka khayra hathihil-laylah, fat-haha, wanasraha, wanooraha, wabarakataha, wahudaha, wa-aʿoothu bika min sharri ma feeha washarri ma baʿdaha.",
144 "english": "We have reached the evening and at this very time all sovereignty belongs to Allah, Lord of the worlds. O Allah, I ask You for the good of tonight, its triumphs and its victories, its light and its blessings and its guidance, and I take refuge in You from the evil of tonight and the evil that follows it."
145 }
146 },
147 {
148 "id": 90,
149 "repeat": 1,
150 "when": "both",
151 "arabic": "أَصْبَحْنا عَلَى فِطْرَةِ الْإِسْلاَمِ، وَعَلَى كَلِمَةِ الْإِخْلاَصِ، وَعَلَى دِينِ نَبِيِّنَا مُحَمَّدٍ صلى الله عليه وسلم، وَعَلَى مِلَّةِ أَبِينَا إِبْرَاهِيمَ، حَنِيفاً مُسْلِماً وَمَا كَانَ مِنَ الْمُشرِكِينَ",
152 "transliteration": "Asbahna ʿala fitratil-islam, waʿala kalimatil-ikhlas, waʿala deeni nabiyyina Muhammad waʿala millati abeena Ibraheem, haneefan musliman wama kana minal-mushrikeen.",
153 "english": "We rise upon the fitrah of Islam, and the word of pure faith, and upon the religion of our Prophet Muhammad and the religion of our forefather Ibraheem, who was a Muslim and of true faith and was not of those who associate others with Allah.",
154 "note": "In the evening, say \"amsayna\" instead of \"asbahna\"."
155 },
156 {
157 "id": 91,
158 "repeat": 100,
159 "when": "both",
160 "arabic": "سُبْحَانَ اللَّهِ وَبِحَمْدِهِ",
161 "transliteration": "Subhanal-lahi wabihamdih.",
162 "english": "How perfect Allah is and I praise Him."
163 },
164 {
165 "id": 92,
166 "repeat": 10,
167 "when": "both",
168 "arabic": "لاَ إِلَهَ إِلاَّ اللَّهُ وَحْدَهُ لاَ شَرِيكَ لَهُ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ، وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ",
169 "transliteration": "La ilaha illal-lah, wahdahu la shareeka lah, lahul-mulku walahul-hamd, wahuwa ʿala kulli shay-in qadeer.",
170 "english": "None has the right to be worshipped except Allah, alone, without partner, to Him belongs all sovereignty and praise, and He is over all things omnipotent.",
171 "note": "Ten times, or once if pressed for time."
172 },
173 {
174 "id": 93,
175 "repeat": 100,
176 "when": "morning",
177 "arabic": "لاَ إِلَهَ إِلاَّ اللَّهُ، وَحْدَهُ لاَ شَرِيكَ لَهُ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ",
178 "transliteration": "La ilaha illal-lah, wahdahu la shareeka lah, lahul-mulku walahul-hamd, wahuwa ʿala kulli shay-in qadeer.",
179 "english": "None has the right to be worshipped except Allah, alone, without partner, to Him belongs all sovereignty and praise, and He is over all things omnipotent.",
180 "note": "Once a day."
181 },
182 {
183 "id": 94,
184 "repeat": 3,
185 "when": "both",
186 "arabic": "سُبْحَانَ اللَّهِ وَبِحَمْدِهِ: عَدَدَ خَلْقِهِ، وَرِضَا نَفْسِهِ، وَزِنَةَ عَرْشِهِ، وَمِدَادَ كَلِمَاتِهِ",
187 "transliteration": "Subhanal-lahi wabihamdih, ʿadada khalqihi warida nafsih, wazinata ʿarshih, wamidada kalimatih.",
188 "english": "How perfect Allah is and I praise Him by the number of His creation and His pleasure, and by the weight of His throne, and the ink of His words."
189 },
190 {
191 "id": 95,
192 "repeat": 1,
193 "when": "morning",
194 "arabic": "اللَّهُمَّ إِنِّي أَسْأَلُكَ عِلْماً نَافِعاً، وَرِزْقاً طَيِّباً، وَعَمَلاً مُتَقَبَّلاً",
195 "transliteration": "Allahumma innee as-aluka ʿilman nafiʿan, warizqan tayyiban, waʿamalan mutaqabbalan.",
196 "english": "O Allah, I ask You for knowledge which is beneficial and sustenance which is good, and deeds which are acceptable.",
197 "note": "Said in the morning."
198 },
199 {
200 "id": 96,
201 "repeat": 100,
202 "when": "morning",
203 "arabic": "أَسْتَغْفِرُ اللَّهَ وَأَتُوبُ إِلَيْهِ",
204 "transliteration": "Astaghfirullaaha wa 'atoobu 'ilayhi",
205 "english": "I seek Allah's forgiveness and I turn to Him in repentance.",
206 "note": "Once a day."
207 },
208 {
209 "id": 97,
210 "repeat": 3,
211 "when": "evening",
212 "arabic": "أَعُوذُ بِكَلِمَاتِ اللَّهِ التَّامَّاتِ مِنْ شَرِّ مَا خَلَقَ",
213 "transliteration": "aʿoothu bikalimatil-lahit-tammati min sharri ma khalaq.",
214 "english": "I take refuge in Allah's perfect words from the evil He has created.",
215 "note": "Said in the evening."
216 },
217 {
218 "id": 98,
219 "repeat": 10,
220 "when": "both",
221 "arabic": "اللَّهُمَّ صَلِّ وَسَلِّمْ عَلَى نَبَيِّنَا مُحَمَّدٍ",
222 "transliteration": "Allahumma salli wa sallim 'alaa Nabiyyinaa Muhammadin",
223 "english": "O Allah, send prayers and peace upon our Prophet Muhammad."
224 }
225]
226hooks/feature.ts 8 lines1import type { On, PluginOptions } from 'claude-code'
2
3/**
4 * A feature of the plugin: it adds its hooks when it is on. Each hooks session.start with
5 * `{ isInteractive: true }`, since a module may hook it only once without a matcher.
6 */
7export type Feature = (on: On, options: PluginOptions) => void
8hooks/daily-ayah/verses.ts 420 lines1// Generated from api.alquran.cloud: Uthmani script and the Sahih International translation.
2
3export type Verse = { ref: string; surah: string; meaning: string; arabic: string; english: string }
4
5export const VERSES: Verse[] = [
6 {
7 "ref": "2:45",
8 "surah": "Al-Baqara",
9 "meaning": "The Cow",
10 "arabic": "وَٱسْتَعِينُوا۟ بِٱلصَّبْرِ وَٱلصَّلَوٰةِ ۚ وَإِنَّهَا لَكَبِيرَةٌ إِلَّا عَلَى ٱلْخَٰشِعِينَ",
11 "english": "And seek help through patience and prayer, and indeed, it is difficult except for the humbly submissive [to Allah]"
12 },
13 {
14 "ref": "2:152",
15 "surah": "Al-Baqara",
16 "meaning": "The Cow",
17 "arabic": "فَٱذْكُرُونِىٓ أَذْكُرْكُمْ وَٱشْكُرُوا۟ لِى وَلَا تَكْفُرُونِ",
18 "english": "So remember Me; I will remember you. And be grateful to Me and do not deny Me."
19 },
20 {
21 "ref": "2:153",
22 "surah": "Al-Baqara",
23 "meaning": "The Cow",
24 "arabic": "يَٰٓأَيُّهَا ٱلَّذِينَ ءَامَنُوا۟ ٱسْتَعِينُوا۟ بِٱلصَّبْرِ وَٱلصَّلَوٰةِ ۚ إِنَّ ٱللَّهَ مَعَ ٱلصَّٰبِرِينَ",
25 "english": "O you who have believed, seek help through patience and prayer. Indeed, Allah is with the patient."
26 },
27 {
28 "ref": "2:186",
29 "surah": "Al-Baqara",
30 "meaning": "The Cow",
31 "arabic": "وَإِذَا سَأَلَكَ عِبَادِى عَنِّى فَإِنِّى قَرِيبٌ ۖ أُجِيبُ دَعْوَةَ ٱلدَّاعِ إِذَا دَعَانِ ۖ فَلْيَسْتَجِيبُوا۟ لِى وَلْيُؤْمِنُوا۟ بِى لَعَلَّهُمْ يَرْشُدُونَ",
32 "english": "And when My servants ask you, [O Muhammad], concerning Me - indeed I am near. I respond to the invocation of the supplicant when he calls upon Me. So let them respond to Me [by obedience] and believe in Me that they may be [rightly] guided."
33 },
34 {
35 "ref": "2:201",
36 "surah": "Al-Baqara",
37 "meaning": "The Cow",
38 "arabic": "وَمِنْهُم مَّن يَقُولُ رَبَّنَآ ءَاتِنَا فِى ٱلدُّنْيَا حَسَنَةًۭ وَفِى ٱلْءَاخِرَةِ حَسَنَةًۭ وَقِنَا عَذَابَ ٱلنَّارِ",
39 "english": "But among them is he who says, \"Our Lord, give us in this world [that which is] good and in the Hereafter [that which is] good and protect us from the punishment of the Fire.\""
40 },
41 {
42 "ref": "2:216",
43 "surah": "Al-Baqara",
44 "meaning": "The Cow",
45 "arabic": "كُتِبَ عَلَيْكُمُ ٱلْقِتَالُ وَهُوَ كُرْهٌۭ لَّكُمْ ۖ وَعَسَىٰٓ أَن تَكْرَهُوا۟ شَيْـًۭٔا وَهُوَ خَيْرٌۭ لَّكُمْ ۖ وَعَسَىٰٓ أَن تُحِبُّوا۟ شَيْـًۭٔا وَهُوَ شَرٌّۭ لَّكُمْ ۗ وَٱللَّهُ يَعْلَمُ وَأَنتُمْ لَا تَعْلَمُونَ",
46 "english": "Fighting has been enjoined upon you while it is hateful to you. But perhaps you hate a thing and it is good for you; and perhaps you love a thing and it is bad for you. And Allah Knows, while you know not."
47 },
48 {
49 "ref": "3:8",
50 "surah": "Aal-i-Imraan",
51 "meaning": "The Family of Imraan",
52 "arabic": "رَبَّنَا لَا تُزِغْ قُلُوبَنَا بَعْدَ إِذْ هَدَيْتَنَا وَهَبْ لَنَا مِن لَّدُنكَ رَحْمَةً ۚ إِنَّكَ أَنتَ ٱلْوَهَّابُ",
53 "english": "[Who say], \"Our Lord, let not our hearts deviate after You have guided us and grant us from Yourself mercy. Indeed, You are the Bestower."
54 },
55 {
56 "ref": "3:26",
57 "surah": "Aal-i-Imraan",
58 "meaning": "The Family of Imraan",
59 "arabic": "قُلِ ٱللَّهُمَّ مَٰلِكَ ٱلْمُلْكِ تُؤْتِى ٱلْمُلْكَ مَن تَشَآءُ وَتَنزِعُ ٱلْمُلْكَ مِمَّن تَشَآءُ وَتُعِزُّ مَن تَشَآءُ وَتُذِلُّ مَن تَشَآءُ ۖ بِيَدِكَ ٱلْخَيْرُ ۖ إِنَّكَ عَلَىٰ كُلِّ شَىْءٍۢ قَدِيرٌۭ",
60 "english": "Say, \"O Allah, Owner of Sovereignty, You give sovereignty to whom You will and You take sovereignty away from whom You will. You honor whom You will and You humble whom You will. In Your hand is [all] good. Indeed, You are over all things competent."
61 },
62 {
63 "ref": "3:139",
64 "surah": "Aal-i-Imraan",
65 "meaning": "The Family of Imraan",
66 "arabic": "وَلَا تَهِنُوا۟ وَلَا تَحْزَنُوا۟ وَأَنتُمُ ٱلْأَعْلَوْنَ إِن كُنتُم مُّؤْمِنِينَ",
67 "english": "So do not weaken and do not grieve, and you will be superior if you are [true] believers."
68 },
69 {
70 "ref": "3:173",
71 "surah": "Aal-i-Imraan",
72 "meaning": "The Family of Imraan",
73 "arabic": "ٱلَّذِينَ قَالَ لَهُمُ ٱلنَّاسُ إِنَّ ٱلنَّاسَ قَدْ جَمَعُوا۟ لَكُمْ فَٱخْشَوْهُمْ فَزَادَهُمْ إِيمَٰنًۭا وَقَالُوا۟ حَسْبُنَا ٱللَّهُ وَنِعْمَ ٱلْوَكِيلُ",
74 "english": "Those to whom hypocrites said, \"Indeed, the people have gathered against you, so fear them.\" But it [merely] increased them in faith, and they said, \"Sufficient for us is Allah, and [He is] the best Disposer of affairs.\""
75 },
76 {
77 "ref": "3:190",
78 "surah": "Aal-i-Imraan",
79 "meaning": "The Family of Imraan",
80 "arabic": "إِنَّ فِى خَلْقِ ٱلسَّمَٰوَٰتِ وَٱلْأَرْضِ وَٱخْتِلَٰفِ ٱلَّيْلِ وَٱلنَّهَارِ لَءَايَٰتٍۢ لِّأُو۟لِى ٱلْأَلْبَٰبِ",
81 "english": "Indeed, in the creation of the heavens and the earth and the alternation of the night and the day are signs for those of understanding."
82 },
83 {
84 "ref": "3:200",
85 "surah": "Aal-i-Imraan",
86 "meaning": "The Family of Imraan",
87 "arabic": "يَٰٓأَيُّهَا ٱلَّذِينَ ءَامَنُوا۟ ٱصْبِرُوا۟ وَصَابِرُوا۟ وَرَابِطُوا۟ وَٱتَّقُوا۟ ٱللَّهَ لَعَلَّكُمْ تُفْلِحُونَ",
88 "english": "O you who have believed, persevere and endure and remain stationed and fear Allah that you may be successful."
89 },
90 {
91 "ref": "4:28",
92 "surah": "An-Nisaa",
93 "meaning": "The Women",
94 "arabic": "يُرِيدُ ٱللَّهُ أَن يُخَفِّفَ عَنكُمْ ۚ وَخُلِقَ ٱلْإِنسَٰنُ ضَعِيفًۭا",
95 "english": "And Allah wants to lighten for you [your difficulties]; and mankind was created weak."
96 },
97 {
98 "ref": "6:162",
99 "surah": "Al-An'aam",
100 "meaning": "The Cattle",
101 "arabic": "قُلْ إِنَّ صَلَاتِى وَنُسُكِى وَمَحْيَاىَ وَمَمَاتِى لِلَّهِ رَبِّ ٱلْعَٰلَمِينَ",
102 "english": "Say, \"Indeed, my prayer, my rites of sacrifice, my living and my dying are for Allah, Lord of the worlds."
103 },
104 {
105 "ref": "7:23",
106 "surah": "Al-A'raaf",
107 "meaning": "The Heights",
108 "arabic": "قَالَا رَبَّنَا ظَلَمْنَآ أَنفُسَنَا وَإِن لَّمْ تَغْفِرْ لَنَا وَتَرْحَمْنَا لَنَكُونَنَّ مِنَ ٱلْخَٰسِرِينَ",
109 "english": "They said, \"Our Lord, we have wronged ourselves, and if You do not forgive us and have mercy upon us, we will surely be among the losers.\""
110 },
111 {
112 "ref": "7:56",
113 "surah": "Al-A'raaf",
114 "meaning": "The Heights",
115 "arabic": "وَلَا تُفْسِدُوا۟ فِى ٱلْأَرْضِ بَعْدَ إِصْلَٰحِهَا وَٱدْعُوهُ خَوْفًۭا وَطَمَعًا ۚ إِنَّ رَحْمَتَ ٱللَّهِ قَرِيبٌۭ مِّنَ ٱلْمُحْسِنِينَ",
116 "english": "And cause not corruption upon the earth after its reformation. And invoke Him in fear and aspiration. Indeed, the mercy of Allah is near to the doers of good."
117 },
118 {
119 "ref": "8:46",
120 "surah": "Al-Anfaal",
121 "meaning": "The Spoils of War",
122 "arabic": "وَأَطِيعُوا۟ ٱللَّهَ وَرَسُولَهُۥ وَلَا تَنَٰزَعُوا۟ فَتَفْشَلُوا۟ وَتَذْهَبَ رِيحُكُمْ ۖ وَٱصْبِرُوٓا۟ ۚ إِنَّ ٱللَّهَ مَعَ ٱلصَّٰبِرِينَ",
123 "english": "And obey Allah and His Messenger, and do not dispute and [thus] lose courage and [then] your strength would depart; and be patient. Indeed, Allah is with the patient."
124 },
125 {
126 "ref": "9:51",
127 "surah": "At-Tawba",
128 "meaning": "The Repentance",
129 "arabic": "قُل لَّن يُصِيبَنَآ إِلَّا مَا كَتَبَ ٱللَّهُ لَنَا هُوَ مَوْلَىٰنَا ۚ وَعَلَى ٱللَّهِ فَلْيَتَوَكَّلِ ٱلْمُؤْمِنُونَ",
130 "english": "Say, \"Never will we be struck except by what Allah has decreed for us; He is our protector.\" And upon Allah let the believers rely."
131 },
132 {
133 "ref": "10:62",
134 "surah": "Yunus",
135 "meaning": "Jonas",
136 "arabic": "أَلَآ إِنَّ أَوْلِيَآءَ ٱللَّهِ لَا خَوْفٌ عَلَيْهِمْ وَلَا هُمْ يَحْزَنُونَ",
137 "english": "Unquestionably, [for] the allies of Allah there will be no fear concerning them, nor will they grieve"
138 },
139 {
140 "ref": "12:87",
141 "surah": "Yusuf",
142 "meaning": "Joseph",
143 "arabic": "يَٰبَنِىَّ ٱذْهَبُوا۟ فَتَحَسَّسُوا۟ مِن يُوسُفَ وَأَخِيهِ وَلَا تَا۟يْـَٔسُوا۟ مِن رَّوْحِ ٱللَّهِ ۖ إِنَّهُۥ لَا يَا۟يْـَٔسُ مِن رَّوْحِ ٱللَّهِ إِلَّا ٱلْقَوْمُ ٱلْكَٰفِرُونَ",
144 "english": "O my sons, go and find out about Joseph and his brother and despair not of relief from Allah. Indeed, no one despairs of relief from Allah except the disbelieving people.\""
145 },
146 {
147 "ref": "13:11",
148 "surah": "Ar-Ra'd",
149 "meaning": "The Thunder",
150 "arabic": "لَهُۥ مُعَقِّبَٰتٌۭ مِّنۢ بَيْنِ يَدَيْهِ وَمِنْ خَلْفِهِۦ يَحْفَظُونَهُۥ مِنْ أَمْرِ ٱللَّهِ ۗ إِنَّ ٱللَّهَ لَا يُغَيِّرُ مَا بِقَوْمٍ حَتَّىٰ يُغَيِّرُوا۟ مَا بِأَنفُسِهِمْ ۗ وَإِذَآ أَرَادَ ٱللَّهُ بِقَوْمٍۢ سُوٓءًۭا فَلَا مَرَدَّ لَهُۥ ۚ وَمَا لَهُم مِّن دُونِهِۦ مِن وَالٍ",
151 "english": "For each one are successive [angels] before and behind him who protect him by the decree of Allah. Indeed, Allah will not change the condition of a people until they change what is in themselves. And when Allah intends for a people ill, there is no repelling it. And there is not for them besides Him any patron."
152 },
153 {
154 "ref": "13:28",
155 "surah": "Ar-Ra'd",
156 "meaning": "The Thunder",
157 "arabic": "ٱلَّذِينَ ءَامَنُوا۟ وَتَطْمَئِنُّ قُلُوبُهُم بِذِكْرِ ٱللَّهِ ۗ أَلَا بِذِكْرِ ٱللَّهِ تَطْمَئِنُّ ٱلْقُلُوبُ",
158 "english": "Those who have believed and whose hearts are assured by the remembrance of Allah. Unquestionably, by the remembrance of Allah hearts are assured.\""
159 },
160 {
161 "ref": "14:7",
162 "surah": "Ibrahim",
163 "meaning": "Abraham",
164 "arabic": "وَإِذْ تَأَذَّنَ رَبُّكُمْ لَئِن شَكَرْتُمْ لَأَزِيدَنَّكُمْ ۖ وَلَئِن كَفَرْتُمْ إِنَّ عَذَابِى لَشَدِيدٌۭ",
165 "english": "And [remember] when your Lord proclaimed, 'If you are grateful, I will surely increase you [in favor]; but if you deny, indeed, My punishment is severe.' \""
166 },
167 {
168 "ref": "15:99",
169 "surah": "Al-Hijr",
170 "meaning": "The Rock",
171 "arabic": "وَٱعْبُدْ رَبَّكَ حَتَّىٰ يَأْتِيَكَ ٱلْيَقِينُ",
172 "english": "And worship your Lord until there comes to you the certainty (death)."
173 },
174 {
175 "ref": "16:97",
176 "surah": "An-Nahl",
177 "meaning": "The Bee",
178 "arabic": "مَنْ عَمِلَ صَٰلِحًۭا مِّن ذَكَرٍ أَوْ أُنثَىٰ وَهُوَ مُؤْمِنٌۭ فَلَنُحْيِيَنَّهُۥ حَيَوٰةًۭ طَيِّبَةًۭ ۖ وَلَنَجْزِيَنَّهُمْ أَجْرَهُم بِأَحْسَنِ مَا كَانُوا۟ يَعْمَلُونَ",
179 "english": "Whoever does righteousness, whether male or female, while he is a believer - We will surely cause him to live a good life, and We will surely give them their reward [in the Hereafter] according to the best of what they used to do."
180 },
181 {
182 "ref": "16:128",
183 "surah": "An-Nahl",
184 "meaning": "The Bee",
185 "arabic": "إِنَّ ٱللَّهَ مَعَ ٱلَّذِينَ ٱتَّقَوا۟ وَّٱلَّذِينَ هُم مُّحْسِنُونَ",
186 "english": "Indeed, Allah is with those who fear Him and those who are doers of good."
187 },
188 {
189 "ref": "17:24",
190 "surah": "Al-Israa",
191 "meaning": "The Night Journey",
192 "arabic": "وَٱخْفِضْ لَهُمَا جَنَاحَ ٱلذُّلِّ مِنَ ٱلرَّحْمَةِ وَقُل رَّبِّ ٱرْحَمْهُمَا كَمَا رَبَّيَانِى صَغِيرًۭا",
193 "english": "And lower to them the wing of humility out of mercy and say, \"My Lord, have mercy upon them as they brought me up [when I was] small.\""
194 },
195 {
196 "ref": "17:80",
197 "surah": "Al-Israa",
198 "meaning": "The Night Journey",
199 "arabic": "وَقُل رَّبِّ أَدْخِلْنِى مُدْخَلَ صِدْقٍۢ وَأَخْرِجْنِى مُخْرَجَ صِدْقٍۢ وَٱجْعَل لِّى مِن لَّدُنكَ سُلْطَٰنًۭا نَّصِيرًۭا",
200 "english": "And say, \"My Lord, cause me to enter a sound entrance and to exit a sound exit and grant me from Yourself a supporting authority.\""
201 },
202 {
203 "ref": "18:10",
204 "surah": "Al-Kahf",
205 "meaning": "The Cave",
206 "arabic": "إِذْ أَوَى ٱلْفِتْيَةُ إِلَى ٱلْكَهْفِ فَقَالُوا۟ رَبَّنَآ ءَاتِنَا مِن لَّدُنكَ رَحْمَةًۭ وَهَيِّئْ لَنَا مِنْ أَمْرِنَا رَشَدًۭا",
207 "english": "[Mention] when the youths retreated to the cave and said, \"Our Lord, grant us from Yourself mercy and prepare for us from our affair right guidance.\""
208 },
209 {
210 "ref": "18:46",
211 "surah": "Al-Kahf",
212 "meaning": "The Cave",
213 "arabic": "ٱلْمَالُ وَٱلْبَنُونَ زِينَةُ ٱلْحَيَوٰةِ ٱلدُّنْيَا ۖ وَٱلْبَٰقِيَٰتُ ٱلصَّٰلِحَٰتُ خَيْرٌ عِندَ رَبِّكَ ثَوَابًۭا وَخَيْرٌ أَمَلًۭا",
214 "english": "Wealth and children are [but] adornment of the worldly life. But the enduring good deeds are better to your Lord for reward and better for [one's] hope."
215 },
216 {
217 "ref": "20:114",
218 "surah": "Taa-Haa",
219 "meaning": "Taa-Haa",
220 "arabic": "فَتَعَٰلَى ٱللَّهُ ٱلْمَلِكُ ٱلْحَقُّ ۗ وَلَا تَعْجَلْ بِٱلْقُرْءَانِ مِن قَبْلِ أَن يُقْضَىٰٓ إِلَيْكَ وَحْيُهُۥ ۖ وَقُل رَّبِّ زِدْنِى عِلْمًۭا",
221 "english": "So high [above all] is Allah, the Sovereign, the Truth. And, [O Muhammad], do not hasten with [recitation of] the Qur'an before its revelation is completed to you, and say, \"My Lord, increase me in knowledge.\""
222 },
223 {
224 "ref": "21:87",
225 "surah": "Al-Anbiyaa",
226 "meaning": "The Prophets",
227 "arabic": "وَذَا ٱلنُّونِ إِذ ذَّهَبَ مُغَٰضِبًۭا فَظَنَّ أَن لَّن نَّقْدِرَ عَلَيْهِ فَنَادَىٰ فِى ٱلظُّلُمَٰتِ أَن لَّآ إِلَٰهَ إِلَّآ أَنتَ سُبْحَٰنَكَ إِنِّى كُنتُ مِنَ ٱلظَّٰلِمِينَ",
228 "english": "And [mention] the man of the fish, when he went off in anger and thought that We would not decree [anything] upon him. And he called out within the darknesses, \"There is no deity except You; exalted are You. Indeed, I have been of the wrongdoers.\""
229 },
230 {
231 "ref": "23:118",
232 "surah": "Al-Muminoon",
233 "meaning": "The Believers",
234 "arabic": "وَقُل رَّبِّ ٱغْفِرْ وَٱرْحَمْ وَأَنتَ خَيْرُ ٱلرَّٰحِمِينَ",
235 "english": "And, [O Muhammad], say, \"My Lord, forgive and have mercy, and You are the best of the merciful.\""
236 },
237 {
238 "ref": "25:63",
239 "surah": "Al-Furqaan",
240 "meaning": "The Criterion",
241 "arabic": "وَعِبَادُ ٱلرَّحْمَٰنِ ٱلَّذِينَ يَمْشُونَ عَلَى ٱلْأَرْضِ هَوْنًۭا وَإِذَا خَاطَبَهُمُ ٱلْجَٰهِلُونَ قَالُوا۟ سَلَٰمًۭا",
242 "english": "And the servants of the Most Merciful are those who walk upon the earth easily, and when the ignorant address them [harshly], they say [words of] peace,"
243 },
244 {
245 "ref": "28:24",
246 "surah": "Al-Qasas",
247 "meaning": "The Stories",
248 "arabic": "فَسَقَىٰ لَهُمَا ثُمَّ تَوَلَّىٰٓ إِلَى ٱلظِّلِّ فَقَالَ رَبِّ إِنِّى لِمَآ أَنزَلْتَ إِلَىَّ مِنْ خَيْرٍۢ فَقِيرٌۭ",
249 "english": "So he watered [their flocks] for them; then he went back to the shade and said, \"My Lord, indeed I am, for whatever good You would send down to me, in need.\""
250 },
251 {
252 "ref": "29:69",
253 "surah": "Al-Ankaboot",
254 "meaning": "The Spider",
255 "arabic": "وَٱلَّذِينَ جَٰهَدُوا۟ فِينَا لَنَهْدِيَنَّهُمْ سُبُلَنَا ۚ وَإِنَّ ٱللَّهَ لَمَعَ ٱلْمُحْسِنِينَ",
256 "english": "And those who strive for Us - We will surely guide them to Our ways. And indeed, Allah is with the doers of good."
257 },
258 {
259 "ref": "31:17",
260 "surah": "Luqman",
261 "meaning": "Luqman",
262 "arabic": "يَٰبُنَىَّ أَقِمِ ٱلصَّلَوٰةَ وَأْمُرْ بِٱلْمَعْرُوفِ وَٱنْهَ عَنِ ٱلْمُنكَرِ وَٱصْبِرْ عَلَىٰ مَآ أَصَابَكَ ۖ إِنَّ ذَٰلِكَ مِنْ عَزْمِ ٱلْأُمُورِ",
263 "english": "O my son, establish prayer, enjoin what is right, forbid what is wrong, and be patient over what befalls you. Indeed, [all] that is of the matters [requiring] determination."
264 },
265 {
266 "ref": "33:41",
267 "surah": "Al-Ahzaab",
268 "meaning": "The Clans",
269 "arabic": "يَٰٓأَيُّهَا ٱلَّذِينَ ءَامَنُوا۟ ٱذْكُرُوا۟ ٱللَّهَ ذِكْرًۭا كَثِيرًۭا",
270 "english": "O you who have believed, remember Allah with much remembrance"
271 },
272 {
273 "ref": "35:15",
274 "surah": "Faatir",
275 "meaning": "The Originator",
276 "arabic": "۞ يَٰٓأَيُّهَا ٱلنَّاسُ أَنتُمُ ٱلْفُقَرَآءُ إِلَى ٱللَّهِ ۖ وَٱللَّهُ هُوَ ٱلْغَنِىُّ ٱلْحَمِيدُ",
277 "english": "O mankind, you are those in need of Allah, while Allah is the Free of need, the Praiseworthy."
278 },
279 {
280 "ref": "39:10",
281 "surah": "Az-Zumar",
282 "meaning": "The Groups",
283 "arabic": "قُلْ يَٰعِبَادِ ٱلَّذِينَ ءَامَنُوا۟ ٱتَّقُوا۟ رَبَّكُمْ ۚ لِلَّذِينَ أَحْسَنُوا۟ فِى هَٰذِهِ ٱلدُّنْيَا حَسَنَةٌۭ ۗ وَأَرْضُ ٱللَّهِ وَٰسِعَةٌ ۗ إِنَّمَا يُوَفَّى ٱلصَّٰبِرُونَ أَجْرَهُم بِغَيْرِ حِسَابٍۢ",
284 "english": "Say, \"O My servants who have believed, fear your Lord. For those who do good in this world is good, and the earth of Allah is spacious. Indeed, the patient will be given their reward without account.\""
285 },
286 {
287 "ref": "39:36",
288 "surah": "Az-Zumar",
289 "meaning": "The Groups",
290 "arabic": "أَلَيْسَ ٱللَّهُ بِكَافٍ عَبْدَهُۥ ۖ وَيُخَوِّفُونَكَ بِٱلَّذِينَ مِن دُونِهِۦ ۚ وَمَن يُضْلِلِ ٱللَّهُ فَمَا لَهُۥ مِنْ هَادٍۢ",
291 "english": "Is not Allah sufficient for His Servant [Prophet Muhammad]? And [yet], they threaten you with those [they worship] other than Him. And whoever Allah leaves astray - for him there is no guide."
292 },
293 {
294 "ref": "39:53",
295 "surah": "Az-Zumar",
296 "meaning": "The Groups",
297 "arabic": "۞ قُلْ يَٰعِبَادِىَ ٱلَّذِينَ أَسْرَفُوا۟ عَلَىٰٓ أَنفُسِهِمْ لَا تَقْنَطُوا۟ مِن رَّحْمَةِ ٱللَّهِ ۚ إِنَّ ٱللَّهَ يَغْفِرُ ٱلذُّنُوبَ جَمِيعًا ۚ إِنَّهُۥ هُوَ ٱلْغَفُورُ ٱلرَّحِيمُ",
298 "english": "Say, \"O My servants who have transgressed against themselves [by sinning], do not despair of the mercy of Allah. Indeed, Allah forgives all sins. Indeed, it is He who is the Forgiving, the Merciful.\""
299 },
300 {
301 "ref": "40:60",
302 "surah": "Ghafir",
303 "meaning": "The Forgiver",
304 "arabic": "وَقَالَ رَبُّكُمُ ٱدْعُونِىٓ أَسْتَجِبْ لَكُمْ ۚ إِنَّ ٱلَّذِينَ يَسْتَكْبِرُونَ عَنْ عِبَادَتِى سَيَدْخُلُونَ جَهَنَّمَ دَاخِرِينَ",
305 "english": "And your Lord says, \"Call upon Me; I will respond to you.\" Indeed, those who disdain My worship will enter Hell [rendered] contemptible."
306 },
307 {
308 "ref": "41:30",
309 "surah": "Fussilat",
310 "meaning": "Explained in detail",
311 "arabic": "إِنَّ ٱلَّذِينَ قَالُوا۟ رَبُّنَا ٱللَّهُ ثُمَّ ٱسْتَقَٰمُوا۟ تَتَنَزَّلُ عَلَيْهِمُ ٱلْمَلَٰٓئِكَةُ أَلَّا تَخَافُوا۟ وَلَا تَحْزَنُوا۟ وَأَبْشِرُوا۟ بِٱلْجَنَّةِ ٱلَّتِى كُنتُمْ تُوعَدُونَ",
312 "english": "Indeed, those who have said, \"Our Lord is Allah \" and then remained on a right course - the angels will descend upon them, [saying], \"Do not fear and do not grieve but receive good tidings of Paradise, which you were promised."
313 },
314 {
315 "ref": "42:30",
316 "surah": "Ash-Shura",
317 "meaning": "Consultation",
318 "arabic": "وَمَآ أَصَٰبَكُم مِّن مُّصِيبَةٍۢ فَبِمَا كَسَبَتْ أَيْدِيكُمْ وَيَعْفُوا۟ عَن كَثِيرٍۢ",
319 "english": "And whatever strikes you of disaster - it is for what your hands have earned; but He pardons much."
320 },
321 {
322 "ref": "47:7",
323 "surah": "Muhammad",
324 "meaning": "Muhammad",
325 "arabic": "يَٰٓأَيُّهَا ٱلَّذِينَ ءَامَنُوٓا۟ إِن تَنصُرُوا۟ ٱللَّهَ يَنصُرْكُمْ وَيُثَبِّتْ أَقْدَامَكُمْ",
326 "english": "O you who have believed, if you support Allah, He will support you and plant firmly your feet."
327 },
328 {
329 "ref": "49:10",
330 "surah": "Al-Hujuraat",
331 "meaning": "The Inner Apartments",
332 "arabic": "إِنَّمَا ٱلْمُؤْمِنُونَ إِخْوَةٌۭ فَأَصْلِحُوا۟ بَيْنَ أَخَوَيْكُمْ ۚ وَٱتَّقُوا۟ ٱللَّهَ لَعَلَّكُمْ تُرْحَمُونَ",
333 "english": "The believers are but brothers, so make settlement between your brothers. And fear Allah that you may receive mercy."
334 },
335 {
336 "ref": "49:13",
337 "surah": "Al-Hujuraat",
338 "meaning": "The Inner Apartments",
339 "arabic": "يَٰٓأَيُّهَا ٱلنَّاسُ إِنَّا خَلَقْنَٰكُم مِّن ذَكَرٍۢ وَأُنثَىٰ وَجَعَلْنَٰكُمْ شُعُوبًۭا وَقَبَآئِلَ لِتَعَارَفُوٓا۟ ۚ إِنَّ أَكْرَمَكُمْ عِندَ ٱللَّهِ أَتْقَىٰكُمْ ۚ إِنَّ ٱللَّهَ عَلِيمٌ خَبِيرٌۭ",
340 "english": "O mankind, indeed We have created you from male and female and made you peoples and tribes that you may know one another. Indeed, the most noble of you in the sight of Allah is the most righteous of you. Indeed, Allah is Knowing and Acquainted."
341 },
342 {
343 "ref": "50:16",
344 "surah": "Qaaf",
345 "meaning": "The letter Qaaf",
346 "arabic": "وَلَقَدْ خَلَقْنَا ٱلْإِنسَٰنَ وَنَعْلَمُ مَا تُوَسْوِسُ بِهِۦ نَفْسُهُۥ ۖ وَنَحْنُ أَقْرَبُ إِلَيْهِ مِنْ حَبْلِ ٱلْوَرِيدِ",
347 "english": "And We have already created man and know what his soul whispers to him, and We are closer to him than [his] jugular vein"
348 },
349 {
350 "ref": "51:56",
351 "surah": "Adh-Dhaariyat",
352 "meaning": "The Winnowing Winds",
353 "arabic": "وَمَا خَلَقْتُ ٱلْجِنَّ وَٱلْإِنسَ إِلَّا لِيَعْبُدُونِ",
354 "english": "And I did not create the jinn and mankind except to worship Me."
355 },
356 {
357 "ref": "55:13",
358 "surah": "Ar-Rahmaan",
359 "meaning": "The Beneficent",
360 "arabic": "فَبِأَىِّ ءَالَآءِ رَبِّكُمَا تُكَذِّبَانِ",
361 "english": "So which of the favors of your Lord would you deny?"
362 },
363 {
364 "ref": "57:4",
365 "surah": "Al-Hadid",
366 "meaning": "The Iron",
367 "arabic": "هُوَ ٱلَّذِى خَلَقَ ٱلسَّمَٰوَٰتِ وَٱلْأَرْضَ فِى سِتَّةِ أَيَّامٍۢ ثُمَّ ٱسْتَوَىٰ عَلَى ٱلْعَرْشِ ۚ يَعْلَمُ مَا يَلِجُ فِى ٱلْأَرْضِ وَمَا يَخْرُجُ مِنْهَا وَمَا يَنزِلُ مِنَ ٱلسَّمَآءِ وَمَا يَعْرُجُ فِيهَا ۖ وَهُوَ مَعَكُمْ أَيْنَ مَا كُنتُمْ ۚ وَٱللَّهُ بِمَا تَعْمَلُونَ بَصِيرٌۭ",
368 "english": "It is He who created the heavens and earth in six days and then established Himself above the Throne. He knows what penetrates into the earth and what emerges from it and what descends from the heaven and what ascends therein; and He is with you wherever you are. And Allah, of what you do, is Seeing."
369 },
370 {
371 "ref": "59:18",
372 "surah": "Al-Hashr",
373 "meaning": "The Exile",
374 "arabic": "يَٰٓأَيُّهَا ٱلَّذِينَ ءَامَنُوا۟ ٱتَّقُوا۟ ٱللَّهَ وَلْتَنظُرْ نَفْسٌۭ مَّا قَدَّمَتْ لِغَدٍۢ ۖ وَٱتَّقُوا۟ ٱللَّهَ ۚ إِنَّ ٱللَّهَ خَبِيرٌۢ بِمَا تَعْمَلُونَ",
375 "english": "O you who have believed, fear Allah. And let every soul look to what it has put forth for tomorrow - and fear Allah. Indeed, Allah is Acquainted with what you do."
376 },
377 {
378 "ref": "64:11",
379 "surah": "At-Taghaabun",
380 "meaning": "Mutual Disillusion",
381 "arabic": "مَآ أَصَابَ مِن مُّصِيبَةٍ إِلَّا بِإِذْنِ ٱللَّهِ ۗ وَمَن يُؤْمِنۢ بِٱللَّهِ يَهْدِ قَلْبَهُۥ ۚ وَٱللَّهُ بِكُلِّ شَىْءٍ عَلِيمٌۭ",
382 "english": "No disaster strikes except by permission of Allah. And whoever believes in Allah - He will guide his heart. And Allah is Knowing of all things."
383 },
384 {
385 "ref": "65:3",
386 "surah": "At-Talaaq",
387 "meaning": "Divorce",
388 "arabic": "وَيَرْزُقْهُ مِنْ حَيْثُ لَا يَحْتَسِبُ ۚ وَمَن يَتَوَكَّلْ عَلَى ٱللَّهِ فَهُوَ حَسْبُهُۥٓ ۚ إِنَّ ٱللَّهَ بَٰلِغُ أَمْرِهِۦ ۚ قَدْ جَعَلَ ٱللَّهُ لِكُلِّ شَىْءٍۢ قَدْرًۭا",
389 "english": "And will provide for him from where he does not expect. And whoever relies upon Allah - then He is sufficient for him. Indeed, Allah will accomplish His purpose. Allah has already set for everything a [decreed] extent."
390 },
391 {
392 "ref": "93:3",
393 "surah": "Ad-Dhuhaa",
394 "meaning": "The Morning Hours",
395 "arabic": "مَا وَدَّعَكَ رَبُّكَ وَمَا قَلَىٰ",
396 "english": "Your Lord has not taken leave of you, [O Muhammad], nor has He detested [you]."
397 },
398 {
399 "ref": "93:5",
400 "surah": "Ad-Dhuhaa",
401 "meaning": "The Morning Hours",
402 "arabic": "وَلَسَوْفَ يُعْطِيكَ رَبُّكَ فَتَرْضَىٰٓ",
403 "english": "And your Lord is going to give you, and you will be satisfied."
404 },
405 {
406 "ref": "94:5",
407 "surah": "Ash-Sharh",
408 "meaning": "The Consolation",
409 "arabic": "فَإِنَّ مَعَ ٱلْعُسْرِ يُسْرًا",
410 "english": "For indeed, with hardship [will be] ease."
411 },
412 {
413 "ref": "94:6",
414 "surah": "Ash-Sharh",
415 "meaning": "The Consolation",
416 "arabic": "إِنَّ مَعَ ٱلْعُسْرِ يُسْرًۭا",
417 "english": "Indeed, with hardship [will be] ease."
418 }
419]
420