Deployment Automation
aiskillstore/marketplace
Automate application deployment to cloud platforms and servers.
Cron expression explainer, validator, and converter. An agent skill from LeoYeAI/openclaw-master-skills.
$ npx skills add LeoYeAI/openclaw-master-skills --skill phy-cron-explainer -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills phy-cron-explainer --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/phy-cron-explainer .claude/skills/phy-cron-explainer && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "phy-cron-explainer" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-cron-explainer into .claude/skills/phy-cron-explainer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "phy-cron-explainer", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-cron-explainerType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add LeoYeAI/openclaw-master-skills --skill phy-cron-explainer -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills phy-cron-explainer --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/phy-cron-explainer .agents/skills/phy-cron-explainer && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "phy-cron-explainer" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-cron-explainer into .agents/skills/phy-cron-explainer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "phy-cron-explainer", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add LeoYeAI/openclaw-master-skills --skill phy-cron-explainer -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills phy-cron-explainer --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/phy-cron-explainer .cursor/skills/phy-cron-explainer && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "phy-cron-explainer" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-cron-explainer into .cursor/skills/phy-cron-explainer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "phy-cron-explainer", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/LeoYeAI/openclaw-master-skills.git --path skills/phy-cron-explainer--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add LeoYeAI/openclaw-master-skills --skill phy-cron-explainer -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills phy-cron-explainer --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/phy-cron-explainer .gemini/skills/phy-cron-explainer && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "phy-cron-explainer" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-cron-explainer into .gemini/skills/phy-cron-explainer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "phy-cron-explainer", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install LeoYeAI/openclaw-master-skills phy-cron-explainerInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add LeoYeAI/openclaw-master-skills --skill phy-cron-explainer -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/phy-cron-explainer .github/skills/phy-cron-explainer && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "phy-cron-explainer" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-cron-explainer into .github/skills/phy-cron-explainer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "phy-cron-explainer", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add LeoYeAI/openclaw-master-skills --skill phy-cron-explainer -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills phy-cron-explainer --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/phy-cron-explainer .opencode/skills/phy-cron-explainer && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "phy-cron-explainer" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-cron-explainer into .opencode/skills/phy-cron-explainer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "phy-cron-explainer", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
phy-cron-explainerCron expression explainer, validator, and converter. An agent skill from LeoYeAI/openclaw-master-skills.
Phy Cron Explainer is an agent skill from LeoYeAI/openclaw-master-skills. Cron expression explainer, validator, and converter. Translates any cron expression into plain English ("0 /6 " → "Every 6 hours, at minute 0"), shows the next 5 scheduled run times with exact dates, detects impossible/conflicting expressions (Feb 30, conflicting day-of-week + day-of-month), identifies suspicious patterns (every minute in production, very-frequent schedules on expensive operations), and converts plain English descriptions to cron ("every weekday at 9am" → "0 9 1-5"). Supports all major flavors…
Its SKILL.md is about 7k tokens, which your agent loads only when the skill is triggered. The skill folder holds 1 other file (for example `_meta.json`).
It sits in Productivity & Automation, covering Scheduled and recurring tasks, Plain language and style rules and Container orchestration. It works with GitHub Actions, Kubernetes and Amazon Web Services. The repository describes itself as: 🧠 Curated collection of 1209+ best OpenClaw skills — weekly updated by MyClaw.ai. The licence is Apache-2.0.
5 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit e5199b5. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
awsFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use aws, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Phy Cron Explainer loads about 7k tokens when it runs. Until then it costs about 250 tokens; SKILL.md has 218 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from LeoYeAI/openclaw-master-skills at commit e5199b5, republished under its Apache-2.0 licence (© LeoYeAI). 218 words, ~6,997 tokens.
.claude/skills/phy-cron-explainer/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.0 2 * * 1
What does that mean? Every Monday at 2am. How about */5 9-17 * * 1-5? Every 5 minutes between 9am and 5pm on weekdays.
You shouldn't need to count fields in your head. This skill translates any cron expression into plain English, shows the next scheduled runs, catches impossible expressions before they go to production, converts English descriptions to cron, and scans your CI/K8s configs to audit all your schedules in one shot.
All major cron flavors. Zero external API.
# Option 1: Explain a cron expression
/cron "0 2 * * 1"
/cron "*/5 9-17 * * 1-5"
/cron "0 0 1,15 * *"
# Option 2: Explain a special string
/cron "@daily"
/cron "@reboot"
/cron "@every 6h" # Kubernetes/Go format
# Option 3: Convert English to cron
/cron "every weekday at 9am"
/cron "every 5 minutes during business hours"
/cron "first day of every month at midnight"
/cron "every 15 minutes from 8am to 6pm on weekdays"
# Option 4: Show next N run times
/cron "0 */6 * * *" --next 10
# Option 5: Validate an expression
/cron "0 25 * * *" --validate # 25th hour — invalid
# Option 6: Scan project files for cron schedules
/cron --scan . # scans GitHub Actions, Kubernetes YAML, crontab
/cron --scan .github/workflows/
# Option 7: Detect flavor
/cron "0/5 * * * ? *" --flavor aws-eventbridgefrom datetime import datetime, timedelta
import re
from typing import Optional
# ── Special strings ──────────────────────────────────────────────────────────
SPECIAL_STRINGS = {
'@yearly': ('0 0 1 1 *', 'Once a year, at midnight on January 1st'),
'@annually': ('0 0 1 1 *', 'Once a year, at midnight on January 1st'),
'@monthly': ('0 0 1 * *', 'Once a month, at midnight on the 1st'),
'@weekly': ('0 0 * * 0', 'Once a week, at midnight on Sunday'),
'@daily': ('0 0 * * *', 'Every day at midnight'),
'@midnight': ('0 0 * * *', 'Every day at midnight'),
'@hourly': ('0 * * * *', 'Every hour, at the top of the hour'),
'@reboot': (None, 'Once, on system startup/reboot'),
'@every_1m': ('* * * * *', 'Every minute'),
'@every_5m': ('*/5 * * * *','Every 5 minutes'),
'@every_1h': ('0 * * * *', 'Every hour'),
}
WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday']
MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December']
def detect_flavor(expression: str) -> str:
"""Detect cron flavor from expression structure."""
parts = expression.strip().split()
if len(parts) == 6:
# Could be 6-field with seconds (Quartz/Spring) or 6-field Linux
if re.search(r'[?L#W]', parts[5]):
return 'quartz'
return '6field-seconds'
if len(parts) == 7:
return 'quartz-7field'
if len(parts) == 5:
return 'standard'
return 'unknown'
def explain_field(value: str, field: str, names: Optional[list] = None) -> str:
"""Convert a single cron field to English."""
if value == '*':
return None # "every" is implied
if value == '?':
return None # Quartz "any" — implied
# Step: */N or start/N
step_match = re.match(r'^(\*|\d+)/(\d+)$', value)
if step_match:
start = step_match.group(1)
step = int(step_match.group(2))
if field == 'minute':
return f'every {step} minute{"s" if step > 1 else ""}'
if field == 'hour':
return f'every {step} hour{"s" if step > 1 else ""}'
if field == 'day_of_month':
return f'every {step} day{"s" if step > 1 else ""}'
if field == 'month':
return f'every {step} month{"s" if step > 1 else ""}'
if field == 'second':
return f'every {step} second{"s" if step > 1 else ""}'
return f'every {step}'
# Range: N-M
range_match = re.match(r'^(\d+)-(\d+)$', value)
if range_match:
lo, hi = int(range_match.group(1)), int(range_match.group(2))
if names:
lo_name = names[lo] if lo < len(names) else str(lo)
hi_name = names[hi] if hi < len(names) else str(hi)
return f'{lo_name} through {hi_name}'
if field == 'hour':
lo_fmt = f'{lo}:00 {"AM" if lo < 12 else "PM"}'
hi_fmt = f'{hi % 12 or 12}:00 {"AM" if hi < 12 else "PM"}'
return f'between {lo_fmt} and {hi_fmt}'
return f'{lo} to {hi}'
# Range with step: N-M/S
range_step_match = re.match(r'^(\d+)-(\d+)/(\d+)$', value)
if range_step_match:
lo = int(range_step_match.group(1))
hi = int(range_step_match.group(2))
step = int(range_step_match.group(3))
range_desc = explain_field(f'{lo}-{hi}', field, names)
return f'every {step} {field.replace("_", " ")}{"s" if step > 1 else ""} {range_desc}'
# List: N,M,P
if ',' in value:
parts = [p.strip() for p in value.split(',')]
if names:
named = [names[int(p)] if p.isdigit() and int(p) < len(names) else p for p in parts]
else:
named = parts
if len(named) == 2:
return f'{named[0]} and {named[1]}'
return ', '.join(named[:-1]) + f', and {named[-1]}'
# Single value
if value.isdigit():
n = int(value)
if field == 'hour':
if n == 0: return 'midnight'
if n == 12: return 'noon'
if n < 12: return f'{n}:00 AM'
return f'{n - 12}:00 PM'
if field == 'minute':
return f'minute {n}' if n != 0 else 'on the hour'
if field == 'second':
return f'second {n}'
if field == 'day_of_month':
suffix = 'st' if n % 10 == 1 and n != 11 else \
'nd' if n % 10 == 2 and n != 12 else \
'rd' if n % 10 == 3 and n != 13 else 'th'
return f'the {n}{suffix}'
if field == 'month' and names:
return names[n - 1] if 1 <= n <= 12 else str(n)
if field == 'day_of_week' and names:
return names[n % 7]
return str(n)
# Special Quartz: L (last), W (nearest weekday), # (Nth weekday)
if value.upper() == 'L' and field == 'day_of_month':
return 'the last day of the month'
if value.upper() == 'L' and field == 'day_of_week':
return 'the last weekday of the month'
lw_match = re.match(r'^(\d+)W$', value, re.I)
if lw_match:
return f'the weekday nearest the {lw_match.group(1)}th'
hash_match = re.match(r'^(\d+)#(\d+)$', value)
if hash_match:
day = int(hash_match.group(1))
nth = int(hash_match.group(2))
ordinals = ['', '1st', '2nd', '3rd', '4th', '5th']
day_name = WEEKDAY_NAMES[day % 7]
return f'the {ordinals[nth]} {day_name} of the month'
return value # fallback — return as-is
def explain_cron(expression: str) -> dict:
"""
Parse a cron expression and return a human-readable explanation.
Returns dict with: explanation, flavor, issues, fields
"""
expr = expression.strip()
# Handle special strings
expr_lower = expr.lower()
if expr_lower in SPECIAL_STRINGS:
cron_equiv, desc = SPECIAL_STRINGS[expr_lower]
return {
'expression': expr,
'flavor': 'special',
'explanation': desc,
'next_runs': [] if expr_lower == '@reboot' else None,
'issues': [],
'fields': {},
}
# Normalize: replace month/weekday names with numbers
expr = expr.upper()
month_map = {'JAN':1,'FEB':2,'MAR':3,'APR':4,'MAY':5,'JUN':6,
'JUL':7,'AUG':8,'SEP':9,'OCT':10,'NOV':11,'DEC':12}
dow_map = {'SUN':0,'MON':1,'TUE':2,'WED':3,'THU':4,'FRI':5,'SAT':6}
for name, num in {**month_map, **dow_map}.items():
expr = expr.replace(name, str(num))
parts = expr.split()
flavor = detect_flavor(expression)
issues = []
# Map parts to field names by flavor
if flavor == '6field-seconds':
if len(parts) != 6:
issues.append({'severity': 'ERROR', 'message': f'Expected 6 fields, got {len(parts)}'})
return {'expression': expression, 'flavor': flavor, 'explanation': 'Invalid', 'issues': issues, 'fields': {}}
field_names = ['second', 'minute', 'hour', 'day_of_month', 'month', 'day_of_week']
field_values = dict(zip(field_names, parts))
elif len(parts) == 5:
field_names = ['minute', 'hour', 'day_of_month', 'month', 'day_of_week']
field_values = dict(zip(field_names, parts))
else:
issues.append({'severity': 'ERROR', 'message': f'Unrecognized format: {len(parts)} fields'})
return {'expression': expression, 'flavor': flavor, 'explanation': 'Invalid', 'issues': issues, 'fields': {}}
# Explain each field
minute_desc = explain_field(field_values.get('minute', '*'), 'minute')
hour_desc = explain_field(field_values.get('hour', '*'), 'hour')
dom_desc = explain_field(field_values.get('day_of_month', '*'), 'day_of_month')
month_desc = explain_field(field_values.get('month', '*'), 'month', MONTH_NAMES)
dow_desc = explain_field(field_values.get('day_of_week', '*'), 'day_of_week', WEEKDAY_NAMES)
sec_desc = explain_field(field_values.get('second', None) or '0', 'second') if flavor == '6field-seconds' else None
# Compose natural language description
parts_en = []
# Frequency
if minute_desc and minute_desc.startswith('every'):
parts_en.append(minute_desc.capitalize())
elif minute_desc and minute_desc.startswith('minute'):
if hour_desc:
parts_en.append(f'At {hour_desc}, {minute_desc}')
else:
parts_en.append(f'Every hour, {minute_desc}')
elif not minute_desc and not hour_desc:
parts_en.append('Every minute')
elif not minute_desc and hour_desc:
parts_en.append(f'Every hour at {hour_desc}')
elif minute_desc == 'on the hour' and hour_desc:
parts_en.append(f'At {hour_desc}')
else:
if hour_desc and minute_desc:
# Format as clock time if both are single values
parts_en.append(f'At {hour_desc}')
if minute_desc and not minute_desc.startswith('on the hour'):
parts_en[-1] += f', {minute_desc}'
elif hour_desc:
parts_en.append(f'At {hour_desc}')
elif minute_desc:
parts_en.append(f'Every hour, at {minute_desc}')
if sec_desc and sec_desc != 'second 0':
parts_en.append(f'at {sec_desc}')
# Day constraints
if dom_desc and dow_desc:
parts_en.append(f'on {dom_desc} or {dow_desc}')
elif dom_desc:
parts_en.append(f'on {dom_desc}')
elif dow_desc:
parts_en.append(f'on {dow_desc}{"s" if not dow_desc.endswith("day") else ""}')
# Month constraint
if month_desc:
parts_en.append(f'in {month_desc}')
explanation = ', '.join(parts_en) if parts_en else 'Every minute'
# ── Validation ────────────────────────────────────────────────────────────
# Every minute in production
if field_values.get('minute', '') == '*' and field_values.get('hour', '') == '*':
issues.append({
'severity': 'WARNING',
'message': 'Runs every minute — confirm this is intentional for a production schedule',
})
# Validate numeric bounds
BOUNDS = {
'minute': (0, 59), 'hour': (0, 23),
'day_of_month': (1, 31), 'month': (1, 12), 'day_of_week': (0, 7),
'second': (0, 59),
}
for fname, fval in field_values.items():
if fname not in BOUNDS or fval in ('*', '?'):
continue
lo, hi = BOUNDS[fname]
# Extract all numeric values
nums = re.findall(r'\d+', fval)
for n_str in nums:
n = int(n_str)
if n < lo or n > hi:
issues.append({
'severity': 'ERROR',
'message': f'Invalid {fname} value: {n} (valid range: {lo}-{hi})',
})
# February 30 / 31 impossibility
month_val = field_values.get('month', '*')
dom_val = field_values.get('day_of_month', '*')
if month_val == '2' and dom_val not in ('*', '?'):
days = re.findall(r'\d+', dom_val)
for d in days:
if int(d) > 29:
issues.append({
'severity': 'ERROR',
'message': f'day-of-month={d} in month=2 (February) — this date never occurs',
})
elif int(d) == 29:
issues.append({
'severity': 'WARNING',
'message': 'day-of-month=29 in February — only runs on leap years',
})
# Detect conflicting DOW + DOM (both specified — runs on OR condition, often surprising)
if dom_val not in ('*', '?') and field_values.get('day_of_week', '*') not in ('*', '?'):
issues.append({
'severity': 'INFO',
'message': (
'Both day-of-month and day-of-week are specified — '
'standard cron uses OR (runs if EITHER matches). '
'Use ? for one field if you want AND behavior (Quartz only).'
),
})
return {
'expression': expression,
'flavor': flavor,
'explanation': explanation,
'fields': field_values,
'issues': issues,
}from datetime import datetime, timezone
def next_runs(expression: str, count: int = 5, after: Optional[datetime] = None) -> list[datetime]:
"""
Calculate the next N run times for a cron expression.
Uses croniter if available, otherwise falls back to manual calculation.
"""
after = after or datetime.now(tz=timezone.utc)
try:
from croniter import croniter
cron = croniter(expression, after)
return [cron.get_next(datetime) for _ in range(count)]
except ImportError:
pass
# Fallback: install croniter
import subprocess
subprocess.run(['pip', 'install', 'croniter', '-q'], check=True)
from croniter import croniter
cron = croniter(expression, after)
return [cron.get_next(datetime) for _ in range(count)]
def format_run_times(runs: list[datetime], timezone_name: str = 'UTC') -> list[str]:
"""Format run times for display."""
now = datetime.now(tz=timezone.utc)
result = []
for dt in runs:
diff = dt - now
if diff.total_seconds() < 60:
rel = 'in less than a minute'
elif diff.total_seconds() < 3600:
mins = int(diff.total_seconds() / 60)
rel = f'in {mins} minute{"s" if mins > 1 else ""}'
elif diff.total_seconds() < 86400:
hours = int(diff.total_seconds() / 3600)
rel = f'in {hours} hour{"s" if hours > 1 else ""}'
else:
days = int(diff.total_seconds() / 86400)
rel = f'in {days} day{"s" if days > 1 else ""}'
result.append(f'{dt.strftime("%Y-%m-%d %H:%M:%S")} UTC ({rel})')
return resultimport re
def english_to_cron(description: str) -> dict:
"""
Convert a plain-English schedule description to cron expression.
Returns {'cron': str, 'explanation': str, 'confidence': str}
"""
desc = description.lower().strip()
# Special patterns
if re.search(r'every\s+minute', desc):
return {'cron': '* * * * *', 'explanation': 'Every minute', 'confidence': 'HIGH'}
if re.search(r'every\s+hour', desc):
return {'cron': '0 * * * *', 'explanation': 'Every hour', 'confidence': 'HIGH'}
if re.search(r'midnight|every\s+(?:day\s+at\s+)?12\s*am', desc):
return {'cron': '0 0 * * *', 'explanation': 'Every day at midnight', 'confidence': 'HIGH'}
if re.search(r'noon|every\s+(?:day\s+at\s+)?12\s*pm', desc):
return {'cron': '0 12 * * *', 'explanation': 'Every day at noon', 'confidence': 'HIGH'}
# Extract time: "at 9am", "at 3:30pm", "at 14:00"
time_match = re.search(
r'at\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?',
desc, re.I
)
hour = minute = None
if time_match:
hour = int(time_match.group(1))
minute = int(time_match.group(2)) if time_match.group(2) else 0
ampm = (time_match.group(3) or '').lower()
if ampm == 'pm' and hour != 12:
hour += 12
elif ampm == 'am' and hour == 12:
hour = 0
# Day of week
dow_map = {
'sunday': 0, 'monday': 1, 'tuesday': 2, 'wednesday': 3,
'thursday': 4, 'friday': 5, 'saturday': 6,
'weekday': '1-5', 'weekdays': '1-5',
'weekend': '0,6', 'weekends': '0,6',
'every day': '*', 'daily': '*',
}
dow = '*'
for key, val in dow_map.items():
if key in desc:
dow = str(val)
break
# Interval: "every N minutes/hours"
interval_match = re.search(r'every\s+(\d+)\s+(minute|hour|min|hr)s?', desc, re.I)
if interval_match:
n = int(interval_match.group(1))
unit = interval_match.group(2).lower()
# Business hours modifier
if re.search(r'business\s+hours?|work\s+hours?|9\s*(?:am|to)\s*5', desc):
if unit in ('minute', 'min'):
cron = f'*/{n} 9-17 * * 1-5'
return {'cron': cron,
'explanation': f'Every {n} minutes, between 9am and 5pm, Monday–Friday',
'confidence': 'HIGH'}
if unit in ('hour', 'hr'):
cron = f'0 */{ n} * * 1-5' if n > 1 else f'0 9-17 * * 1-5'
return {'cron': cron,
'explanation': f'Every {n} hour{"s" if n>1 else ""}, weekdays only',
'confidence': 'MEDIUM'}
if unit in ('minute', 'min'):
cron_min = f'*/{n}' if n > 1 else '*'
cron = f'{cron_min} * * * *'
return {'cron': cron,
'explanation': f'Every {n} minute{"s" if n>1 else ""}',
'confidence': 'HIGH'}
if unit in ('hour', 'hr'):
cron = f'0 */{n} * * *'
return {'cron': cron,
'explanation': f'Every {n} hour{"s" if n>1 else ""}',
'confidence': 'HIGH'}
# "first day of every month"
if re.search(r'first\s+day\s+of\s+(?:every|each)\s+month', desc):
h = hour if hour is not None else 0
m = minute if minute is not None else 0
cron = f'{m} {h} 1 * *'
time_str = f'at {h:02d}:{m:02d}' if hour is not None else 'at midnight'
return {'cron': cron,
'explanation': f'On the 1st of every month, {time_str}',
'confidence': 'HIGH'}
# "every weekday at Xam"
if hour is not None and dow != '*':
m = minute if minute is not None else 0
cron = f'{m} {hour} * * {dow}'
dow_names = {'1-5': 'Monday–Friday', '0,6': 'Saturday and Sunday',
'0': 'Sunday', '1': 'Monday', '*': 'every day'}
dow_str = dow_names.get(str(dow), f'day {dow}')
return {'cron': cron,
'explanation': f'At {hour:02d}:{m:02d}, {dow_str}',
'confidence': 'HIGH'}
# Simple "every day at X"
if hour is not None:
m = minute if minute is not None else 0
cron = f'{m} {hour} * * *'
am_pm = 'AM' if hour < 12 else 'PM'
h12 = hour % 12 or 12
return {'cron': cron,
'explanation': f'Every day at {h12}:{m:02d} {am_pm}',
'confidence': 'HIGH'}
return {
'cron': None,
'explanation': 'Could not parse — try: "every weekday at 9am", "every 5 minutes", "first day of month at midnight"',
'confidence': 'NONE',
}import glob
import re
import yaml # pyyaml
from pathlib import Path
def scan_for_cron_schedules(root: str = '.') -> list[dict]:
"""Find all cron expressions in GitHub Actions, Kubernetes, and crontab files."""
schedules = []
# GitHub Actions schedule triggers
for fpath in glob.glob(f'{root}/.github/workflows/*.yml', recursive=False):
fpath += '' if fpath.endswith('.yml') else ''
for path in glob.glob(f'{root}/.github/workflows/*.yml') + glob.glob(f'{root}/.github/workflows/*.yaml'):
try:
content = Path(path).read_text(errors='replace')
doc = yaml.safe_load(content)
if not isinstance(doc, dict):
continue
on = doc.get('on', doc.get(True, {}))
if isinstance(on, dict):
schedule = on.get('schedule', [])
if isinstance(schedule, list):
for item in schedule:
if isinstance(item, dict) and 'cron' in item:
schedules.append({
'file': path,
'source': 'github-actions',
'expression': item['cron'],
'context': doc.get('name', 'workflow'),
})
except Exception:
continue
# Kubernetes CronJob specs
for path in glob.glob(f'{root}/**/*.yaml', recursive=True) + glob.glob(f'{root}/**/*.yml', recursive=True):
if any(skip in path for skip in ['.git', 'node_modules', 'vendor']):
continue
try:
content = Path(path).read_text(errors='replace')
if 'CronJob' not in content:
continue
doc = yaml.safe_load(content)
if isinstance(doc, dict) and doc.get('kind') == 'CronJob':
schedule = (doc.get('spec', {}) or {}).get('schedule')
if schedule:
schedules.append({
'file': path,
'source': 'kubernetes-cronjob',
'expression': schedule,
'context': doc.get('metadata', {}).get('name', 'cronjob'),
})
except Exception:
continue
# crontab files
for crontab_path in ['crontab', 'crontab.txt', 'etc/cron.d', 'docker/cron']:
full = f'{root}/{crontab_path}'
if not Path(full).exists():
continue
try:
for i, line in enumerate(Path(full).read_text().splitlines(), 1):
line = line.strip()
if line.startswith('#') or not line:
continue
# crontab line format: min hour dom month dow command
parts = line.split()
if len(parts) >= 6:
expr = ' '.join(parts[:5])
command = ' '.join(parts[5:])
schedules.append({
'file': crontab_path,
'source': 'crontab',
'expression': expr,
'context': command[:60],
'line': i,
})
except Exception:
continue
return schedules## Cron Expression Analysis
---
### Expression: `*/5 9-17 * * 1-5`
**Translation:** Every 5 minutes, between 9:00 AM and 5:00 PM, on Monday through Friday
**Flavor:** Standard Unix (5-field)
**Field Breakdown:**
| Field | Value | Meaning |
|-------|-------|---------|
| Minute | `*/5` | Every 5 minutes |
| Hour | `9-17` | Between 9:00 AM and 5:00 PM |
| Day-of-month | `*` | Every day |
| Month | `*` | Every month |
| Day-of-week | `1-5` | Monday through Friday |
**Next 5 scheduled runs:**2026-03-19 09:00:00 UTC (in 3 minutes) 2026-03-19 09:05:00 UTC (in 8 minutes) 2026-03-19 09:10:00 UTC (in 13 minutes) 2026-03-19 09:15:00 UTC (in 18 minutes) 2026-03-19 09:20:00 UTC (in 23 minutes)
**Issues:** None ✅
---
### Expression: `0 0 30 2 *`
**Translation:** At midnight, on the 30th of February
**Issues:**
- 🔴 ERROR: `day-of-month=30 in month=2 (February)` — **this date never occurs**. Job will never run.
**Fix:** Did you mean the last day of every month?
---
### English → Cron Examples
| Input | Output | Explanation |
|-------|--------|-------------|
| `"every weekday at 9am"` | `0 9 * * 1-5` | At 09:00, Monday–Friday |
| `"every 15 minutes during business hours"` | `*/15 9-17 * * 1-5` | Every 15 min, 9am–5pm, Mon–Fri |
| `"first day of every month at midnight"` | `0 0 1 * *` | At midnight on the 1st |
| `"every Sunday at 2:30am"` | `30 2 * * 0` | At 02:30, Sunday |
| `"every 6 hours"` | `0 */6 * * *` | At minute 0, every 6 hours |
---
### Project Scan: .github/workflows/
Found 3 cron schedules:
| File | Schedule | Explanation | Issues |
|------|----------|-------------|--------|
| `.github/workflows/deploy.yml` | `0 2 * * 1` | Every Monday at 2:00 AM | ✅ OK |
| `.github/workflows/cleanup.yml` | `*/5 * * * *` | Every 5 minutes | ⚠️ Very frequent — confirm intentional |
| `.github/workflows/report.yml` | `0 0 30 2 *` | Feb 30th at midnight | 🔴 NEVER RUNS (Feb 30 doesn't exist) |
---
### Flavor Reference
| Flavor | Fields | Example | Notes |
|--------|--------|---------|-------|
| Standard Unix | `min hour dom month dow` | `0 2 * * 1` | `cron`, `crontab` |
| 6-field (seconds) | `sec min hour dom month dow` | `0 0 2 * * 1` | Spring, Quartz |
| GitHub Actions | `min hour dom month dow` | `'0 2 * * 1'` | Under `on.schedule.cron` |
| AWS EventBridge | `min hour dom month dow year` | `0 2 ? * MON *` | Uses `?` for DOM/DOW |
| Kubernetes | `min hour dom month dow` | `0 2 * * 1` | `.spec.schedule` |Cron: */5 9-17 * * 1-5
Every 5 minutes, between 9:00 AM and 5:00 PM, Monday–Friday
Next run: 2026-03-19 09:00:00 UTC (in 3 minutes)
Issues: none
To convert English → cron:
"every weekday at 9am" → 0 9 * * 1-5
"first of month midnight" → 0 0 1 * *© LeoYeAI, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file in skills/phy-cron-explainer of LeoYeAI/openclaw-master-skills.
Open the folder on GitHubat commit e5199b5
Phy Cron Explainer next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Phy Cron Explainer this skillLeoYeAI/openclaw-master-skills | 2.2k | — | ~7k | Automated safety check: Pass | Apache-2.0 | |
| Deployment Automationaiskillstore/marketplace | 430 | 1 repos | ~3k | Automated safety check: Notes | None | |
| Ak Cloud Deployyaalalabs/agent-kernel | 191 | — | ~14k | Automated safety check: Pass | Apache-2.0 | |
| Project Referencesaiskillstore/marketplace | 430 | — | ~2.3k | Automated safety check: Notes | None | |
| Tsh Managing SecretsTheSoftwareHouse/copilot-collections | 284 | — | ~877 | Automated safety check: Notes | MIT | |
| Senior DevOps Toolkitmaslennikov-ig/claude-code-orchestrator-kit | 259 | 6 repos | ~1.1k | Automated safety check: Notes | Custom licence |
aiskillstore/marketplace
Automate application deployment to cloud platforms and servers.
yaalalabs/agent-kernel
Deploy an Agent Kernel project to AWS, Azure, or GCP using Terraform modules, or to any Kubernetes cluster (on-prem, baremetal, EKS) using the official Helm chart.
aiskillstore/marketplace
Look up conventions, patterns, and concrete implementations from your own GitHub repositories checked out locally under ~/projects/referenzen/.
TheSoftwareHouse/copilot-collections
Secrets management patterns for cloud and Kubernetes environments.
maslennikov-ig/claude-code-orchestrator-kit
Comprehensive DevOps skill for CI/CD, infrastructure automation, containerization, and cloud platforms (AWS, GCP, Azure). Includes pipeline setup…
mondoohq/mql
Deep static code review of an mql provider for logic errors, nil-handling bugs, pagination truncation, caching/id collisions, and other defects that silently give users wrong data.
LeoYeAI/openclaw-master-skills
Manages pipelines on a DevOps quality and efficiency platform through its OpenAPI: list workspaces and templates, create, update, run and cancel pipelines, and read run records.
LeoYeAI/openclaw-master-skills
Patches OpenClaw's Feishu extension so an edited document triggers an isolated agent session that reads the doc and replies inline, turning it into a live chat space.
LeoYeAI/openclaw-master-skills
Multi-context memory management system for OpenClaw agents with group-isolated storage, global shared memory, workspace organization, and group-specific skills isolation.
LeoYeAI/openclaw-master-skills
Runs a brand's AI-search visibility work end to end: diagnosing how AI platforms represent it, repositioning it, producing AI-optimized content and monitoring ongoing mentions.
LeoYeAI/openclaw-master-skills
Installs and authenticates the gws CLI, then automates Gmail, Drive, Sheets, Calendar, Docs, Chat and Tasks with ready-made recipes, persona bundles and security audits.
LeoYeAI/openclaw-master-skills
Runs four advisor roles, a fitness coach, nutritionist, data analyst and TCM practitioner, to build a health profile and track workouts, diet and wellness over time.
Categories
Cron expression explainer, validator, and converter. An agent skill from LeoYeAI/openclaw-master-skills. Phy Cron Explainer is an agent skill from LeoYeAI/openclaw-master-skills. Cron expression explainer, validator, and converter.
Phy Cron Explainer fits situations like: cron expression; what does this cron mean; cron to English.
Run `npx skills add LeoYeAI/openclaw-master-skills --skill phy-cron-explainer -a claude-code`. Or copy the skill folder (skills/phy-cron-explainer in LeoYeAI/openclaw-master-skills) into .claude/skills/phy-cron-explainer in your project. Claude Code loads it when a task matches its description.
Run `npx skills add LeoYeAI/openclaw-master-skills --skill phy-cron-explainer -a codex`. Or copy the skill folder (skills/phy-cron-explainer in LeoYeAI/openclaw-master-skills) into .agents/skills/phy-cron-explainer in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add LeoYeAI/openclaw-master-skills --skill phy-cron-explainer -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/phy-cron-explainer, .gemini/skills/phy-cron-explainer, .github/skills/phy-cron-explainer and .opencode/skills/phy-cron-explainer in your project.
Going by SKILL.md and its folder, Phy Cron Explainer needs the command-line tools its instructions call (aws). Our summary lists: Python 3.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Phy Cron Explainer is published under the Apache-2.0 licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.
About 7k tokens (SKILL.md is roughly 28k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Phy Cron Explainer: Deployment Automation (aiskillstore/marketplace, 430 stars), Ak Cloud Deploy (yaalalabs/agent-kernel, 191 stars), Project References (aiskillstore/marketplace, 430 stars) and Tsh Managing Secrets (TheSoftwareHouse/copilot-collections, 284 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
LeoYeAI (a GitHub user) maintains it in LeoYeAI/openclaw-master-skills, which has 2,158 GitHub stars. The repository holds 1,215 skills in this directory. The repository was last updated on July 20, 2026.
Source: LeoYeAI/openclaw-master-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.