SLOPSHOPPER

daily-challenge

One small Claude Code exercise a day, with your streak and a year of 365 challenges that never repeat.

newpanecommandtimer
v0.1.0MITupdated 2026-10-14hearsawho25/roberts-mods/daily-challenge
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · daily-challenge
│ ┃ Daily challenge ✕ › fix the failing auth test and add an audit log call │ ┃ DAILY CHALLENGE Th │ ┃ ━━━━━━━━━━━━━━━───────────────────────────── ⏺ Read(src/auth.ts) │ ┃ ⎿ Read 6 lines │ ┃ ╭─────────────────────────────────────────── ⏺ Update(src/auth.ts) │ ┃ │ ┏━┓ ⎿ Added 2 lines, removed 1 line │ ┃ │ ┃ ┃ ⏺ Bash(bun test) │ ┃ │ ┗━┛ ⎿ 3 pass, 1 fail │ ┃ │ DAY STREAK │ ┃ ╰─────────────────────────────────────────── ● Done. refresh now rejects expired claims and logs an audit event. │ ┃ ╭─────────────────────────────────────────── │ ┃ │ ┏━┓ ✻ Worked for 42s · done 4:20 PM │ ┃ │ ┃ ┃ │ ┃ │ ┗━┛ › /challenge │ ┃ │ COMPLETED / 365 ⎿ daily-challenge: Today's challenge (waiting for you): Lead with │ ┃ ╰─────────────────────────────────────────── │ ┃ ╭─────────────────────────────────────────── │ ┃ │ ┏━┓ │ ┃ │ ┃ ┃ │ ┃ │ ┗━┛ │ ┃ │ BEST STREAK │ ┃ ╰─────────────────────────────────────────── │ ┃ │ ┃ TODAY'S CHALLENGE ────────────────────────── │ ┃ ╭─────────────────────────────────────────── │ ┃ │ PROMPTING ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Pane · Daily challenge
DAILY CHALLENGE Thu 9 Oct 2025 ━━━━━━━━━━━━━━━───────────────────────────────────────── ╭──────────────────────────────────────────────────────╮ │ ┏━┓ │ │ ┃ ┃ │ │ ┗━┛ │ │ DAY STREAK │ ╰──────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────╮ │ ┏━┓ │ │ ┃ ┃ │ │ ┗━┛ │ │ COMPLETED / 365 │ ╰──────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────╮ │ ┏━┓ │ │ ┃ ┃ │ │ ┗━┛ │ │ BEST STREAK │ ╰──────────────────────────────────────────────────────╯ TODAY'S CHALLENGE ────────────────────────────────────── ╭──────────────────────────────────────────────────────╮ │ PROMPTING │ │ Lead with the goal │ │ Ask Claude for a packing list for a weekend away, │ │ and start your message with what the trip is for. │ │ Notice how stating the purpose first shapes the │ │ list. │ ╰──────────────────────────────────────────────────────╯ complete coach another LAST 14 DAYS ─────────────────────────────────────────── □ □ □ □ □ □ □ □ □ □ □ □ □ □ Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th YOUR YEAR ────────────────────────────────────────────── ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0 done 365 to go If a command is not on your version, ask Claude how to do it.
README

roberts-mods

A small collection of Claude Code mods (hooks-module plugins), shared as a plugin marketplace. Use any of them, change them, pass them on: they're MIT-licensed.

Install

claude plugin marketplace add hearsawho25/roberts-mods
claude plugin install <mod>@roberts-mods

Install as many as you like, then run /reload-plugins or start a new session.

The mods

ModCommandWhat it does
roberts-toolbox/toolboxOne button at the end of the prompt hint line that opens a menu of your mods and the commands that change Claude Code.
clean-view`/simple on\off`Hides tool calls, diffs and command output, and shows one plain-English checklist above the prompt instead.
prompt-upgrade/betterTurns a rough prompt into a better one in a pane: up to three quick questions, a rewrite, and a side-by-side review before anything is sent.
daily-challenge/challengeOne small Claude Code exercise a day, with a streak and a year of challenges that never repeat.
work-report/reportCounts the tasks Claude finished for you and estimates the hours and dollars saved, by day, week, month and year.
copy-blocks/copyblock [n]A Copy button under every code block in Claude's replies, so you get clean text without the frame.
relay/relay, /handoff, /keepwarmA context and prompt-cache meter above the prompt, one-click handoffs to a fresh session, and cache warming with a cold-cache guard. See relay/README.md.
git-ship/shipRepo status above the prompt, and a pane whose buttons fill ready-to-run git and gh commands (commit messages and PRs drafted for you). It never runs a write: you press Enter. See git-ship/README.md.
mod-doctor/mod-doctorChecks that every enabled mod actually loaded. Stays quiet until one didn't, then shows a red row and the reason from claude plugin validate. See mod-doctor/README.md.

Credits

Several of these started as other people's ideas, and they deserve the credit:

  • Clean View, Prompt Upgrade, Daily Challenge and Work Report were built from prompts by Tristen O'Brien (AI Made Easy). The ideas are Tristen's. These are my builds of them.
  • Robert's Toolbox is based on a toolbox Tristen O'Brien showed in a video (tristenobrien.ai).
  • Relay's price table is adapted from cache-tax by Karan Bansal (MIT License).

The mods themselves were built with Claude Code.

License

MIT.

Source 5 files
hooks/register.tsx 363 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Progress } from '../types'
5import { TOTAL, themeOf } from './challenges'
6import {
7  COLORS,
8  TILE_LABELS,
9  TITLE,
10  bigNumber,
11  chipRows,
12  dayGrid,
13  dayLabel,
14  fit,
15  heading,
16  tileLayout,
17  titleRule,
18  wrap,
19  yearBar,
20} from './layout'
21import {
22  bestStreak,
23  canSwap,
24  coachPrompt,
25  completeToday,
26  dayKey,
27  doneCount,
28  formatDay,
29  lastDays,
30  normalize,
31  streak,
32  swapToday,
33  todayOf,
34  undoToday,
35} from './logic'
36
37const PANE = 'daily-challenge'
38const PANE_TITLE = 'Daily challenge'
39const STORE_KEY = 'progress'
40const FOOTNOTE = 'If a command is not on your version, ask Claude how to do it.'
41const FAILED = 'Daily challenge could not do that just now. Please try again in a moment.'
42
43const progressAtom = atom({ plugin: 'daily-challenge', key: 'progress' } as const, null)
44
45/** The session's copy if there is one, else what the store kept. A read only. */
46async function readProgress($: EngineInterface): Promise<Progress> {
47  const held = await read($, progressAtom)
48  if (held !== null) return held
49  return normalize(await $.store.get(STORE_KEY))
50}
51
52async function today($: EngineInterface): Promise<string> {
53  return dayKey(await $.clock.now())
54}
55
56/** Applies one change to the progress and keeps it in the store; null when nothing changed. */
57async function change(
58  $: EngineInterface,
59  apply: (progress: Progress, day: string) => Progress | null,
60): Promise<{ progress: Progress; day: string; isChanged: boolean }> {
61  const day = await today($)
62  const stored = await readProgress($)
63  const outcome = { isChanged: false }
64  const progress = await update($, progressAtom, held => {
65    const base = held ?? stored
66    const next = apply(base, day)
67    outcome.isChanged = next !== null
68    return next ?? base
69  })
70  const settled = progress ?? stored
71  if (outcome.isChanged) await $.store.set(STORE_KEY, settled)
72  return { progress: settled, day, isChanged: outcome.isChanged }
73}
74
75const complete = ($: EngineInterface) => change($, completeToday)
76const undo = ($: EngineInterface) => change($, undoToday)
77const swap = ($: EngineInterface) =>
78  change($, (progress, day) => swapToday(progress, day, Math.random))
79
80async function openPane($: EngineInterface): Promise<void> {
81  await $.ui.open({ id: PANE, title: PANE_TITLE })
82}
83
84async function coach($: EngineInterface): Promise<void> {
85  const progress = await readProgress($)
86  const now = todayOf(progress, await today($))
87  if (now.challenge === undefined) return
88  await $.ui.close({ id: PANE })
89  await $.prompt.fill({ text: coachPrompt(now.challenge) })
90}
91
92function quietly(work: Promise<unknown>): void {
93  work.catch(() => undefined)
94}
95
96function tally(progress: Progress, day: string): string {
97  const days = streak(progress, day)
98  return `Streak: ${days} ${days === 1 ? 'day' : 'days'}. Completed: ${doneCount(progress)} of ${TOTAL}.`
99}
100
101async function runCommand($: EngineInterface, args: string): Promise<string> {
102  const word = args.trim().toLowerCase()
103
104  if (word === '' || word === 'open') {
105    const progress = await readProgress($)
106    const day = await today($)
107    const now = todayOf(progress, day)
108    quietly(openPane($))
109    if (now.challenge === undefined) return `All ${TOTAL} challenges are complete. ${tally(progress, day)}`
110    const state = now.isDone ? 'done for today' : 'waiting for you'
111    return `Today's challenge (${state}): ${now.challenge.title}. ${tally(progress, day)}`
112  }
113
114  if (word === 'done' || word === 'complete') {
115    const before = todayOf(await readProgress($), await today($))
116    const { progress, day, isChanged } = await complete($)
117    if (isChanged && before.challenge !== undefined) {
118      return `Marked complete: ${before.challenge.title}. ${tally(progress, day)}`
119    }
120    if (before.isDone && before.challenge !== undefined) {
121      return `You already completed today's challenge (${before.challenge.title}). ${tally(progress, day)}`
122    }
123    return `All ${TOTAL} challenges are complete. ${tally(progress, day)}`
124  }
125
126  if (word === 'another' || word === 'swap') {
127    const { progress, day, isChanged } = await swap($)
128    const now = todayOf(progress, day)
129    if (isChanged && now.challenge !== undefined) return `Swapped. Today's challenge is now: ${now.challenge.title}.`
130    if (now.isDone) return `Today's challenge is already complete. A new one is waiting tomorrow.`
131    return `There is no other challenge left to swap to.`
132  }
133
134  return `Try /challenge to open the pane, /challenge done to mark today's complete, or /challenge another to swap it.`
135}
136
137export const register: Register = on => {
138  let shownDay = ''
139
140  on('session.start', async ($, e, next) => {
141    await $.command.register({
142      name: 'challenge',
143      description: 'Open your daily Claude Code challenge',
144      argumentHint: '[done | another]',
145    })
146    try {
147      const stored = normalize(await $.store.get(STORE_KEY))
148      await update($, progressAtom, held => held ?? stored)
149      shownDay = await today($)
150      $.clock.every(60_000, () => {
151        quietly(
152          today($).then(day => {
153            if (day === shownDay) return
154            shownDay = day
155            $.ui.invalidate('ui.render')
156          }),
157        )
158      })
159    } catch {
160      // The pane falls back to the store on its own; nothing here needs the person.
161    }
162
163    return next(e)
164  })
165
166  on('command.run', { command: 'challenge' }, async ($, e) => {
167    try {
168      return { text: await runCommand($, e.args) }
169    } catch {
170      return { text: FAILED }
171    }
172  }).catch(() => ({ text: FAILED }))
173
174  on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
175    const { Box, Button, Text } = $.ui.resolve(e)
176    const progress = await readProgress($)
177    const day = await today($)
178    const cols = Math.max(16, Math.floor(e.props.bodyColumns))
179    const now = todayOf(progress, day)
180    const done = doneCount(progress)
181
182    const headingRow = (label: string) => {
183      const parts = heading(label, cols)
184      return (
185        <Text wrap="truncate">
186          <Text color={COLORS.muted} bold>{parts.label}</Text>
187          <Text color={COLORS.line}>{parts.hairline}</Text>
188        </Text>
189      )
190    }
191
192    const rule = titleRule(cols)
193    const longDate = formatDay(day)
194    const date = TITLE.length + 1 + longDate.length <= cols ? longDate : formatDay(day, true)
195    const showDate = TITLE.length + 1 + date.length <= cols
196
197    const tiles = tileLayout(cols)
198    const tileValues = [streak(progress, day), done, bestStreak(progress)]
199    const tileInner = tiles.width - 4
200
201    const cardInner = cols - 4
202    const card = now.challenge
203    const theme = card === undefined ? undefined : themeOf(card)
204    const themeName = card === undefined ? 'Finished' : card.isCloser ? `${theme?.name} · Closer` : (theme?.name ?? '')
205    const cardTitle = card === undefined ? `All ${TOTAL} complete` : card.title
206    const cardTask =
207      card === undefined
208        ? 'You finished every challenge in the year. Thank you for practising with Claude Code.'
209        : card.task
210    const isGreen = now.isDone || card === undefined
211
212    const chips = chipRows(
213      [
214        { key: 'complete', label: 'Mark complete' },
215        { key: 'coach', label: 'Coach me through it' },
216        ...(canSwap(progress, day) ? [{ key: 'another', label: 'Give me another' }] : []),
217      ],
218      cols,
219    )
220
221    const grid = dayGrid(cols)
222    const cells = lastDays(progress, day, grid.count)
223    const bar = yearBar(done, TOTAL, cols)
224    const doneText = `${done} done`
225    const leftText = `${TOTAL - done} to go`
226
227    const chip = (key: string, label: string, isMain: boolean) => (
228      <Button key={key} plain onPress={() => quietly(press($, key))}>
229        <Text
230          backgroundColor={isMain ? COLORS.gold : COLORS.line}
231          color={isMain ? COLORS.ink : COLORS.text}
232          bold={isMain}
233        >
234          {` ${label} `}
235        </Text>
236      </Button>
237    )
238
239    return (
240      <Box flexDirection="column" width={cols}>
241        <Box flexDirection="row" justifyContent="space-between" width={cols}>
242          <Text bold color={COLORS.text} wrap="truncate">{fit(TITLE, cols)}</Text>
243          {showDate && <Text color={COLORS.muted}>{date}</Text>}
244        </Box>
245        <Text wrap="truncate">
246          <Text color={COLORS.gold}>{rule.gold}</Text>
247          <Text color={COLORS.line}>{rule.hairline}</Text>
248        </Text>
249
250        <Box flexDirection={tiles.direction} columnGap={1} marginTop={1}>
251          {TILE_LABELS.map((label, index) => {
252            const rows = bigNumber(tileValues[index] ?? 0)
253            return (
254              <Box
255                key={`tile-${index}`}
256                borderStyle="round"
257                borderColor={COLORS.line}
258                paddingX={1}
259                width={tiles.width}
260                flexDirection="column"
261                alignItems="center"
262              >
263                {rows.map(row => (
264                  <Text color={index === 0 ? COLORS.gold : COLORS.text}>{fit(row, tileInner)}</Text>
265                ))}
266                <Text color={COLORS.muted}>{fit(label, tileInner)}</Text>
267              </Box>
268            )
269          })}
270        </Box>
271
272        <Box marginTop={1} flexDirection="column">
273          {headingRow("Today's challenge")}
274        </Box>
275        <Box
276          borderStyle="round"
277          borderColor={isGreen ? COLORS.green : COLORS.gold}
278          paddingX={1}
279          flexDirection="column"
280          width={cols}
281        >
282          <Text color={card === undefined ? COLORS.green : (theme?.color ?? COLORS.text)}>
283            {fit(themeName.toUpperCase(), cardInner)}
284          </Text>
285          {wrap(cardTitle, cardInner).map(line => (
286            <Text bold color={COLORS.text}>{line}</Text>
287          ))}
288          {wrap(cardTask, cardInner).map(line => (
289            <Text color={COLORS.text}>{line}</Text>
290          ))}
291        </Box>
292
293        {card !== undefined && !now.isDone && (
294          <Box flexDirection="column" marginTop={1}>
295            {chips.map(row => (
296              <Box flexDirection="row" columnGap={1}>
297                {row.map(one => chip(one.key, one.label, one.key === 'complete'))}
298              </Box>
299            ))}
300          </Box>
301        )}
302
303        {now.isDone && (
304          <Box flexDirection="column" marginTop={1}>
305            <Text bold color={COLORS.green}>{fit('Completed today', cols)}</Text>
306            {wrap('A new challenge is waiting for you tomorrow.', cols).map(line => (
307              <Text color={COLORS.text}>{line}</Text>
308            ))}
309            <Box>
310              <Button key="undo" plain dimColor onPress={() => quietly(press($, 'undo'))}>
311                <Text color={COLORS.muted}>Undo</Text>
312              </Button>
313            </Box>
314          </Box>
315        )}
316
317        <Box marginTop={1} flexDirection="column">
318          {headingRow("Last 14 days")}
319          <Text>
320            {cells.map(cell => (
321              <Text color={cell.isDone ? COLORS.green : COLORS.line}>
322                {(cell.isDone ? '■' : '□').padEnd(grid.cell)}
323              </Text>
324            ))}
325          </Text>
326          <Text>
327            {cells.map(cell => (
328              <Text color={cell.isToday ? COLORS.text : COLORS.muted}>
329                {dayLabel(cell.weekday, grid.cell).padEnd(grid.cell)}
330              </Text>
331            ))}
332          </Text>
333        </Box>
334
335        <Box marginTop={1} flexDirection="column">
336          {headingRow("Your year")}
337          <Text>
338            <Text color={COLORS.gold}>{'━'.repeat(bar.filled)}</Text>
339            <Text color={COLORS.line}>{'━'.repeat(bar.empty)}</Text>
340          </Text>
341          <Box flexDirection="row" justifyContent="space-between" width={cols}>
342            <Text color={COLORS.green}>{doneText}</Text>
343            {doneText.length + 1 + leftText.length <= cols && <Text color={COLORS.muted}>{leftText}</Text>}
344          </Box>
345        </Box>
346
347        <Box marginTop={1} flexDirection="column">
348          {wrap(FOOTNOTE, cols).map(line => (
349            <Text color={COLORS.muted}>{line}</Text>
350          ))}
351        </Box>
352      </Box>
353    )
354  })
355}
356
357async function press($: EngineInterface, key: string): Promise<void> {
358  if (key === 'complete') await complete($)
359  else if (key === 'undo') await undo($)
360  else if (key === 'another') await swap($)
361  else if (key === 'coach') await coach($)
362}
363
hooks/challenges.ts 461 lines
1export type ThemeId =
2  | 'prompting'
3  | 'files'
4  | 'commands'
5  | 'memory'
6  | 'agents'
7  | 'build'
8  | 'habits'
9
10export type Theme = { id: ThemeId; name: string; color: string }
11
12export type Challenge = {
13  id: string
14  theme: ThemeId
15  title: string
16  task: string
17  isCloser: boolean
18}
19
20export const THEMES: readonly Theme[] = [
21  { id: 'prompting', name: 'Prompting', color: '#79C0FF' },
22  { id: 'files', name: 'Files', color: '#D2A8FF' },
23  { id: 'commands', name: 'Commands', color: '#56D4DD' },
24  { id: 'memory', name: 'Memory', color: '#FFA657' },
25  { id: 'agents', name: 'Agents', color: '#FF7B72' },
26  { id: 'build', name: 'Build', color: '#F778BA' },
27  { id: 'habits', name: 'Habits', color: '#A5B4FC' },
28]
29
30type Entry = readonly [title: string, task: string]
31
32const PROMPTING: readonly Entry[] = [
33  ['Lead with the goal', 'Ask Claude for a packing list for a weekend away, and start your message with what the trip is for. Notice how stating the purpose first shapes the list.'],
34  ['Name the audience', 'Ask Claude to explain what a spreadsheet formula is, once for a ten-year-old and once for an accountant. Compare the two answers.'],
35  ['Set a length limit', 'Ask Claude to summarise a topic you care about in exactly three sentences. Check that it kept to the limit.'],
36  ['Ask for a table', 'Ask Claude to compare three ways of getting to work as a table with columns for cost, time and comfort.'],
37  ['Show an example', 'Give Claude one example of a friendly reminder text you like, then ask for three more in the same style.'],
38  ['Ask what it needs', 'Describe a small plan you have, then ask Claude what extra information would help it help you.'],
39  ['Request three options', 'Ask Claude for three subject lines for an email to a neighbour, each in a different tone.'],
40  ['Name the format', 'Ask Claude for a recipe as a numbered shopping list followed by numbered steps.'],
41  ['Add two constraints', 'Ask Claude to plan a dinner menu with two limits of your choosing, such as no nuts and under thirty minutes.'],
42  ['Let it interview you', 'Tell Claude you want help planning a celebration, and ask it to question you one point at a time before it suggests anything.'],
43  ['Refine in one step', 'Ask Claude to rewrite a sentence you wrote recently, then reply with one specific change and watch it adjust.'],
44  ['Ask for the reasoning', 'Ask Claude which of two phone plans is better value for you, and ask it to show the steps behind its choice.'],
45  ['Choose the tone', 'Ask Claude for a short thank-you note in a warm tone, then for the same note in a formal tone.'],
46  ['Shrink a big task', 'Pick a chore that feels large and ask Claude to break it into steps of ten minutes or less.'],
47  ['Say what to avoid', 'Ask Claude for weekend activity ideas and tell it what you do not want, such as anything that costs money.'],
48  ['Make it a checklist', 'Ask Claude to turn your morning routine into a short checklist you could print.'],
49  ['Give it a role', 'Ask Claude to act as a patient librarian and recommend three books based on one you loved.'],
50  ['Weigh a decision', 'Pick a decision you are weighing and ask Claude for an even list of pros and cons, then for the one question that matters most.'],
51  ['Ask for plain words', 'Paste a confusing paragraph from a bill, letter or policy and ask Claude to explain it in plain words.'],
52  ['Quiz yourself', 'Ask Claude to quiz you on a topic you know, one question at a time, waiting for your answer before the next.'],
53  ['Draft then edit', 'Ask Claude to draft a polite message declining an invitation, then change one line of it yourself.'],
54  ['Point to the error', 'Ask Claude a factual question, and if any part looks off, tell it exactly which part and ask it to check again.'],
55  ['Ask how sure', 'Ask Claude a question about history, then ask how confident it is in each part of its answer.'],
56  ['Get a one-liner', 'After the next long answer you get today, ask Claude to sum it up in one line.'],
57  ['Compare two versions', 'Ask Claude for two versions of a short bio of you, and ask it to say what makes them different.'],
58  ['Give context first', 'Before asking for advice on a hobby, tell Claude your experience level in two sentences and see how the advice changes.'],
59  ['Ask for the next step', 'Describe something you are stuck on and ask Claude for only the very next step, not the whole plan.'],
60  ['Cap the bullets', 'Ask Claude for tips on sleeping better, in no more than five bullets.'],
61  ['Ask for a rubric', 'Ask Claude how it would judge whether the opening of a short story is good, as a simple checklist.'],
62  ['Argue the other side', 'Ask Claude to make the best short case for a view you disagree with, fairly and calmly.'],
63  ['Ask for examples', 'Ask Claude to explain a word you find tricky, with three example sentences.'],
64  ['Describe the reader', 'Ask Claude to write a notice for a shared fridge, and tell it who will read it and where it will hang.'],
65  ['Keep the best line', 'Ask Claude for a paragraph, tell it which sentence you like most, and ask it to rewrite the rest around that sentence.'],
66  ['Ask for a timeline', 'Ask Claude to lay out a two-week plan for learning a new skill as a day-by-day timeline.'],
67  ['Prepare good questions', 'Ask Claude for five good questions to ask at an appointment or meeting you have coming up.'],
68  ['Notes into prose', 'Type a few rough notes about your day and ask Claude to turn them into a tidy paragraph.'],
69  ['Prose into notes', 'Paste a long paragraph and ask Claude to turn it into short notes you could scan in ten seconds.'],
70  ['Find a simpler way', 'Describe how you do a weekly task today and ask Claude whether there is a simpler way.'],
71  ['Request a template', 'Ask Claude for a fill-in-the-blanks template for a message you send often.'],
72  ['Set a word budget', 'Ask Claude to describe your town in fifty words or fewer.'],
73  ['Score your ideas', 'Ask Claude to rate three of your ideas from one to five on effort and on payoff.'],
74  ['Ask for a rough draft', 'Ask Claude for a rough first draft of a short toast and say you will polish it yourself.'],
75  ['Define what good means', 'Before asking Claude to write a product review, describe what a helpful review looks like to you.'],
76  ['Ask for the catch', 'Describe a deal or offer you have seen and ask Claude what the catch might be.'],
77  ['Separate known from unsure', 'Ask Claude about a topic and ask it to say which parts are well known and which you should double-check.'],
78  ['Ask for an analogy', 'Ask Claude to explain how the internet reaches your home using an everyday comparison.'],
79  ['Chain two requests', 'Ask Claude for a list of gift ideas, then in your next message ask it to rank them for one particular person.'],
80  ['Halve an answer', 'Take an answer you got today and ask Claude to make it half as long without losing the main point.'],
81  ['Have it repeat back', 'Before Claude starts a task for you, ask it to say back what it thinks you want.'],
82  ['Ask what could slip', 'Describe a simple plan, such as a watering schedule, and ask Claude what could go wrong with it.'],
83  ['Ask like a friend', 'Ask Claude something the way you would ask a friend, with no special phrasing, and see how well it copes.'],
84  ['Keep your best prompt', 'Look back over this week, find the prompt that worked best, and save it in a note for next time.'],
85]
86
87const FILES: readonly Entry[] = [
88  ['Point at a file', 'Type @ in the prompt box, pick any file in your folder, and ask Claude what it contains.'],
89  ['Summarise a document', 'Ask Claude to read a document in your folder and summarise it in five bullets.'],
90  ['List your folder', 'Ask Claude to list the files in your current folder, grouped by type.'],
91  ['Read a PDF', 'Ask Claude to read a PDF you have and pull out its three most important points.'],
92  ['Find the deadlines', 'Ask Claude to find every date or deadline mentioned in a PDF or letter you have.'],
93  ['Describe an image', 'Point Claude at an image in your folder and ask it to describe what it shows.'],
94  ['Paste a screenshot', 'Copy a screenshot to your clipboard, press Ctrl+V in the prompt box, and ask Claude about it.'],
95  ['Total a column', 'Ask Claude to open a spreadsheet or CSV file and tell you the total of one column.'],
96  ['Start an ideas file', 'Ask Claude to create a file called ideas.md with three things you want to try this month.'],
97  ['Fix the spelling', 'Ask Claude to fix the spelling in a document you wrote, and review each change before it saves.'],
98  ['Rename with care', 'Ask Claude to suggest clearer names for three files, and approve each rename one at a time.'],
99  ['Plan your folders', 'Ask Claude to suggest a simple folder structure for your personal documents, without moving anything yet.'],
100  ['Compare two drafts', 'Ask Claude to compare two versions of a document and list what changed between them.'],
101  ['Lift out a table', 'Ask Claude to pull a table out of a PDF or document and save it as a spreadsheet.'],
102  ['Clean up a list', 'Give Claude a file holding a messy list and ask it to remove duplicates and sort what is left.'],
103  ['Tidy the formatting', 'Ask Claude to turn a plain text file into a neatly formatted Markdown document.'],
104  ['Search your files', 'Ask Claude to find every file in your folder that mentions a word you choose.'],
105  ['Read a trend', 'Ask Claude to read a spreadsheet and describe the trend in one column in plain words.'],
106  ['Write a README', 'Ask Claude to write a short README file explaining what is in your current folder.'],
107  ['Find the biggest files', 'Ask Claude which files in your folder are the largest, without deleting anything.'],
108  ['Read text from a photo', 'Show Claude a photo of a sign, receipt or handwritten note and ask it to type out the words.'],
109  ['Check a form', 'Ask Claude to read a form you are filling in and list anything you still need to provide.'],
110  ['Merge two notes', 'Ask Claude to combine two short notes files into one tidy document.'],
111  ['Make a small spreadsheet', 'Ask Claude to create a CSV file tracking five things you want to buy, with a price column.'],
112  ['Newest first', 'Ask Claude to list the files in a folder from newest to oldest.'],
113  ['Outline a long document', 'Ask Claude to read a long document and give you its outline as headings only.'],
114  ['Quote it exactly', 'Ask Claude to find a particular sentence in a document and quote it word for word, saying where it appears.'],
115  ['Catch mismatches', 'Ask Claude to check a document for names, dates or numbers that do not agree with each other.'],
116  ['Add another folder', 'Run /add-dir to let Claude see a second folder in this session, then ask it to list what is there.'],
117  ['Explain a chart', 'Show Claude a picture of a chart or graph and ask what it says.'],
118  ['Build a glossary', 'Ask Claude to read a document and list its special terms, each with a short definition.'],
119  ['Split by topic', 'Ask Claude to propose how to split a long notes file into separate files by topic, and approve the plan before it acts.'],
120  ['Fill in a template', 'Write a short template with blanks, then ask Claude to make a filled-in copy for a real case.'],
121  ['Find empty cells', 'Ask Claude to look through a spreadsheet for empty cells and tell you where they are.'],
122  ['Hear the rough spots', 'Ask Claude to read a document and point out the sentences that would be hard to read aloud.'],
123  ['Add contents', 'Ask Claude to add a short table of contents to the top of a Markdown document.'],
124  ['Count the words', 'Ask Claude how many words a document has and roughly how long it takes to read.'],
125  ['Gather the contacts', 'Ask Claude to pull every name and email address from a document into a list saved on your machine.'],
126  ['Keep a backup copy', 'Before any edit today, ask Claude to copy the file first with -backup added to its name.'],
127  ['Write alt text', 'Ask Claude to write a short description of an image, the kind a screen reader would read aloud.'],
128  ['Decode a formula', 'Ask Claude to explain in plain words what one formula in your spreadsheet is doing.'],
129  ['Study notes from PDF', 'Ask Claude to read a PDF and write study notes into a new file.'],
130  ['Plan a photo rename', 'Ask Claude to show you a plan for renaming a group of photos by date, and approve it only if it looks right.'],
131  ['What changed this week', 'Ask Claude which files in your folder changed in the last seven days.'],
132  ['A tick-off trip file', 'Ask Claude to create a checklist file for an upcoming trip that you can tick off as you pack.'],
133  ['Translate a document', 'Ask Claude to translate a short document into another language and save it as a new file.'],
134  ['One line per file', 'Ask Claude to skim every document in a small folder and write one line about each.'],
135  ['Compare two months', 'Ask Claude to compare two columns or months in a spreadsheet and say what went up and what went down.'],
136  ['Action items from email', 'Save a long email as a text file and ask Claude to list the action items in it.'],
137  ['Match the headings', 'Ask Claude to make all the headings in a document consistent in style.'],
138  ['Collect the links', 'Ask Claude to list every web link mentioned in a document you have.'],
139  ['Undo a file change', 'Make a small change to a file with Claude, then run /rewind to go back to before it.'],
140]
141
142const COMMANDS: readonly Entry[] = [
143  ['Open the help', 'Run /help and read through the list of commands available to you.'],
144  ['See the shortcuts', 'With the prompt box empty, type ? to see the keyboard shortcuts.'],
145  ['Browse with slash', 'Type / in the prompt box and scroll through the commands that appear.'],
146  ['Check your status', 'Run /status to see your version, model and account.'],
147  ['Look at context', 'Run /context to see how much of Claude\'s working memory this conversation is using.'],
148  ['Compact a long chat', 'In a long conversation, run /compact to summarise it and free up room.'],
149  ['Start with a clean slate', 'Run /clear to start a new conversation with empty context.'],
150  ['Pick up where you left', 'Run /resume, look through your past conversations, and open one.'],
151  ['Name this session', 'Run /rename and give this conversation a name you will recognise later.'],
152  ['Meet the models', 'Run /model to see the models available to you and what each is for.'],
153  ['Try another effort level', 'Run /effort to see the effort levels, then ask a question at a different level.'],
154  ['Pick a theme', 'Run /theme and try a theme that is easy on your eyes.'],
155  ['Colour the prompt bar', 'Run /color to give this session\'s prompt bar a colour of its own.'],
156  ['Open the settings', 'Run /config and read through the settings you can change.'],
157  ['Copy the last answer', 'Run /copy to put Claude\'s last response on your clipboard, then paste it somewhere.'],
158  ['Export a conversation', 'Run /export to save this conversation to a file or to your clipboard.'],
159  ['See your usage', 'Run /usage to see your session cost or plan usage.'],
160  ['Interrupt and redirect', 'Ask Claude something long, press Esc to stop it partway, and tell it what you want instead.'],
161  ['Clear the box quickly', 'Type a sentence in the prompt box, then press Esc twice to clear it.'],
162  ['Search past prompts', 'Press Ctrl+R and type a word to find something you asked before.'],
163  ['Bring back a prompt', 'Press the up arrow in an empty prompt box to bring back your last prompt.'],
164  ['Add a line break', 'While typing, press Ctrl+J to start a new line without sending.'],
165  ['Write in your editor', 'Press Ctrl+G to write a long prompt in your text editor, then save and close it to bring the text back.'],
166  ['Open the transcript', 'Press Ctrl+O to see the detailed transcript, then press it again to return.'],
167  ['Cycle the modes', 'Press Shift+Tab a few times and watch the mode shown under the prompt change.'],
168  ['Plan without changes', 'Run /plan and ask Claude to plan a small task without changing anything.'],
169  ['Ask by the way', 'While Claude is working, use /btw to ask a quick side question without interrupting it.'],
170  ['Get a recap', 'Run /recap to get a one-line recap of this session.'],
171  ['Take a lesson', 'Run /powerup and complete one of the short interactive lessons.'],
172  ['Read the release notes', 'Run /release-notes and find one change you did not know about.'],
173  ['Run a checkup', 'Run /doctor to check that your Claude Code setup is healthy.'],
174  ['Stash a draft', 'Type a half-finished prompt, press Ctrl+S to stash it, send something else, and see your draft come back.'],
175  ['Run a shell command', 'Type ! followed by a simple command such as dir or ls to run it directly and let Claude see the result.'],
176  ['See background tasks', 'Run /tasks to see what is running in the background right now.'],
177  ['List your skills', 'Run /skills to see every skill Claude can use in this session.'],
178  ['Review permissions', 'Run /permissions to see which actions Claude may take without asking you.'],
179  ['Read your insights', 'Run /insights to get a report on how you have been using Claude Code.'],
180  ['Branch the conversation', 'Run /branch to copy this conversation at this point, so you can try a different direction.'],
181  ['Set a goal', 'Run /goal with a short goal, such as finishing a document, and see Claude check it before stopping.'],
182  ['Try focus view', 'Run /focus to see only your prompt, the summary and the response, then run it again to switch back.'],
183  ['Show the to-do list', 'Ask Claude to do a three-step task, then press Ctrl+T to show or hide its to-do list.'],
184  ['Send to background', 'While a long command runs, press Ctrl+B to send it to the background and keep chatting.'],
185  ['Mention a file fast', 'Type @ and the first letters of a file name, then pick it from the suggestions.'],
186  ['Change the output style', 'Run /output-style to see the response styles available, and try one.'],
187  ['Set up a status line', 'Run /statusline and ask for a status line that shows your folder and model.'],
188  ['Read your keybindings', 'Run /keybindings to open your keyboard shortcuts file and read it, without changing anything.'],
189  ['Browse your plugins', 'Run /plugin to see the plugins and mods you have installed.'],
190  ['Look at connectors', 'Run /mcp to see which connectors are set up in this session.'],
191  ['Check your hooks', 'Run /hooks to see whether any automatic actions are set up.'],
192  ['Try the other layout', 'Run /tui to read about the default and fullscreen layouts, and try the one you are not using.'],
193  ['Make an alias', 'Run /alias to give a command you use often a shorter name.'],
194  ['Check your version', 'Run /version to see exactly which version of Claude Code this session runs.'],
195]
196
197const MEMORY: readonly Entry[] = [
198  ['Open your memory', 'Run /memory to see which instruction files Claude reads in this folder.'],
199  ['Create a CLAUDE.md', 'Ask Claude to create a CLAUDE.md file in a folder you use, with one line saying what the folder is for.'],
200  ['Prefer short answers', 'Add a line to your instructions asking Claude to keep answers short unless you ask for detail.'],
201  ['Test a saved preference', 'Run /clear to start fresh, then check that Claude follows a preference you have saved.'],
202  ['Let init draft it', 'In a folder with some documents, run /init and read the CLAUDE.md it drafts before keeping it.'],
203  ['Ask what it follows', 'Ask Claude which instructions it is following in this folder right now.'],
204  ['Save your name', 'Add a line to your personal instructions with the name you like to be called.'],
205  ['Remove an old rule', 'Run /memory, find an instruction that no longer fits, and remove it.'],
206  ['Personal or folder', 'Ask Claude to explain the difference between your personal instructions and a folder\'s CLAUDE.md.'],
207  ['Write a house style', 'Add three bullet points to a CLAUDE.md describing how you like documents formatted.'],
208  ['Read about a skill', 'Run /skills, pick one skill, and ask Claude what it does.'],
209  ['Use a skill by name', 'Ask Claude to use one of your listed skills, by name, for a small task.'],
210  ['What a skill is', 'Ask Claude to explain in plain words what a skill is and when one gets used.'],
211  ['Draft a tiny skill', 'Ask Claude to help you write a skill that turns rough notes into a meeting summary.'],
212  ['Test a skill', 'Use one of your skills on a real piece of work and decide whether it helped.'],
213  ['Improve a skill', 'Ask Claude to read one of your skills and suggest one improvement to its instructions.'],
214  ['Reload your skills', 'After a skill changes on disk, run /reload-skills so this session picks up the change.'],
215  ['Find unused skills', 'Run /skill-doctor to see which skills you never use.'],
216  ['Save tricky names', 'Add a short list of names or terms Claude tends to get wrong for you to your CLAUDE.md.'],
217  ['Choose your spelling', 'Add a line asking Claude to use British or American spelling, whichever you prefer.'],
218  ['Name your apps', 'Tell Claude in your CLAUDE.md which apps you use for notes and calendars, so its suggestions fit.'],
219  ['Review your instructions', 'Ask Claude to read your CLAUDE.md and point out anything unclear or contradictory.'],
220  ['Trim to essentials', 'Ask Claude to shorten your CLAUDE.md to the rules that matter most.'],
221  ['Ask before deleting', 'Add a line asking Claude to always ask before deleting any file.'],
222  ['Set a date format', 'Add a line asking Claude to write dates in the format you prefer.'],
223  ['A rule for one folder', 'Create a CLAUDE.md in one folder with a rule that applies only there, and test it.'],
224  ['Which rule wins', 'Ask Claude what happens when your personal instructions and a folder\'s instructions disagree.'],
225  ['Log a decision', 'Ask Claude to add today\'s date and one decision you made to a decisions note in your folder.'],
226  ['Save a repeat checklist', 'Put a checklist you repeat often into your CLAUDE.md so Claude can follow it when you ask.'],
227  ['Map the key files', 'Ask Claude to add a short section to CLAUDE.md describing the most important files in this folder.'],
228  ['Save a correction', 'Correct Claude on something small, then ask it to save that correction to your instructions.'],
229  ['Read every saved line', 'Open your instruction files with /memory and read every line you have saved so far.'],
230  ['Learn the memory settings', 'Run /memory, look at the memory settings, and ask Claude what each one does.'],
231  ['Save your abbreviations', 'Add three abbreviations you use to your CLAUDE.md, with what each stands for.'],
232  ['End with a summary', 'Add a line asking Claude to finish longer answers with a one-line summary.'],
233  ['Describe yourself briefly', 'Add one sentence about who you are, such as your work or a hobby, so Claude\'s examples fit you.'],
234  ['Find a skill idea', 'Ask Claude what kinds of skills people often add, and choose one idea for yourself.'],
235  ['Skills from plugins', 'Run /plugin and see which installed plugins bring skills of their own.'],
236  ['A checking skill', 'Ask Claude to write a skill that checks a document against a short checklist you give it.'],
237  ['Prepare to share', 'Ask Claude what you should remove from a CLAUDE.md before sharing it with someone else.'],
238  ['Date your rules', 'Add today\'s date in brackets beside one rule, so you remember when you added it.'],
239  ['Remove repeated rules', 'Ask Claude to find any instructions that say the same thing twice across your memory files.'],
240  ['Ask about import', 'Ask Claude what /import does and whether it would be useful to you.'],
241  ['Save a handy link', 'Add a link you use often to your CLAUDE.md, with a note on when Claude should use it.'],
242  ['Remove something saved', 'Ask Claude how to remove something it saved for you, then remove one item.'],
243  ['Write a never list', 'Add two things you never want Claude to do, such as sending messages without asking.'],
244  ['Check a rule holds', 'Pick one rule in your instructions and write a short prompt that tests whether Claude follows it.'],
245  ['Sharpen a description', 'Ask Claude to rewrite one skill\'s description so it is clear when that skill should be used.'],
246  ['Note a project', 'Ask Claude to add a short project note to CLAUDE.md with the goal, the deadline and who it is for.'],
247  ['Ask what slows it', 'Ask Claude whether anything in your instructions makes its job harder.'],
248  ['Copy before changing', 'Ask Claude to make a dated copy of your CLAUDE.md before you change it.'],
249  ['Habit into a skill', 'Pick something you ask for every week and ask Claude to turn it into a skill.'],
250]
251
252const AGENTS: readonly Entry[] = [
253  ['Meet the helpers', 'Ask Claude what a subagent is and when it would use one for you.'],
254  ['Two at once', 'Ask Claude to research two unrelated questions at the same time using helpers, and compare the answers.'],
255  ['Send a subtask', 'Run /subtask with a small research question and wait for its answer to come back here.'],
256  ['Fork this session', 'Run /fork to copy this conversation into a background session, then keep working here.'],
257  ['Watch it finish', 'Start something in the background and use /tasks to watch it finish.'],
258  ['Look at schedules', 'Run /schedule and see how a scheduled agent is set up, without creating one yet.'],
259  ['Sketch a routine', 'Ask Claude to help you plan a weekly scheduled agent, writing down what it would do before you decide to create it.'],
260  ['Try a loop', 'Ask Claude how /loop works, set one up to check something every few minutes, then stop it.'],
261  ['Pick a connector', 'Run /mcp, choose one connector, and ask Claude what it can do with it.'],
262  ['What connectors do', 'Ask Claude which connectors you have and what each could do for you.'],
263  ['Next three events', 'If you have a calendar connector, ask Claude what your next three events are.'],
264  ['Find an email', 'If you have an email connector, ask Claude to find the last message from someone you choose, without replying.'],
265  ['Find a document online', 'If you have a Drive or documents connector, ask Claude to find a document by its topic.'],
266  ['Split a question three ways', 'Ask Claude to split a question into three parts and send a helper after each one.'],
267  ['Compare two helpers', 'Ask two helpers the same question in different words, and ask Claude to compare what came back.'],
268  ['Fresh eyes review', 'After Claude writes something for you, ask it to send a helper to review it with fresh eyes.'],
269  ['Check the workflows', 'Run /workflows to see whether any multi-step workflows are running or finished.'],
270  ['Ask what it is doing', 'While a helper is working, ask Claude what the helper is doing right now.'],
271  ['Stop a background job', 'Ask Claude how to stop a background job you no longer need, then stop one.'],
272  ['Save the findings', 'Ask a helper to gather facts on a topic, then ask Claude to save the findings to a file.'],
273  ['Ask about the cloud', 'Ask Claude what /teleport does and when sending a session to the cloud would help you.'],
274  ['Keep a job running', 'Ask Claude how to keep a long job running in the background while you do something else.'],
275  ['Summaries in parallel', 'Ask Claude to use helpers to summarise several documents at the same time.'],
276  ['Plan, then delegate', 'Ask Claude to plan a job first, then hand each step to its own helper.'],
277  ['Read a web page', 'If Claude in Chrome is connected, ask Claude to open a web page and tell you its headline.'],
278  ['Know a connector\'s reach', 'Ask Claude what a connector can read and what it can change before you use it.'],
279  ['Design a morning brief', 'Ask Claude what a scheduled morning briefing could include for you, and write the list down.'],
280  ['Meet the advisor', 'Run /advisor, read what it does, and decide whether you want it on.'],
281  ['Write the helper\'s brief', 'Ask Claude to send a helper with a one-sentence job description that you write yourself.'],
282  ['Estimate the job', 'Before a multi-step job, ask Claude how long it might take and how many helpers it would use.'],
283  ['Cap the helpers', 'Ask Claude to do a research task using no more than two helpers.'],
284  ['Three current facts', 'Ask Claude to look up three current facts about a topic and give you the links.'],
285  ['Compare three products', 'Ask Claude to have helpers research three products you are considering, one each, and compare them.'],
286  ['Report surprises', 'Ask Claude to tell you as soon as a helper finds something surprising.'],
287  ['List every helper', 'At the end of a session, ask Claude to list every helper it used and what each did.'],
288  ['Draft, do not send', 'If you have a messaging connector, ask Claude to draft a message there without sending it.'],
289  ['Set a check-in', 'Ask Claude to remind you in this session, in thirty minutes, to say how a task is going.'],
290  ['Cancel a repeat job', 'Ask Claude to list any scheduled or repeating jobs you have, and cancel one you no longer need.'],
291  ['List your routines', 'Run /schedule and look at the routines you already have, if any.'],
292  ['Connector rules', 'Run /permissions and look for any rules about connector tools.'],
293  ['Three-line status', 'While a long job runs, ask Claude for a three-line status report.'],
294  ['Peek at a helper', 'While a helper runs, open /tasks and look at what it is working on.'],
295  ['Cloud environments', 'Run /remote-env to see the environments cloud agents can use.'],
296  ['Three viewpoints', 'Ask Claude to have three helpers each argue a different view on a question, then summarise them.'],
297  ['Fact-check a claim', 'Ask Claude to send a helper to fact-check one claim in a document you have.'],
298  ['Research in the background', 'Ask Claude to run a research question in the background and tell you when it is done.'],
299  ['Find a new connector', 'Ask Claude what connectors exist for an app you use and how you would add one.'],
300  ['Meeting prep notes', 'If you have a calendar connector, ask Claude to prepare short notes for your next meeting.'],
301  ['Write agent instructions', 'Ask Claude to draft the instructions a weekly summary agent would follow, and save them to a file.'],
302  ['Helpers and usage', 'Ask Claude how running several helpers affects your usage, then check /usage.'],
303  ['Write a handoff note', 'Ask Claude to write a short handoff note so a helper could pick up a task without the whole conversation.'],
304  ['Review a helper\'s work', 'When a helper finishes, ask Claude what it checked and what it did not.'],
305]
306
307const BUILD: readonly Entry[] = [
308  ['A hobby page', 'Ask Claude to make a one-page HTML file about a hobby of yours, then open it in your browser.'],
309  ['A packing checklist page', 'Ask Claude to build a simple web page checklist you can tick off for your next trip.'],
310  ['A bill splitter', 'Ask Claude to make a small web page that splits a bill between friends and adds a tip.'],
311  ['A countdown page', 'Ask Claude to build a web page that counts down the days to an event you care about.'],
312  ['A recipe card', 'Ask Claude to make a printable recipe card page for a dish you cook.'],
313  ['A habit tracker', 'Ask Claude to make a spreadsheet that tracks one habit across this month.'],
314  ['A monthly budget', 'Ask Claude to make a simple budget spreadsheet with income, spending and what is left.'],
315  ['A reading list', 'Ask Claude to make a reading list file with title, author and a done column.'],
316  ['A quiz page', 'Ask Claude to make a five-question quiz web page on a topic you love.'],
317  ['A dinner picker', 'Ask Claude to build a page that picks a random dinner idea from a list you give it.'],
318  ['A birthday card', 'Ask Claude to make a birthday card as a web page for someone you know.'],
319  ['A focus timer', 'Ask Claude to make a web page with a ten-minute focus timer.'],
320  ['A chore chart', 'Ask Claude to make a printable weekly chore chart for your household.'],
321  ['A watering schedule', 'Ask Claude to make a watering schedule table for your plants.'],
322  ['A gift tracker', 'Ask Claude to make a spreadsheet for gift ideas and budgets for the people you buy for.'],
323  ['A baking converter', 'Ask Claude to make a page that converts cups to grams for common baking ingredients.'],
324  ['A flashcard set', 'Ask Claude to make a flashcard web page for ten words you want to learn.'],
325  ['A meal planner', 'Ask Claude to make a week-long meal plan table and a shopping list drawn from it.'],
326  ['An emergency contacts sheet', 'Ask Claude to make a one-page emergency contacts sheet you can print, with blanks to fill in.'],
327  ['A mood log', 'Ask Claude to make a simple spreadsheet for noting your mood each day.'],
328  ['A word counter', 'Ask Claude to make a page where you paste text and see how many words it has.'],
329  ['A colour palette', 'Ask Claude to make a page showing five colours that go well together, with their codes.'],
330  ['A trip itinerary', 'Ask Claude to turn a trip plan into a tidy day-by-day itinerary document.'],
331  ['A savings goal', 'Ask Claude to make a spreadsheet showing how long it takes to save for something at a set amount each week.'],
332  ['A party plan', 'Ask Claude to make a one-page party plan with guests, food and a timeline.'],
333  ['A stretching card', 'Ask Claude to make a printable card with a gentle ten-minute stretching routine.'],
334  ['Book club questions', 'Ask Claude to make a simple page of discussion questions for a book you have read.'],
335  ['A garden bed map', 'Ask Claude to draw a simple layout of a small garden bed showing what grows where.'],
336  ['A film night picker', 'Ask Claude to make a page that shuffles a list of films and shows one.'],
337  ['A family calendar', 'Ask Claude to make a printable monthly calendar page with a column for each person.'],
338  ['A price per unit table', 'Ask Claude to make a table comparing the price per unit of a few products you buy.'],
339  ['A thank-you list', 'Ask Claude to make a file listing the people to thank after an event, with a done column.'],
340  ['A study timetable', 'Ask Claude to make a study timetable for an exam or course, as a table.'],
341  ['A talk outline', 'Ask Claude to make a five-slide outline for a short talk on something you know well.'],
342  ['A family newsletter', 'Ask Claude to make a short newsletter about your month for friends or family, as a document.'],
343  ['Name badges', 'Ask Claude to make a printable page of name badges for a small event.'],
344  ['A score keeper', 'Ask Claude to make a page for keeping score in a board game with two to four players.'],
345  ['Box labels', 'Ask Claude to make printable labels for boxes when moving or storing things.'],
346  ['A care guide', 'Ask Claude to make a one-page guide for someone looking after your pet or plants.'],
347  ['A journal template', 'Ask Claude to make a journal page template with three short prompts for each day.'],
348  ['A bookmarks page', 'Ask Claude to make a web page with links to the ten sites you visit most.'],
349  ['A recipe scaler', 'Ask Claude to make a page that doubles or halves the amounts in a recipe.'],
350  ['A wish list page', 'Ask Claude to make a simple wish list page you could share with family.'],
351  ['A walking log', 'Ask Claude to make a spreadsheet to log walks or runs with distance and time.'],
352  ['A loan calculator', 'Ask Claude to make a spreadsheet that shows the monthly payment for a loan amount and rate you enter.'],
353  ['A seating plan', 'Ask Claude to make a seating plan for a dinner, keeping two people you name apart.'],
354  ['A playlist with notes', 'Ask Claude to make a document listing songs for an occasion, with a note on why each fits.'],
355  ['A priorities page', 'Ask Claude to make a web page showing today\'s date and your three priorities.'],
356  ['An errand route', 'Ask Claude to make a checklist of stops for a day of errands, in a sensible order.'],
357  ['An activity sheet', 'Ask Claude to make a printable puzzle or activity sheet for a child.'],
358  ['A personal FAQ', 'Ask Claude to make a short document answering questions people often ask you.'],
359  ['Polish a build', 'Pick something you have built with Claude and ask it to make it look more polished; if you have nothing yet, ask for a simple page first.'],
360]
361
362const HABITS: readonly Entry[] = [
363  ['Read before approving', 'The next time Claude asks permission, read exactly what it wants to do before you approve.'],
364  ['Ask for a self-check', 'After Claude finishes a task, ask it to check its own work and list anything it is unsure of.'],
365  ['Verify one fact', 'Pick one fact Claude told you today and check it against another source.'],
366  ['Plan before acting', 'Before a bigger task, ask Claude for a plan and approve it before anything changes.'],
367  ['Keep secrets out', 'Ask Claude what kinds of information you should never paste into a conversation.'],
368  ['Read the change', 'After Claude edits a file, open it yourself and read what changed.'],
369  ['Know what rewind undoes', 'Ask Claude what /rewind can and cannot undo, so you know your safety net.'],
370  ['Track usage over a session', 'Run /usage at the start and end of a session and compare the two.'],
371  ['Clear between topics', 'When you switch topics today, run /clear first and notice whether answers stay sharper.'],
372  ['Compact in time', 'Run /context, and if the conversation is getting full, run /compact before it slows down.'],
373  ['Ask where to confirm', 'When Claude states a fact you will rely on, ask where you could confirm it.'],
374  ['Describe moves first', 'Before letting Claude reorganise any folder, ask it to describe exactly what will move where.'],
375  ['Match model to task', 'Ask Claude which model suits a quick question and which suits a long task, then choose one with /model.'],
376  ['Lighter effort for small asks', 'For a quick question today, use /effort to pick a lower level and see whether the answer is still good.'],
377  ['Stop early', 'Practise pressing Esc as soon as Claude heads the wrong way, then tell it what you wanted.'],
378  ['Spot the guesses', 'Ask Claude to mark any part of its answer that is a guess rather than something it knows.'],
379  ['Define done', 'Before a task, tell Claude exactly what finished will look like.'],
380  ['Prune permissions', 'Run /permissions and remove any rule you do not remember adding.'],
381  ['Protect passwords', 'Ask Claude how it should treat a file that contains a password, and add a rule if you want one.'],
382  ['Question the risky step', 'In plan mode, read Claude\'s plan and ask one question about the riskiest step before approving.'],
383  ['Batch small asks', 'Put three small requests into one clear message instead of sending three separate ones.'],
384  ['Close with a summary', 'Before you end a session, ask Claude what it changed and what is left to do.'],
385  ['Keep a session log', 'Ask Claude to add a dated line to a log file describing what you did together today.'],
386  ['Test its confidence', 'Ask Claude a question you already know the answer to, and check that its confidence matches its accuracy.'],
387  ['Prefer a dry run', 'Ask Claude to describe what it would do to a folder without doing it, then decide.'],
388  ['Pick a permission mode', 'Ask Claude what each permission mode allows, then choose the one that matches how careful you want to be today.'],
389  ['Read before it goes', 'If Claude drafts any message for you today, read it in full before it goes anywhere.'],
390  ['Pause with a recap', 'After a long session, run /recap, note where you are, and step away for five minutes.'],
391  ['Ask how to undo', 'Before approving a change, ask Claude what could go wrong and how to undo it.'],
392  ['Spot-check a summary', 'Ask Claude to summarise a document, then read one section yourself and compare.'],
393  ['Restate the goal', 'In a long chat, ask Claude to restate your original goal and check it still matches.'],
394  ['Right tool for the job', 'Ask Claude whether a task you have in mind is better done here, in a spreadsheet app, or by hand.'],
395  ['Check a download', 'Ask Claude how to tell whether a file or link someone sent you is safe to open.'],
396  ['Share only what is needed', 'Before pasting anything about another person, ask Claude whether it needs that detail at all.'],
397  ['Review your week', 'Run /insights and pick one habit to keep and one to change.'],
398  ['Read the error', 'When something fails, ask Claude to explain the error message in plain words before trying again.'],
399  ['One change at a time', 'Ask Claude to make changes one at a time today, checking each before the next.'],
400  ['Make a backup', 'Ask Claude how to back up an important folder, and make one backup now.'],
401  ['Test on a copy', 'Ask Claude to try a risky change on a copy of a file first.'],
402  ['Weigh a big job', 'Before a large job with several helpers, ask Claude to estimate the effort and decide whether it is worth it.'],
403  ['Send feedback', 'Run /feedback and send one honest note about something that helped or confused you.'],
404  ['Check what is current', 'Ask Claude what today\'s date is and whether its knowledge might be out of date for your question.'],
405  ['Choose the safer path', 'When Claude offers two approaches, ask which one is easier to undo.'],
406  ['Look before cleanup', 'Ask Claude to list exactly which files would be removed before any cleanup.'],
407  ['Check the sandbox', 'Run /sandbox to see whether commands run in a protected sandbox on your machine.'],
408  ['Open the link', 'Ask Claude for a web link, then check that it opens to the page it described.'],
409  ['Hold the scope', 'If Claude starts doing more than you asked, press Esc and restate the scope in one sentence.'],
410  ['Set a time box', 'Tell Claude you have ten minutes and ask it to aim for the most useful result in that time.'],
411  ['Close open loops', 'At the end of the day, ask Claude for a list of anything you started but did not finish.'],
412  ['Stay up to date', 'Run /status to see your version, then ask Claude how updates reach you.'],
413  ['Argue against itself', 'For an answer that matters, ask Claude to argue against its own conclusion.'],
414  ['Choose what to practise', 'Ask Claude to look at what you learned this week and suggest what to practise next.'],
415]
416
417const BY_THEME: Record<ThemeId, readonly Entry[]> = {
418  prompting: PROMPTING,
419  files: FILES,
420  commands: COMMANDS,
421  memory: MEMORY,
422  agents: AGENTS,
423  build: BUILD,
424  habits: HABITS,
425}
426
427export const PER_THEME = 52
428export const TOTAL = THEMES.length * PER_THEME + 1
429
430const CLOSER: Challenge = {
431  id: 'closer',
432  theme: 'prompting',
433  title: 'Write your next year',
434  task: 'Ask Claude to look back at what you have practised this year and help you write five challenges of your own for the year ahead.',
435  isCloser: true,
436}
437
438function deal(): readonly Challenge[] {
439  const list: Challenge[] = []
440  for (let round = 0; round < PER_THEME; round++) {
441    for (const theme of THEMES) {
442      const entry = BY_THEME[theme.id][round]
443      if (entry === undefined) continue
444      const [title, task] = entry
445      const id = `${theme.id}-${String(round + 1).padStart(2, '0')}`
446      list.push({ id, theme: theme.id, title, task, isCloser: false })
447    }
448  }
449  list.push(CLOSER)
450  return list
451}
452
453/** All challenges in the order they are dealt: one of each theme in turn, then the closer. */
454export const CHALLENGES: readonly Challenge[] = deal()
455
456export const BY_ID: ReadonlyMap<string, Challenge> = new Map(CHALLENGES.map(one => [one.id, one]))
457
458export function themeOf(challenge: Challenge): Theme {
459  return THEMES.find(theme => theme.id === challenge.theme) ?? THEMES[0]!
460}
461
hooks/layout.ts 111 lines
1// Pure sizing for the pane: every row is measured against the body width here,
2// so the drawing never relies on the surface wrapping or cutting text.
3
4export const COLORS = {
5  gold: '#F2C46D',
6  line: '#262B33',
7  muted: '#7D8590',
8  text: '#E6EDF3',
9  green: '#3DD68C',
10  amber: '#E3B341',
11  ink: '#0D1117',
12} as const
13
14export const TITLE = 'DAILY CHALLENGE'
15
16const DIGITS: Record<string, readonly [string, string, string]> = {
17  '0': ['┏━┓', '┃ ┃', '┗━┛'],
18  '1': ['╺┓ ', ' ┃ ', '╺┻╸'],
19  '2': ['╺━┓', '┏━┛', '┗━╸'],
20  '3': ['╺━┓', ' ━┫', '╺━┛'],
21  '4': ['╻ ╻', '┗━┫', '  ╹'],
22  '5': ['┏━╸', '┗━┓', '╺━┛'],
23  '6': ['┏━╸', '┣━┓', '┗━┛'],
24  '7': ['╺━┓', '  ┃', '  ╹'],
25  '8': ['┏━┓', '┣━┫', '┗━┛'],
26  '9': ['┏━┓', '┗━┫', '╺━┛'],
27}
28
29/** A number drawn three rows tall in heavy box-drawing characters. */
30export function bigNumber(value: number): [string, string, string] {
31  const glyphs = String(Math.max(0, Math.floor(value)))
32    .split('')
33    .map(digit => DIGITS[digit] ?? DIGITS['0']!)
34  return [0, 1, 2].map(row => glyphs.map(glyph => glyph[row]).join(' ')) as [string, string, string]
35}
36
37/** Greedy word wrap to `width` columns; a word longer than the width is cut. */
38export function wrap(text: string, width: number): string[] {
39  const room = Math.max(1, width)
40  const lines: string[] = []
41  let line = ''
42  for (const word of text.split(/\s+/).filter(Boolean)) {
43    const piece = word.length > room ? word.slice(0, room) : word
44    if (line === '') line = piece
45    else if (line.length + 1 + piece.length <= room) line += ` ${piece}`
46    else {
47      lines.push(line)
48      line = piece
49    }
50  }
51  if (line !== '') lines.push(line)
52  return lines.length === 0 ? [''] : lines
53}
54
55export function fit(text: string, width: number): string {
56  return text.length <= width ? text : text.slice(0, Math.max(0, width))
57}
58
59/** The gold rule under the title, as long as the title, then the hairline to the edge. */
60export function titleRule(cols: number): { gold: string; hairline: string } {
61  const goldWidth = Math.min(TITLE.length, cols)
62  return { gold: '━'.repeat(goldWidth), hairline: '─'.repeat(Math.max(0, cols - goldWidth)) }
63}
64
65/** A section heading in capitals followed by a hairline to the edge. */
66export function heading(label: string, cols: number): { label: string; hairline: string } {
67  const text = fit(label.toUpperCase(), cols)
68  const rest = cols - text.length - 1
69  return { label: text, hairline: rest > 0 ? ` ${'─'.repeat(rest)}` : '' }
70}
71
72export type TileLayout = { direction: 'row' | 'column'; width: number }
73
74export const TILE_LABELS = ['DAY STREAK', 'COMPLETED / 365', 'BEST STREAK'] as const
75
76/** Tiles side by side when three fit, else stacked at full width. */
77export function tileLayout(cols: number): TileLayout {
78  const inner = Math.max(...TILE_LABELS.map(label => label.length), bigNumber(365)[0].length)
79  const minimum = inner + 4
80  const sideBySide = Math.floor((cols - 2) / 3)
81  if (sideBySide >= minimum) return { direction: 'row', width: sideBySide }
82  return { direction: 'column', width: cols }
83}
84
85export type Chip = { key: string; label: string }
86
87/** Buttons on one row with a space between when they fit, else one per row. */
88export function chipRows(chips: readonly Chip[], cols: number): Chip[][] {
89  const total = chips.reduce((sum, chip) => sum + chip.label.length + 2, 0) + Math.max(0, chips.length - 1)
90  if (total <= cols) return [chips.slice()]
91  return chips.map(chip => [chip])
92}
93
94/** How many day cells fit and how wide each is: up to 14, two to four columns each. */
95export function dayGrid(cols: number): { count: number; cell: number } {
96  const count = Math.max(1, Math.min(14, Math.floor(cols / 2)))
97  const cell = Math.max(2, Math.min(4, Math.floor(cols / count)))
98  return { count, cell }
99}
100
101export function dayLabel(weekday: string, cell: number): string {
102  return weekday.slice(0, cell >= 3 ? 2 : 1)
103}
104
105/** The year bar's filled and empty widths; any progress shows at least one cell. */
106export function yearBar(done: number, total: number, cols: number): { filled: number; empty: number } {
107  const raw = Math.round((done / total) * cols)
108  const filled = Math.min(cols, done > 0 ? Math.max(1, raw) : 0)
109  return { filled, empty: cols - filled }
110}
111
hooks/logic.ts 186 lines
1import type { Progress } from '../types'
2
3import { BY_ID, CHALLENGES, TOTAL, type Challenge } from './challenges'
4
5export const EMPTY: Progress = { completed: {}, swap: null }
6
7const DAY = /^\d{4}-\d{2}-\d{2}$/
8
9/** Reads whatever the store held into a clean Progress, dropping anything unknown. */
10export function normalize(raw: unknown): Progress {
11  if (typeof raw !== 'object' || raw === null) return EMPTY
12  const source = raw as { completed?: unknown; swap?: unknown }
13  const completed: Record<string, string> = {}
14  if (typeof source.completed === 'object' && source.completed !== null) {
15    for (const [id, day] of Object.entries(source.completed)) {
16      if (BY_ID.has(id) && typeof day === 'string' && DAY.test(day)) completed[id] = day
17    }
18  }
19  const swap = source.swap as { day?: unknown; id?: unknown } | null | undefined
20  const isSwap =
21    typeof swap === 'object' &&
22    swap !== null &&
23    typeof swap.day === 'string' &&
24    typeof swap.id === 'string' &&
25    BY_ID.has(swap.id)
26
27  return { completed, swap: isSwap ? { day: swap.day as string, id: swap.id as string } : null }
28}
29
30function pad(value: number): string {
31  return String(value).padStart(2, '0')
32}
33
34function keyOf(date: Date): string {
35  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
36}
37
38function dateOf(day: string): Date {
39  const [year = 1970, month = 1, date = 1] = day.split('-').map(Number)
40  return new Date(year, month - 1, date, 12)
41}
42
43/** The local calendar day of an instant, as YYYY-MM-DD. */
44export function dayKey(ms: number): string {
45  return keyOf(new Date(ms))
46}
47
48export function shiftDay(day: string, days: number): string {
49  const date = dateOf(day)
50  date.setDate(date.getDate() + days)
51  return keyOf(date)
52}
53
54const WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
55const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
56
57export function weekdayName(day: string): string {
58  return WEEKDAYS[dateOf(day).getDay()] ?? ''
59}
60
61/** `Mon 12 Oct 2026`, or `12 Oct` when short. */
62export function formatDay(day: string, isShort = false): string {
63  const date = dateOf(day)
64  const month = MONTHS[date.getMonth()] ?? ''
65  if (isShort) return `${date.getDate()} ${month}`
66  return `${weekdayName(day).slice(0, 3)} ${date.getDate()} ${month} ${date.getFullYear()}`
67}
68
69export function remaining(progress: Progress): Challenge[] {
70  return CHALLENGES.filter(one => progress.completed[one.id] === undefined)
71}
72
73export function doneCount(progress: Progress): number {
74  return Object.keys(progress.completed).length
75}
76
77export function completedOn(progress: Progress, day: string): Challenge | undefined {
78  const id = Object.keys(progress.completed).find(key => progress.completed[key] === day)
79  return id === undefined ? undefined : BY_ID.get(id)
80}
81
82export type Today = {
83  /** Today's challenge; undefined once all are done and none was completed today. */
84  challenge: Challenge | undefined
85  isDone: boolean
86  isFinished: boolean
87}
88
89/**
90 * Today's challenge: the one completed today if any, else today's swap if it
91 * is still open, else the first in dealt order that was never completed.
92 */
93export function todayOf(progress: Progress, today: string): Today {
94  const done = completedOn(progress, today)
95  const isFinished = doneCount(progress) >= TOTAL
96  if (done !== undefined) return { challenge: done, isDone: true, isFinished }
97  const swap = progress.swap
98  if (swap !== null && swap.day === today && progress.completed[swap.id] === undefined) {
99    return { challenge: BY_ID.get(swap.id), isDone: false, isFinished }
100  }
101  return { challenge: remaining(progress)[0], isDone: false, isFinished }
102}
103
104export function canSwap(progress: Progress, today: string): boolean {
105  const now = todayOf(progress, today)
106  return !now.isDone && now.challenge !== undefined && remaining(progress).length > 1
107}
108
109/** Swaps today's challenge for a random one never completed; null when there is nothing to swap to. */
110export function swapToday(progress: Progress, today: string, random: () => number): Progress | null {
111  if (!canSwap(progress, today)) return null
112  const current = todayOf(progress, today).challenge
113  const pool = remaining(progress).filter(one => one.id !== current?.id)
114  const pick = pool[Math.min(pool.length - 1, Math.floor(random() * pool.length))]
115  if (pick === undefined) return null
116  return { ...progress, swap: { day: today, id: pick.id } }
117}
118
119/** Marks today's challenge complete; null when today is already done or nothing is left. */
120export function completeToday(progress: Progress, today: string): Progress | null {
121  const now = todayOf(progress, today)
122  if (now.isDone || now.challenge === undefined) return null
123  return { ...progress, completed: { ...progress.completed, [now.challenge.id]: today } }
124}
125
126/** Takes back today's completion; null when nothing was completed today. */
127export function undoToday(progress: Progress, today: string): Progress | null {
128  const ids = Object.keys(progress.completed).filter(id => progress.completed[id] === today)
129  if (ids.length === 0) return null
130  const completed = { ...progress.completed }
131  for (const id of ids) delete completed[id]
132  return { ...progress, completed }
133}
134
135function activeDays(progress: Progress): Set<string> {
136  return new Set(Object.values(progress.completed))
137}
138
139/** Days in a row with a completion, ending today, or ending yesterday while today is still open. */
140export function streak(progress: Progress, today: string): number {
141  const days = activeDays(progress)
142  let day = days.has(today) ? today : shiftDay(today, -1)
143  let count = 0
144  while (days.has(day)) {
145    count += 1
146    day = shiftDay(day, -1)
147  }
148  return count
149}
150
151export function bestStreak(progress: Progress): number {
152  const days = [...activeDays(progress)].sort()
153  let best = 0
154  let run = 0
155  let previous: string | undefined
156  for (const day of days) {
157    run = previous !== undefined && shiftDay(previous, 1) === day ? run + 1 : 1
158    best = Math.max(best, run)
159    previous = day
160  }
161  return best
162}
163
164export type DayCell = { day: string; weekday: string; isDone: boolean; isToday: boolean }
165
166/** The last `count` days, oldest first, ending today. */
167export function lastDays(progress: Progress, today: string, count: number): DayCell[] {
168  const days = activeDays(progress)
169  const cells: DayCell[] = []
170  for (let back = count - 1; back >= 0; back--) {
171    const day = shiftDay(today, -back)
172    cells.push({ day, weekday: weekdayName(day), isDone: days.has(day), isToday: back === 0 })
173  }
174  return cells
175}
176
177export function coachPrompt(challenge: Challenge): string {
178  return [
179    `Please coach me through today's Claude Code challenge, one step at a time.`,
180    `After each step, stop and wait for me to tell you I am ready before you give the next one.`,
181    '',
182    `Challenge: ${challenge.title}`,
183    challenge.task,
184  ].join('\n')
185}
186
types/index.d.ts 13 lines
1export type Swap = { day: string; id: string }
2
3export type Progress = {
4  completed: Record<string, string>
5  swap: Swap | null
6}
7
8declare module 'claude-code' {
9  interface PluginState {
10    'daily-challenge': { progress: Progress | null }
11  }
12}
13