SLOPSHOPPER

muslim-mods

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…

newpanebandspinnercommandtoast
★ 10v1.0.1MITupdated 2026-10-08ashafizullah/claude-code-muslim-mods/muslim-mods
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · muslim-mods
│ ┃ Muslim mods ✕ › fix the failing auth test and add an audit log call │ ┃ [ ● On ] 1 Prayer times │ ┃ 🕌 Finding your location… ⏺ Read(src/auth.ts) │ ┃ [ ● On ] 2 Hijri date ⎿ Read 6 lines │ ┃ 📅 17 Rabi' al-Akhir 1447 ⏺ Update(src/auth.ts) │ ┃ [ ● On ] 3 Adhkar ⎿ Added 2 lines, removed 1 line │ ┃ 🤲 Morning adhkar · /adhkar ⏺ Bash(bun test) │ ┃ [ ● On ] 4 Sunnah fasting ⎿ 3 pass, 1 fail │ ┃ Monday, Thursday, the white days; │ ┃ [ ● On ] 5 Ramadan ● Done. refresh now rejects expired claims and logs an audit event. │ ┃ Imsak and iftar countdown in Rama │ ┃ [ ● On ] 6 Jumu'ah ✻ Worked for 42s · done 4:20 PM │ ┃ Al-Kahf, salawat and the Friday p │ ┃ [ ● On ] 7 Daily ayah › /muslim │ ┃ A verse of the Qur'an above the p ⎿ muslim-mods: Muslim mods opened. Keys 1–8 switch each one on or │ ┃ [ ● On ] 8 Tasbih │ ┃ Dhikr when the usage limit runs o │ ┃ Settings: /config, under muslim-mods [ Clos │ 📖 "And I did not create the jinn and mankind except to worship Me." — Adh-Dhaariyat (The Winnowing Winds) 51:56 ↻ × ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
📖 "And I did not create the jinn and mankind except to worship Me." — Adh-Dhaariyat (The Winnowing Winds) 51:56 ↻ ×
Pane · Muslim mods
[ ● On ] 1 Prayer times 🕌 Finding your location… [ ● On ] 2 Hijri date 📅 17 Rabi' al-Akhir 1447 [ ● On ] 3 Adhkar 🤲 Morning adhkar · /adhkar [ ● On ] 4 Sunnah fasting Monday, Thursday, the white days; /fasting [ ● On ] 5 Ramadan Imsak and iftar countdown in Ramadan; /ramadan [ ● On ] 6 Jumu'ah Al-Kahf, salawat and the Friday prayer; /jumuah [ ● On ] 7 Daily ayah A verse of the Qur'an above the prompt; /ayah [ ● On ] 8 Tasbih Dhikr when the usage limit runs out; /tasbih Settings: /config, under muslim-mods [ Close ]
Pane · Morning adhkar
Morning adhkar · 1 of 23 [ 0 / 1 ] [ Previous ] [ Next ] [ Finish ] [ Close ] Ayat al-Kursi (Al-Baqarah 2:255), after seeking refuge in Allah from the accursed devil. 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.
Pane · Tasbih
Remember Allah while you wait. 1 of 4 · 0 / 100 [ 0 / 33 ] [ Restart ] [ Close ] SubḥānAllāh Glory be to Allah.
README

Muslim mods for Claude Code

CI License: MIT Claude Code TypeScript Features Last commit Stars

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.

FeatureWhat it doesCommand
Prayer timesCounts down to the next prayer under the prompt, reminds you before it and tells you when its time begins./prayer-times
Hijri dateShows the Hijri date under the prompt, turning over at Maghrib, and announces Ramadan, the Eids, Arafah, Ashura and the other days that matter./hijri
AdhkarReminds you of the morning and evening adhkar and opens a pane to read them, with a counter./adhkar
Sunnah fastingReminds you the evening before Monday, Thursday, the white days, Arafah and Ashura, then gives suhur and iftar reminders on the days you fast./fasting
RamadanIn 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'ahFrom 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 ayahShows a verse of the Qur'an in a band above the prompt, a new one each session./ayah
TasbihWhen 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.

Dashboard

/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.
  • A switched-off feature is not loaded at all: no hooks, no timers, no network. Switching saves the setting and reloads the plugin.
  • Every feature's part of the hint line under the prompt is joined into one line in a fixed order, with no labels.
  • Hijri date, adhkar, sunnah fasting, Ramadan and Jumu'ah read today's times from Prayer times, and sunnah fasting and Ramadan their dates from Hijri date. Switched off, what depends on them works only in part (the dashboard says so): adhkar falls back to fixed hours, and Ramadan needs both.

All features are on to start with. Every option below is in /config under muslim-mods.

Prayer times

? 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

Where you are

It works anywhere in the world. It finds your location in this order:

  1. The 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.
  2. Your IP address, if 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.

How the times are calculated

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.

  • Time zone comes from the place (Europe/London, America/New_York, …), so daylight saving time is handled.
  • Calculation method is picked from your country when method is Auto:
MethodFajr / IshaUsed for
Kemenag20° / 18°Indonesia
JAKIM20° / 18°Malaysia, Singapore, Brunei
Makkah (Umm al-Qura)18.5° / 90 min after MaghribSaudi Arabia and the Gulf
Egypt19.5° / 17.5°Egypt, the Levant, North Africa
Karachi18° / 18°Pakistan, India, Bangladesh, Afghanistan
ISNA15° / 15°United States, Canada
Diyanet18° / 17°, with Diyanet's own minute correctionsTurkey
MWL18° / 17°everywhere else
  • High latitudes: where the sun never sinks far enough for Fajr or Isha (UK and Scandinavian summers), the angle-based rule is used.
  • Ihtiyat (a safety margin) is 2 minutes for Kemenag and JAKIM, as their published timetables use, and 0 elsewhere.
  • Dhuha begins when the sun is 4.5° above the horizon, as in Kemenag's timetable (about 20 minutes after sunrise).
  • Tahajud begins with the last third of the night, the night counted from Maghrib to Fajr. Some timetables (Aladhan's "Lastthird") count it to sunrise instead, which puts it later.
  • Dhuha and Tahajud are listed and announced, and while their time lasts the hint line adds ☀️ 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.

Options

OptionDefault
cityblankA city, or lat, lng. Blank detects it from your IP.
methodAutoAuto, Kemenag, JAKIM, MWL, ISNA, Egypt, Makkah, Karachi, Diyanet
asrStandardStandard (Shafi'i, Maliki, Hanbali) or Hanafi
ihtiyatMinutes-1Minutes added to each time; -1 uses the method's own.
reminderMinutes10How early to remind you; 0 turns the early reminder off.
sunnahReminderstrueA toast when the time for Dhuha, and for Tahajud, begins, and a note on the hint line while it lasts.

Adhkar

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.

  • When: morning adhkar from Fajr until Dhuhr, evening adhkar from Asr until Isha, taken from Prayer times. With it off, adhkar falls back to 04:00–11:00 and 15:00–19:00 on your clock.
  • Reminder: a toast 15 minutes after Fajr or Asr begins (time to pray first), and 🤲 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
cCount one repetition. When a dhikr reaches its number, the pane moves on to the next.
n / pNext / previous.
fFinish: marks today's session done and closes the pane.
x / EscClose 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.

OptionDefault
reminderDelayMinutes15How long after Fajr and Asr begin to remind you.
showArabicfalseShow 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.

Jumu'ah

🌙 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).
  • Jumu'ah runs from Thursday's Maghrib to Friday's. As it begins, a toast for salawat and Al-Kahf; on Friday from Fajr, Jumu'ah Mubarak with the sunnah of the day (ghusl, clean clothes and perfume, going early). Each is said once, even across sessions.
  • 📖 Al-Kahf stays on the hint line until you run /jumuah read.
  • A reminder 45 minutes before Dhuhr to get ready for the Friday prayer, and at Asr the last hour of Friday, when dua is answered.
  • /jumuah lists the sunnah of the day with a ✓ on Al-Kahf once read; midweek, when the next Jumu'ah is.
OptionDefault
jumuahReminderMinutes45For those who attend the Friday prayer: how early to remind you; 0 turns it off.
kahftrueAl-Kahf on the hint line until you've read it.

Daily ayah

📖 "So remember Me; I will remember you. And be grateful to Me and do not deny Me."
— Al-Baqara (The Cow) 2:152  ↻ ×
  • A new verse each time Claude Code starts or you /clear, from 59 verses that read well on their own (2:152, 13:28, 39:53, 65:3, 94:5, …).
  • ↻ shows another verse; × hides the band for the rest of the session.
  • /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.

OptionDefault
showArabicfalseAlso show the Arabic in the band. Off by default because many terminals draw right-to-left text poorly; /ayah always includes it.

Hijri date

? 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
  • The Islamic day begins at sunset, so the date turns over at Maghrib, read from Prayer times (🌙 until midnight). Until Prayer times has found your place, or with it off, it turns over at midnight.
  • A toast the evening before and on the day of the Islamic New Year, Nisf Sha'ban, the start of Ramadan and its last ten nights, Eid al-Fitr and the six days of Shawwal, the first ten days of Dhu al-Hijjah, Eid al-Adha and the days of Tashriq, and on the day of Tasu'a, Ashura and Arafah. Each is said once, even across sessions. The reminders to fast the evening before are Sunnah fasting's.

/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.

OptionDefault
calendarUmm al-QuraUmm al-Qura (Saudi Arabia's) or Tabular (arithmetical).
adjustDays0Days to add or subtract, e.g. -1 or 1, to follow local sighting.
announceDaystrueThe toasts for the days above.

Sunnah fasting

🌙 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
  • The evening before (from Maghrib), a toast when tomorrow is a sunnah fast: Monday or Thursday, a white day (13th to 15th), Arafah, Tasu'a, Ashura or the first days of Dhu al-Hijjah. No reminders during Ramadan, and none for the Eids or the days of Tashriq, when fasting is forbidden.
  • /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).

OptionDefault
mondayThursdaytrueRemind you before every Monday and Thursday.
whiteDaystrueRemind you before the white days.
suhurMinutes45How long before Fajr the suhur reminder comes, here and in Ramadan; 0 turns it off.

Ramadan

? 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:

  • A countdown on the hint line to imsak before Fajr and to iftar through the day, ticking every second; on the odd nights of the last ten (21, 23, 25, 27, 29), ✨ Night 23 · seek Laylat al-Qadr after Maghrib.
  • Toasts for suhur (45 minutes before Fajr), imsak (10 minutes before), iftar at Maghrib with its dua, and Tarawih at Isha, from the night before the first fast to the night before the last. On the odd nights the Isha toast adds the dua for Laylat al-Qadr (Tirmidhi 3513).
  • A reminder of zakat al-fitr from the 27th, once a Ramadan.
  • /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.

OptionDefault
suhurMinutes45The same option as sunnah fasting's.
imsakMinutes10When imsak is, before Fajr; 0 turns the imsak reminder off.
tarawihtrueThe toast at Isha.

Tasbih

📿 5-hour limit reached · back in 1h 42m · /tasbih
  • When the 5-hour or weekly usage limit is used up, a toast invites you to dhikr and the Tasbih pane opens (on a terminal 144 columns or wider; otherwise run /tasbih).
  • The pane counts SubhanAllah ×33, Alhamdulillah ×33, Allahu akbar ×33 and the tahlil once, a hundred in all (Sahih Muslim 597). Keys: c count, r restart, x close.
  • The hint under the prompt says when the limit comes back, and clears itself once it has.
  • Each used-up window invites you once; a new session in the same window keeps only the hint. /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.

OptionDefault
showArabicfalseAlso show the Arabic in the pane (one option for adhkar, the daily ayah and the tasbih).

Install

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).

From the marketplace

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.

From a folder

The steps below load the plugin from a clone instead, which a git pull updates.

1. Clone the repository

git clone https://github.com/ashafizullah/claude-code-muslim-mods.git ~/claude-code-muslim-mods

2. Load the plugin

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"
  }
}
  • To load other plugins too, separate the paths with : on macOS and Linux, ; on Windows. Absolute paths and ~ both work.
  • Only the user settings file is read for this. A project's .claude/settings.json is ignored.
  • Merge the line into your existing env block rather than replacing the file.
  • Restart Claude Code afterwards. Sessions that are already open don't pick it up.

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.

3. Check that it loaded

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.

4. Set your options

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.

Updating

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.

Troubleshooting

  • Nothing shows up in a new terminal: the folder isn't in 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.
  • It is missing from claude plugin list: check the path, then run claude plugin validate ~/claude-code-muslim-mods/muslim-mods.
  • A feature does nothing: check it is on in /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.
  • **Arabic shows as sep
Source 17 files
hooks/register.tsx 204 lines
1import { 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}
204
hooks/adhkar/index.tsx 257 lines
1import { 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}
257
hooks/daily-ayah/index.tsx 80 lines
1import { 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}
80
hooks/hijri-date/index.ts 136 lines
1import { 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}
136
hooks/jumuah/index.ts 186 lines
1import { 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}
186
hooks/prayer-times/index.ts 249 lines
1import { 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}
249
hooks/ramadan/index.ts 216 lines
1import { 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}
216
hooks/sunnah-fasting/index.ts 205 lines
1import { 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}
205
hooks/tasbih/index.tsx 222 lines
1import { 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}
222
hooks/adhkar/adhkar.ts 226 lines
1// 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]
226
hooks/feature.ts 8 lines
1import 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
8
hooks/daily-ayah/verses.ts 420 lines
1// 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