.claude/skills/pikku-cron/SKILL.md
Use when adding scheduled tasks, recurring jobs, or cron-based automation to a Pikku app. Covers wireScheduler, cron expressions, scheduled task wire object, and scheduler middleware. TRIGGER when: code uses wireScheduler, user asks about cron, scheduled tasks, recurring jobs, or "run every X minutes/hours". DO NOT TRIGGER when: user asks about background jobs with retries (use pikku-queue) or event-driven triggers (use pikku-trigger).
npx skillsauth add pikkujs/pikku pikku-cronInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Wire Pikku functions to run on a schedule using cron expressions. Uses pikkuVoidFunc (no input/output).
pikku info functions --verbose # See existing functions and their types
pikku info tags --verbose # Understand project organization
See pikku-concepts for the core mental model.
wireScheduler(config)import { wireScheduler } from '@pikku/core/scheduler'
wireScheduler({
name: string, // Unique scheduler name
schedule: string, // Cron expression
func: PikkuVoidFunc, // Must be pikkuVoidFunc (no input/output)
middleware?: PikkuMiddleware[],
})
wire.scheduledTask)Inside scheduled functions:
wire.scheduledTask.name // Scheduler name
wire.scheduledTask.schedule // Cron expression string
wire.scheduledTask.executionTime // When this execution was triggered
wire.scheduledTask.skip(reason) // Skip this execution (no error)
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * *
Common patterns:
| Expression | Meaning |
| ------------- | -------------------------- |
| */5 * * * * | Every 5 minutes |
| 0 9 * * * | Daily at 9:00 AM |
| 0 9 * * 1 | Every Monday at 9:00 AM |
| 0 0 1 * * | First of month at midnight |
| 0 */6 * * * | Every 6 hours |
| 30 2 * * 0 | Sundays at 2:30 AM |
const dailySummary = pikkuVoidFunc({
title: 'Daily Summary',
func: async ({ db, emailService, logger }) => {
logger.info('Generating daily summary')
const stats = await db.getDailyStats()
await emailService.sendSummary(stats)
},
})
wireScheduler({
name: 'dailySummary',
schedule: '0 9 * * *',
func: dailySummary,
})
const weeklyCleanup = pikkuVoidFunc({
title: 'Weekly Cleanup',
func: async ({ db, logger }, _input, wire) => {
logger.info(`Running: ${wire.scheduledTask.name}`)
logger.info(`Schedule: ${wire.scheduledTask.schedule}`)
logger.info(`Execution time: ${wire.scheduledTask.executionTime}`)
const staleCount = await db.countStaleTodos()
if (staleCount === 0) {
wire.scheduledTask.skip('No stale todos found')
return
}
await db.deleteCompletedTodos({ olderThan: '30d' })
logger.info(`Cleaned ${staleCount} stale todos`)
},
})
wireScheduler({
name: 'weeklyCleanup',
schedule: '0 0 * * 0',
func: weeklyCleanup,
})
const schedulerMetrics = pikkuMiddleware(
async ({ logger }, { scheduledTask }, next) => {
const start = Date.now()
logger.info(`Task started: ${scheduledTask.name}`)
try {
await next()
logger.info(`Task completed: ${scheduledTask.name}`, {
duration: Date.now() - start,
})
} catch (error) {
logger.error(`Task failed: ${scheduledTask.name}`, {
error: error.message,
duration: Date.now() - start,
})
throw error
}
}
)
wireScheduler({
name: 'dailySummary',
schedule: '0 9 * * *',
func: dailySummary,
middleware: [schedulerMetrics],
})
// functions/scheduled.functions.ts
export const dailySummary = pikkuVoidFunc({
title: 'Daily Summary',
func: async ({ db, emailService, logger }) => {
const stats = await db.getDailyStats()
await emailService.sendSummary(stats)
logger.info('Daily summary sent', { stats })
},
})
export const cleanupExpired = pikkuVoidFunc({
title: 'Cleanup Expired',
func: async ({ db, logger }, _input, wire) => {
const count = await db.countExpiredSessions()
if (count === 0) {
wire.scheduledTask.skip('No expired sessions')
return
}
await db.deleteExpiredSessions()
logger.info(`Cleaned ${count} expired sessions`)
},
})
export const syncInventory = pikkuVoidFunc({
title: 'Sync Inventory',
func: async ({ inventoryApi, db, logger }) => {
const updates = await inventoryApi.getChanges()
await db.applyInventoryUpdates(updates)
logger.info(`Synced ${updates.length} inventory changes`)
},
})
// wirings/scheduler.wiring.ts
wireScheduler({
name: 'dailySummary',
schedule: '0 9 * * *',
func: dailySummary,
})
wireScheduler({
name: 'cleanupExpired',
schedule: '0 */6 * * *',
func: cleanupExpired,
})
wireScheduler({
name: 'syncInventory',
schedule: '*/15 * * * *',
func: syncInventory,
})
documentation
Standard cleanup to run right after a Pikku template is cloned or scaffolded into a new project. TRIGGER when: a Pikku template was just cloned/scaffolded (via `pikku create`, `git clone <template>`, or the user says "I cloned the kanban template / starter / template"), or the working tree still looks like an untouched template (template README, placeholder `@project/*` name in package.json). DO NOT TRIGGER when: working in an established project mid-feature, or editing the template repo itself.
development
Make a Pikku frontend work in both English (LTR) and Arabic / right-to-left languages. Direction is derived from the active locale, applied once at the document root, and the layout mirrors itself — but only if styling is written flow-relative (margin-inline-start, text-align: start, Mantine ms/me) instead of left/right. TRIGGER when: adding Arabic (or Hebrew/Farsi/Urdu), asked to "support RTL / right-to-left / bidi / mirror the layout", or writing layout styles in an app that may run RTL. Builds on pikku-i18n (an RTL language is just another locale file). DO NOT TRIGGER for backend functions or for LTR-only copy changes.
development
Wire i18n into a Pikku frontend (Vite SPA, Vite SSR, or Next.js app-router) with react-i18next + i18next. English by default, every user-facing string goes through a `t()` token, and additional languages are served under `/de` `/es` URL prefixes. TRIGGER when: scaffolding or editing a frontend and writing user-facing text, adding a second language, or asked to "make this translatable / use tokens / add i18n". DO NOT TRIGGER for backend functions, error messages thrown from functions, or log output.
development
Use when translating an n8n Code node body into a real Pikku function body. Triggered when the user opens or points at a stub generated by @pikku/n8n-import (look for `STUB — generated from n8n Code node` in the file's JSDoc), or when the user says 'translate this n8n code', 'port this n8n code node', 'finish the codeStub__... function', etc. The stub file is a `pikkuSessionlessFunc` with a Zod input/output, a JSDoc preserving the original n8n JavaScript verbatim, and a `throw new Error('… — implement me')` body.