@wonsukchoi/crondex 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -2
- package/catalog.json +530 -1
- package/jobs/content/rss-feed-validate.yaml +32 -0
- package/jobs/content/seo-meta-check.yaml +32 -0
- package/jobs/devops/api-rate-limit-check.yaml +31 -0
- package/jobs/devops/db-backup-verify.yaml +37 -0
- package/jobs/devops/dns-record-check.yaml +32 -0
- package/jobs/devops/license-compliance-check.yaml +31 -0
- package/jobs/devops/orphaned-branch-cleanup.yaml +28 -0
- package/jobs/devops/queue-depth-check.yaml +35 -0
- package/jobs/finance/invoice-overdue-check.yaml +32 -0
- package/jobs/finance/saas-seat-audit.yaml +35 -0
- package/jobs/finance/tax-deadline-reminder.yaml +33 -0
- package/jobs/learning/course-progress-checkin.yaml +37 -0
- package/jobs/learning/daily-flashcard-review.yaml +29 -0
- package/jobs/learning/reading-list-nudge.yaml +25 -0
- package/jobs/productivity/focus-block-reminder.yaml +25 -0
- package/jobs/security/failed-login-watch.yaml +37 -0
- package/jobs/security/firewall-rule-diff.yaml +30 -0
- package/jobs/security/open-port-check.yaml +36 -0
- package/jobs/security/secrets-scan.yaml +28 -0
- package/jobs/security/sudo-usage-audit.yaml +35 -0
- package/jobs/team/1on1-prep-reminder.yaml +26 -0
- package/jobs/team/pto-balance-check.yaml +33 -0
- package/package.json +1 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
id: rss-feed-validate
|
|
2
|
+
version: 1
|
|
3
|
+
name: RSS Feed Validation
|
|
4
|
+
description: >
|
|
5
|
+
Checks your RSS/Atom feed is valid XML and shows the most recent
|
|
6
|
+
item's publish date. Use this if you've ever shipped a broken feed
|
|
7
|
+
and only found out when a reader's app stopped updating.
|
|
8
|
+
category: content
|
|
9
|
+
tags: [rss, feed, content]
|
|
10
|
+
schedule: "0 8 * * *"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
url={{feed_url}};
|
|
15
|
+
xml=$(curl -sL --max-time 10 "$url");
|
|
16
|
+
[ -z "$xml" ] && { echo "ERROR: could not fetch $url"; exit 1; };
|
|
17
|
+
if command -v xmllint >/dev/null 2>&1; then
|
|
18
|
+
echo "$xml" | xmllint --noout - 2>&1 && echo "OK: valid XML" || { echo "INVALID XML"; exit 1; };
|
|
19
|
+
else
|
|
20
|
+
echo "xmllint not installed, skipping strict XML validation";
|
|
21
|
+
fi;
|
|
22
|
+
pubdate=$(echo "$xml" | grep -oE "<pubDate>[^<]+</pubDate>" | head -n1 | sed 's/<[^>]*>//g');
|
|
23
|
+
echo "Most recent item pubDate: ${pubdate:-unknown}"
|
|
24
|
+
variables:
|
|
25
|
+
feed_url:
|
|
26
|
+
default: "https://example.com/feed.xml"
|
|
27
|
+
description: URL of the RSS/Atom feed to check.
|
|
28
|
+
compatible_agents: [generic, claude, codex]
|
|
29
|
+
notes: >
|
|
30
|
+
Zero-token. Only reports the latest pubDate for you to eyeball —
|
|
31
|
+
doesn't flag "stale" automatically since feed cadences vary too much
|
|
32
|
+
for one threshold. Install `xmllint` (libxml2) for strict validation.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
id: seo-meta-check
|
|
2
|
+
version: 1
|
|
3
|
+
name: SEO Meta Tag Check
|
|
4
|
+
description: >
|
|
5
|
+
Checks a list of pages for missing title, meta description, and
|
|
6
|
+
Open Graph tags. Use this if pages have ever shipped without basic
|
|
7
|
+
SEO/social-preview tags because nobody double-checked.
|
|
8
|
+
category: content
|
|
9
|
+
tags: [seo, content, meta-tags]
|
|
10
|
+
schedule: "0 7 * * 1"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
file={{urls_file}};
|
|
15
|
+
[ -f "$file" ] || { echo "ERROR: $file not found"; exit 1; };
|
|
16
|
+
while IFS= read -r url; do
|
|
17
|
+
[ -z "$url" ] && continue;
|
|
18
|
+
html=$(curl -sL --max-time 10 "$url");
|
|
19
|
+
missing="";
|
|
20
|
+
echo "$html" | grep -qi "<title>" || missing="$missing title";
|
|
21
|
+
echo "$html" | grep -qi 'name="description"' || missing="$missing meta-description";
|
|
22
|
+
echo "$html" | grep -qi 'property="og:title"' || missing="$missing og:title";
|
|
23
|
+
if [ -n "$missing" ]; then echo "MISSING on $url:$missing"; else echo "OK: $url"; fi
|
|
24
|
+
done < "$file"
|
|
25
|
+
variables:
|
|
26
|
+
urls_file:
|
|
27
|
+
default: "./pages.txt"
|
|
28
|
+
description: Plain text file with one page URL per line to check.
|
|
29
|
+
compatible_agents: [generic, claude, codex]
|
|
30
|
+
notes: >
|
|
31
|
+
Zero-token. Basic presence check only (does the tag exist), not content
|
|
32
|
+
quality — pair with a fuller SEO audit for anything deeper.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
id: api-rate-limit-check
|
|
2
|
+
version: 1
|
|
3
|
+
name: API Rate Limit Check
|
|
4
|
+
description: >
|
|
5
|
+
Checks how many API requests you have left before hitting a rate
|
|
6
|
+
limit. Use this if you've ever gotten a 429 in production because
|
|
7
|
+
nobody was watching the remaining quota.
|
|
8
|
+
category: devops
|
|
9
|
+
tags: [api, rate-limit, monitoring]
|
|
10
|
+
schedule: "*/15 * * * *"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
url={{api_url}};
|
|
15
|
+
headers=$(curl -sI --max-time 10 "$url");
|
|
16
|
+
remaining=$(echo "$headers" | grep -i "^x-ratelimit-remaining:" | awk '{print $2}' | tr -d '\r');
|
|
17
|
+
if [ -z "$remaining" ]; then echo "no rate-limit headers found on $url (nothing to check, or a different header convention)"; exit 0;
|
|
18
|
+
elif [ "$remaining" -le {{warn_threshold}} ]; then echo "WARNING: $url has only $remaining requests remaining"; exit 1;
|
|
19
|
+
else echo "OK: $url has $remaining requests remaining"; fi
|
|
20
|
+
variables:
|
|
21
|
+
api_url:
|
|
22
|
+
default: "https://api.example.com/status"
|
|
23
|
+
description: An endpoint on the API you want to watch — any request that returns rate-limit headers works.
|
|
24
|
+
warn_threshold:
|
|
25
|
+
default: 50
|
|
26
|
+
description: Warn if remaining requests drops to this or below.
|
|
27
|
+
compatible_agents: [generic, claude, codex]
|
|
28
|
+
notes: >
|
|
29
|
+
Assumes the standard `X-RateLimit-Remaining` response header — adjust
|
|
30
|
+
the grep pattern if your API uses a different convention
|
|
31
|
+
(e.g. `RateLimit-Remaining`, a custom header).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
id: db-backup-verify
|
|
2
|
+
version: 1
|
|
3
|
+
name: Database Backup Restore Verification
|
|
4
|
+
description: >
|
|
5
|
+
Actually restores your latest database backup to a throwaway database
|
|
6
|
+
and checks it's queryable. Use this if `backup-reminder` isn't enough —
|
|
7
|
+
a backup file existing doesn't mean it's actually restorable.
|
|
8
|
+
category: devops
|
|
9
|
+
tags: [database, backup, postgres]
|
|
10
|
+
schedule: "0 5 * * *"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
dump={{dump_path}}; scratch_db={{scratch_db_name}};
|
|
15
|
+
[ -f "$dump" ] || { echo "ERROR: $dump not found"; exit 1; };
|
|
16
|
+
command -v pg_restore >/dev/null 2>&1 || { echo "ERROR: pg_restore not installed"; exit 1; };
|
|
17
|
+
dropdb --if-exists "$scratch_db" 2>/dev/null;
|
|
18
|
+
createdb "$scratch_db" || { echo "ERROR: could not create scratch db $scratch_db"; exit 1; };
|
|
19
|
+
pg_restore -d "$scratch_db" "$dump" >/dev/null 2>&1;
|
|
20
|
+
count=$(psql -d "$scratch_db" -tAc "SELECT count(*) FROM {{checkpoint_table}}" 2>/dev/null);
|
|
21
|
+
dropdb --if-exists "$scratch_db" 2>/dev/null;
|
|
22
|
+
if [ -z "$count" ]; then echo "FAILED: restore or query failed on $dump"; exit 1;
|
|
23
|
+
else echo "OK: restored $dump, $checkpoint_table has $count rows"; fi
|
|
24
|
+
variables:
|
|
25
|
+
dump_path:
|
|
26
|
+
default: "./backups/latest.dump"
|
|
27
|
+
description: Path to the pg_dump/pg_restore-format backup file.
|
|
28
|
+
scratch_db_name:
|
|
29
|
+
default: "backup_verify_scratch"
|
|
30
|
+
description: Throwaway database name used for the test restore (created and dropped each run).
|
|
31
|
+
checkpoint_table:
|
|
32
|
+
default: "users"
|
|
33
|
+
description: A table expected to have rows — used to confirm the restore actually worked.
|
|
34
|
+
compatible_agents: [generic, claude, codex]
|
|
35
|
+
notes: >
|
|
36
|
+
Postgres-specific (pg_restore/psql/createdb/dropdb) — adapt the command
|
|
37
|
+
for other database engines. Needs CREATE/DROP DATABASE privileges.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
id: dns-record-check
|
|
2
|
+
version: 1
|
|
3
|
+
name: DNS Record Drift Check
|
|
4
|
+
description: >
|
|
5
|
+
Checks that a DNS record still resolves to the value you expect, and
|
|
6
|
+
flags drift. Use this if you've ever had DNS quietly change (an
|
|
7
|
+
expired record, a misconfigured provider) and only found out when
|
|
8
|
+
something broke.
|
|
9
|
+
category: devops
|
|
10
|
+
tags: [dns, monitoring, drift]
|
|
11
|
+
schedule: "0 */6 * * *"
|
|
12
|
+
timezone: "UTC"
|
|
13
|
+
runner: shell
|
|
14
|
+
command: >
|
|
15
|
+
domain={{domain}}; type={{record_type}}; expected="{{expected_value}}";
|
|
16
|
+
command -v dig >/dev/null 2>&1 || { echo "ERROR: dig not installed"; exit 1; };
|
|
17
|
+
actual=$(dig +short "$domain" "$type" | tr '\n' ' ' | sed 's/ *$//');
|
|
18
|
+
if [ -z "$actual" ]; then echo "ERROR: no $type record found for $domain"; exit 1;
|
|
19
|
+
elif echo "$actual" | grep -qF "$expected"; then echo "OK: $domain $type matches expected ($actual)";
|
|
20
|
+
else echo "DRIFT: $domain $type is '$actual', expected to contain '$expected'"; exit 1; fi
|
|
21
|
+
variables:
|
|
22
|
+
domain:
|
|
23
|
+
default: "example.com"
|
|
24
|
+
description: Domain to check.
|
|
25
|
+
record_type:
|
|
26
|
+
default: "A"
|
|
27
|
+
description: DNS record type (A, CNAME, TXT, MX, etc).
|
|
28
|
+
expected_value:
|
|
29
|
+
default: "93.184.216.34"
|
|
30
|
+
description: Value the record should contain — replace with your actual expected record.
|
|
31
|
+
compatible_agents: [generic, claude, codex]
|
|
32
|
+
notes: Zero-token, deterministic. Requires `dig` (part of dnsutils/bind-tools).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
id: license-compliance-check
|
|
2
|
+
version: 1
|
|
3
|
+
name: Dependency License Compliance Check
|
|
4
|
+
description: >
|
|
5
|
+
Scans your dependencies for licenses you've flagged as risky (like GPL
|
|
6
|
+
or AGPL). Use this if you've ever shipped something and only later
|
|
7
|
+
found out a dependency's license doesn't play well with your product.
|
|
8
|
+
category: devops
|
|
9
|
+
tags: [license, compliance, dependencies]
|
|
10
|
+
schedule: "0 8 * * 1"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
cd {{repo_path}} &&
|
|
15
|
+
if [ -f package.json ]; then
|
|
16
|
+
npx --yes license-checker --summary --excludePrivatePackages 2>/dev/null | grep -iE "{{flagged_licenses}}" && echo "^ flagged licenses found above" || echo "OK: no flagged licenses found";
|
|
17
|
+
else
|
|
18
|
+
echo "no package.json found — this job only supports npm/Node projects currently";
|
|
19
|
+
fi
|
|
20
|
+
variables:
|
|
21
|
+
repo_path:
|
|
22
|
+
default: "."
|
|
23
|
+
description: Path to the repo to check.
|
|
24
|
+
flagged_licenses:
|
|
25
|
+
default: "GPL|AGPL"
|
|
26
|
+
description: Regex alternation of license names to flag.
|
|
27
|
+
compatible_agents: [generic, claude, codex]
|
|
28
|
+
notes: >
|
|
29
|
+
npm/Node projects only (uses `npx license-checker`, downloads on first
|
|
30
|
+
run). For other ecosystems, swap in the equivalent tool
|
|
31
|
+
(pip-licenses, cargo-license, etc.) and adjust the command.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
id: orphaned-branch-cleanup
|
|
2
|
+
version: 1
|
|
3
|
+
name: Orphaned Branch Cleanup
|
|
4
|
+
description: >
|
|
5
|
+
Lists remote branches already merged into main that nobody's deleted
|
|
6
|
+
yet. Use this if your branch list has become a graveyard nobody wants
|
|
7
|
+
to clean up.
|
|
8
|
+
category: devops
|
|
9
|
+
tags: [git, cleanup, branches]
|
|
10
|
+
schedule: "0 9 * * 1"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
cd {{repo_path}} && git fetch --prune >/dev/null 2>&1;
|
|
15
|
+
merged=$(git branch -r --merged {{base_branch}} | grep -v -E "(\*|{{base_branch}}$)" | sed 's#origin/##');
|
|
16
|
+
if [ -z "$merged" ]; then echo "OK: no merged branches to clean up";
|
|
17
|
+
else echo "Merged into {{base_branch}}, safe to delete:"; echo "$merged"; fi
|
|
18
|
+
variables:
|
|
19
|
+
repo_path:
|
|
20
|
+
default: "."
|
|
21
|
+
description: Path to the repo to check.
|
|
22
|
+
base_branch:
|
|
23
|
+
default: "main"
|
|
24
|
+
description: Branch to check merges against.
|
|
25
|
+
compatible_agents: [generic, claude, codex]
|
|
26
|
+
notes: >
|
|
27
|
+
List-only by design — never deletes anything. Review the list, then
|
|
28
|
+
`git push origin --delete <branch>` yourself for ones you actually want gone.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
id: queue-depth-check
|
|
2
|
+
version: 1
|
|
3
|
+
name: Queue Depth Check
|
|
4
|
+
description: >
|
|
5
|
+
Checks how backed up a job queue is and warns if it crosses a
|
|
6
|
+
threshold. Use this if a stuck worker has ever let a queue silently
|
|
7
|
+
balloon until someone noticed customers weren't getting emails.
|
|
8
|
+
category: devops
|
|
9
|
+
tags: [queue, redis, monitoring]
|
|
10
|
+
schedule: "*/10 * * * *"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
command -v redis-cli >/dev/null 2>&1 || { echo "ERROR: redis-cli not installed"; exit 1; };
|
|
15
|
+
depth=$(redis-cli -h {{redis_host}} -p {{redis_port}} llen {{queue_key}} 2>/dev/null);
|
|
16
|
+
if [ -z "$depth" ]; then echo "ERROR: could not read queue depth for {{queue_key}}"; exit 1;
|
|
17
|
+
elif [ "$depth" -ge {{threshold}} ]; then echo "WARNING: queue {{queue_key}} depth is $depth (threshold {{threshold}})"; exit 1;
|
|
18
|
+
else echo "OK: queue {{queue_key}} depth is $depth"; fi
|
|
19
|
+
variables:
|
|
20
|
+
redis_host:
|
|
21
|
+
default: "localhost"
|
|
22
|
+
description: Redis host.
|
|
23
|
+
redis_port:
|
|
24
|
+
default: 6379
|
|
25
|
+
description: Redis port.
|
|
26
|
+
queue_key:
|
|
27
|
+
default: "jobs:pending"
|
|
28
|
+
description: Redis list key backing the queue.
|
|
29
|
+
threshold:
|
|
30
|
+
default: 1000
|
|
31
|
+
description: Warn if the queue depth reaches this many items.
|
|
32
|
+
compatible_agents: [generic, claude, codex]
|
|
33
|
+
notes: >
|
|
34
|
+
Redis list-backed queues only (uses LLEN). Adapt the depth-check
|
|
35
|
+
command for RabbitMQ, SQS, or other queue systems.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
id: invoice-overdue-check
|
|
2
|
+
version: 1
|
|
3
|
+
name: Invoice Overdue Check
|
|
4
|
+
description: >
|
|
5
|
+
Flags invoices that are past due and still unpaid. Use this if you
|
|
6
|
+
freelance or run a small business and invoices have ever slipped
|
|
7
|
+
through the cracks.
|
|
8
|
+
category: finance
|
|
9
|
+
tags: [invoices, accounts-receivable, finance]
|
|
10
|
+
schedule: "0 9 * * *"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
file={{invoices_file}};
|
|
15
|
+
[ -f "$file" ] || { echo "ERROR: $file not found"; exit 1; };
|
|
16
|
+
today=$(date +%s);
|
|
17
|
+
tail -n +2 "$file" | while IFS=, read -r id client amount due paid; do
|
|
18
|
+
[ "$paid" = "yes" ] && continue;
|
|
19
|
+
due_epoch=$(date -j -f %Y-%m-%d "$due" +%s 2>/dev/null || date -d "$due" +%s);
|
|
20
|
+
days=$(( (today-due_epoch)/86400 ));
|
|
21
|
+
if [ "$days" -gt 0 ]; then echo "OVERDUE: invoice $id ($client, \$$amount) — $days day(s) past due ($due)"; fi
|
|
22
|
+
done
|
|
23
|
+
variables:
|
|
24
|
+
invoices_file:
|
|
25
|
+
default: "./invoices.csv"
|
|
26
|
+
description: >
|
|
27
|
+
CSV with header "invoice_id,client,amount,due_date,paid"
|
|
28
|
+
(due_date is YYYY-MM-DD, paid is yes/no).
|
|
29
|
+
compatible_agents: [generic, claude, codex]
|
|
30
|
+
notes: >
|
|
31
|
+
Zero-token. You maintain invoices.csv yourself (mark paid=yes once
|
|
32
|
+
settled) — this doesn't connect to any billing/accounting system.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
id: saas-seat-audit
|
|
2
|
+
version: 1
|
|
3
|
+
name: Team SaaS Seat Audit
|
|
4
|
+
description: >
|
|
5
|
+
Flags team SaaS seats (Slack, Notion, GitHub, etc.) nobody's actually
|
|
6
|
+
used in a while. Use this if you suspect you're paying for seats of
|
|
7
|
+
people who left or tools nobody opens anymore.
|
|
8
|
+
category: finance
|
|
9
|
+
tags: [saas, seats, team, budget]
|
|
10
|
+
schedule: "0 9 1 * *"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
file={{seats_file}}; unused_days={{unused_days}};
|
|
15
|
+
[ -f "$file" ] || { echo "ERROR: $file not found"; exit 1; };
|
|
16
|
+
today=$(date +%s);
|
|
17
|
+
tail -n +2 "$file" | while IFS=, read -r tool owner cost last_active; do
|
|
18
|
+
last_epoch=$(date -j -f %Y-%m-%d "$last_active" +%s 2>/dev/null || date -d "$last_active" +%s);
|
|
19
|
+
days=$(( (today-last_epoch)/86400 ));
|
|
20
|
+
if [ "$days" -gt "$unused_days" ]; then echo "UNUSED SEAT: $tool - $owner (\$$cost/mo) - inactive $days days"; fi
|
|
21
|
+
done
|
|
22
|
+
variables:
|
|
23
|
+
seats_file:
|
|
24
|
+
default: "./saas-seats.csv"
|
|
25
|
+
description: >
|
|
26
|
+
CSV with header "tool,seat_owner,monthly_cost,last_active_date"
|
|
27
|
+
(YYYY-MM-DD), one seat per row.
|
|
28
|
+
unused_days:
|
|
29
|
+
default: 30
|
|
30
|
+
description: Flag a seat if it hasn't been active in this many days.
|
|
31
|
+
compatible_agents: [generic, claude, codex]
|
|
32
|
+
notes: >
|
|
33
|
+
Zero-token, deterministic. You maintain saas-seats.csv yourself (or
|
|
34
|
+
export last-active data from your SSO/IdP if it tracks that) — this job
|
|
35
|
+
only reads what's in the file, doesn't pull from any SaaS API.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
id: tax-deadline-reminder
|
|
2
|
+
version: 1
|
|
3
|
+
name: Tax Deadline Reminder
|
|
4
|
+
description: >
|
|
5
|
+
Warns you ahead of upcoming tax deadlines you've listed. Use this if
|
|
6
|
+
quarterly estimated payments or annual filing dates have ever snuck
|
|
7
|
+
up on you.
|
|
8
|
+
category: finance
|
|
9
|
+
tags: [tax, deadlines, finance]
|
|
10
|
+
schedule: "0 9 * * *"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
file={{deadlines_file}}; warn={{warn_days}};
|
|
15
|
+
[ -f "$file" ] || { echo "ERROR: $file not found"; exit 1; };
|
|
16
|
+
today=$(date +%s);
|
|
17
|
+
tail -n +2 "$file" | while IFS=, read -r label due; do
|
|
18
|
+
[ -z "$due" ] && continue;
|
|
19
|
+
due_epoch=$(date -j -f %Y-%m-%d "$due" +%s 2>/dev/null || date -d "$due" +%s);
|
|
20
|
+
days=$(( (due_epoch-today)/86400 ));
|
|
21
|
+
if [ "$days" -ge 0 ] && [ "$days" -le "$warn" ]; then echo "REMINDER: $label due in $days day(s) ($due)"; fi
|
|
22
|
+
done
|
|
23
|
+
variables:
|
|
24
|
+
deadlines_file:
|
|
25
|
+
default: "./tax-deadlines.csv"
|
|
26
|
+
description: CSV with header "label,date" (date is YYYY-MM-DD), one deadline per row.
|
|
27
|
+
warn_days:
|
|
28
|
+
default: 14
|
|
29
|
+
description: Start warning this many days before a deadline.
|
|
30
|
+
compatible_agents: [generic, claude, codex]
|
|
31
|
+
notes: >
|
|
32
|
+
Zero-token. You maintain tax-deadlines.csv yourself with your actual
|
|
33
|
+
jurisdiction's dates — this job doesn't know tax law or deadlines on its own.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
id: course-progress-checkin
|
|
2
|
+
version: 1
|
|
3
|
+
name: Course Progress Check-in
|
|
4
|
+
description: >
|
|
5
|
+
Compares your current chapter/lesson against a target date and tells
|
|
6
|
+
you if you're on track or falling behind. Use this if you've ever
|
|
7
|
+
signed up for a course and quietly stopped halfway through.
|
|
8
|
+
category: learning
|
|
9
|
+
tags: [learning, goals, progress]
|
|
10
|
+
schedule: "0 9 * * 1"
|
|
11
|
+
timezone: "America/Los_Angeles"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
current={{current_chapter}}; target={{target_chapter}}; target_date={{target_date}};
|
|
15
|
+
today_epoch=$(date +%s);
|
|
16
|
+
target_epoch=$(date -j -f %Y-%m-%d "$target_date" +%s 2>/dev/null || date -d "$target_date" +%s);
|
|
17
|
+
days_left=$(( (target_epoch-today_epoch)/86400 ));
|
|
18
|
+
chapters_left=$(( target - current ));
|
|
19
|
+
if [ "$chapters_left" -le 0 ]; then echo "OK: at or past target chapter $target already.";
|
|
20
|
+
elif [ "$days_left" -le 0 ]; then echo "OVERDUE: target date was $target_date, still $chapters_left chapter(s) to go.";
|
|
21
|
+
elif [ "$chapters_left" -gt "$days_left" ]; then echo "BEHIND: $chapters_left chapter(s) left, only $days_left day(s) until $target_date.";
|
|
22
|
+
else echo "ON TRACK: $chapters_left chapter(s) left, $days_left day(s) until $target_date."; fi
|
|
23
|
+
variables:
|
|
24
|
+
current_chapter:
|
|
25
|
+
default: 1
|
|
26
|
+
description: Chapter/lesson number you're currently on — update this yourself as you progress.
|
|
27
|
+
target_chapter:
|
|
28
|
+
default: 10
|
|
29
|
+
description: Chapter/lesson number you want to finish by target_date.
|
|
30
|
+
target_date:
|
|
31
|
+
default: "2026-12-31"
|
|
32
|
+
description: Date (YYYY-MM-DD) you want to hit target_chapter by.
|
|
33
|
+
compatible_agents: [generic, claude, codex]
|
|
34
|
+
notes: >
|
|
35
|
+
Zero-token. "Behind/on track" is a rough linear heuristic (chapters-left
|
|
36
|
+
vs. days-left), not a real study-pace model. You update current_chapter
|
|
37
|
+
yourself as you go.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
id: daily-flashcard-review
|
|
2
|
+
version: 1
|
|
3
|
+
name: Daily Flashcard Review
|
|
4
|
+
description: >
|
|
5
|
+
Lists which flashcards are due for review today from a simple deck
|
|
6
|
+
file. Use this if you're learning something with spaced repetition but
|
|
7
|
+
don't want a whole separate app for it.
|
|
8
|
+
category: learning
|
|
9
|
+
tags: [learning, flashcards, spaced-repetition]
|
|
10
|
+
schedule: "0 8 * * *"
|
|
11
|
+
timezone: "America/Los_Angeles"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
deck={{deck_path}};
|
|
15
|
+
[ -f "$deck" ] || { echo "ERROR: $deck not found"; exit 1; };
|
|
16
|
+
today=$(date +%F);
|
|
17
|
+
due=$(awk -F, -v today="$today" 'NR>1 && $3<=today {print $1" -> "$2" (due "$3")"}' "$deck");
|
|
18
|
+
if [ -z "$due" ]; then echo "No cards due today.";
|
|
19
|
+
else echo "Cards due today:"; echo "$due"; fi
|
|
20
|
+
variables:
|
|
21
|
+
deck_path:
|
|
22
|
+
default: "./flashcards.csv"
|
|
23
|
+
description: CSV with header "front,back,next_review_date" (YYYY-MM-DD).
|
|
24
|
+
compatible_agents: [generic, claude, codex]
|
|
25
|
+
notes: >
|
|
26
|
+
Zero-token. You maintain flashcards.csv and update each card's
|
|
27
|
+
next_review_date yourself after reviewing — this is simple date-based
|
|
28
|
+
scheduling, not full SM-2 spaced repetition. This job only tells you
|
|
29
|
+
what's due, it doesn't reschedule cards for you.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
id: reading-list-nudge
|
|
2
|
+
version: 1
|
|
3
|
+
name: Reading List Nudge
|
|
4
|
+
description: >
|
|
5
|
+
Reminds you what's next on your reading list. Use this if you save
|
|
6
|
+
articles/books to "read later" and later never comes.
|
|
7
|
+
category: learning
|
|
8
|
+
tags: [learning, reading, reminder]
|
|
9
|
+
schedule: "0 9 * * 6"
|
|
10
|
+
timezone: "America/Los_Angeles"
|
|
11
|
+
runner: shell
|
|
12
|
+
command: >
|
|
13
|
+
file={{reading_list_path}};
|
|
14
|
+
[ -f "$file" ] || { echo "ERROR: $file not found"; exit 1; };
|
|
15
|
+
next=$(head -n 1 "$file");
|
|
16
|
+
if [ -z "$next" ]; then echo "Reading list is empty — nice work.";
|
|
17
|
+
else echo "Next up: $next"; fi
|
|
18
|
+
variables:
|
|
19
|
+
reading_list_path:
|
|
20
|
+
default: "./reading-list.txt"
|
|
21
|
+
description: Plain text file, one item per line — the top line is treated as "next."
|
|
22
|
+
compatible_agents: [generic, claude, codex]
|
|
23
|
+
notes: >
|
|
24
|
+
Zero-token. Suggests the top line only — remove an item from
|
|
25
|
+
reading-list.txt yourself once you've read it, or reorder to reprioritize.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
id: focus-block-reminder
|
|
2
|
+
version: 1
|
|
3
|
+
name: Focus Block Reminder
|
|
4
|
+
description: >
|
|
5
|
+
Nudges you to start a deep-work block at set times. Use this if your
|
|
6
|
+
day fills up with reactive work and real focus time only happens by
|
|
7
|
+
accident.
|
|
8
|
+
category: productivity
|
|
9
|
+
tags: [focus, productivity, deep-work]
|
|
10
|
+
schedule: "0 9,14 * * 1-5"
|
|
11
|
+
timezone: "America/Los_Angeles"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
echo "Focus block: {{block_minutes}} minutes on \"{{focus_task}}\" starting now. Silence notifications."
|
|
15
|
+
variables:
|
|
16
|
+
focus_task:
|
|
17
|
+
default: "deep work"
|
|
18
|
+
description: What to focus on for this block.
|
|
19
|
+
block_minutes:
|
|
20
|
+
default: 50
|
|
21
|
+
description: Length of the focus block in minutes.
|
|
22
|
+
compatible_agents: [generic, claude, codex]
|
|
23
|
+
notes: >
|
|
24
|
+
Zero-token. Purely a nudge — doesn't block notifications/apps itself;
|
|
25
|
+
pair with your OS's focus/DND mode if you want it enforced.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
id: failed-login-watch
|
|
2
|
+
version: 1
|
|
3
|
+
name: Failed Login Watch
|
|
4
|
+
description: >
|
|
5
|
+
Watches an auth log for a burst of failed login attempts and alerts if
|
|
6
|
+
it crosses a threshold. Use this if you run a server exposed to SSH and
|
|
7
|
+
want an early warning for brute-force attempts.
|
|
8
|
+
category: security
|
|
9
|
+
tags: [security, auth, monitoring]
|
|
10
|
+
schedule: "*/15 * * * *"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
log={{auth_log_path}}; state={{state_file}}; threshold={{fail_threshold}};
|
|
15
|
+
[ -f "$log" ] || { echo "ERROR: $log not found"; exit 1; };
|
|
16
|
+
total=$(grep -c "Failed password" "$log");
|
|
17
|
+
last=$(cat "$state" 2>/dev/null || echo 0);
|
|
18
|
+
new=$(( total - last ));
|
|
19
|
+
[ "$new" -lt 0 ] && new=0;
|
|
20
|
+
echo "$total" > "$state";
|
|
21
|
+
if [ "$new" -ge "$threshold" ]; then echo "WARNING: $new new failed login attempt(s) since last check (threshold $threshold)"; exit 1;
|
|
22
|
+
else echo "OK: $new new failed login attempt(s) since last check"; fi
|
|
23
|
+
variables:
|
|
24
|
+
auth_log_path:
|
|
25
|
+
default: "/var/log/auth.log"
|
|
26
|
+
description: Path to the auth log to watch (Linux-style).
|
|
27
|
+
state_file:
|
|
28
|
+
default: "./.failed-login-watch-state"
|
|
29
|
+
description: File this job uses to remember the last count seen, so it only alerts on new failures.
|
|
30
|
+
fail_threshold:
|
|
31
|
+
default: 5
|
|
32
|
+
description: Alert if this many new failed attempts appear since the last check.
|
|
33
|
+
compatible_agents: [generic, claude, codex]
|
|
34
|
+
notes: >
|
|
35
|
+
Linux-oriented (/var/log/auth.log). On macOS, use
|
|
36
|
+
`log show --predicate 'eventMessage contains "authentication failure"'`
|
|
37
|
+
instead and adapt the command — this one won't work there as-is.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
id: firewall-rule-diff
|
|
2
|
+
version: 1
|
|
3
|
+
name: Firewall Rule Diff
|
|
4
|
+
description: >
|
|
5
|
+
Diffs your current firewall rules against a saved baseline and flags
|
|
6
|
+
any drift. Use this if a firewall rule has ever changed without
|
|
7
|
+
anyone remembering who did it or why.
|
|
8
|
+
category: security
|
|
9
|
+
tags: [security, firewall, drift]
|
|
10
|
+
schedule: "0 6 * * *"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
baseline={{baseline_path}};
|
|
15
|
+
if command -v ufw >/dev/null 2>&1; then current=$(sudo ufw status verbose 2>/dev/null);
|
|
16
|
+
elif command -v iptables >/dev/null 2>&1; then current=$(sudo iptables -S 2>/dev/null);
|
|
17
|
+
else echo "ERROR: no supported firewall tool found (ufw/iptables)"; exit 1; fi;
|
|
18
|
+
if [ ! -f "$baseline" ]; then echo "$current" > "$baseline"; echo "no baseline found — saved current rules as the new baseline"; exit 0; fi;
|
|
19
|
+
if diff -q <(echo "$current") "$baseline" >/dev/null 2>&1; then echo "OK: firewall rules match baseline";
|
|
20
|
+
else echo "DRIFT: firewall rules differ from baseline:"; diff <(echo "$current") "$baseline"; exit 1; fi
|
|
21
|
+
variables:
|
|
22
|
+
baseline_path:
|
|
23
|
+
default: "./firewall-baseline.txt"
|
|
24
|
+
description: File storing the known-good firewall rule snapshot.
|
|
25
|
+
compatible_agents: [generic, claude, codex]
|
|
26
|
+
notes: >
|
|
27
|
+
Requires sudo access to read ufw/iptables rules (configure passwordless
|
|
28
|
+
sudo for this specific read-only command if running unattended). First
|
|
29
|
+
run saves the baseline; later runs diff against it. Requires bash
|
|
30
|
+
(process substitution).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
id: open-port-check
|
|
2
|
+
version: 1
|
|
3
|
+
name: Expected Open Ports Check
|
|
4
|
+
description: >
|
|
5
|
+
Checks that the ports you expect to be open on a host actually are, and
|
|
6
|
+
flags any that aren't. Use this if a service has ever silently stopped
|
|
7
|
+
listening and you found out from a user instead of a monitor.
|
|
8
|
+
category: security
|
|
9
|
+
tags: [security, network, ports]
|
|
10
|
+
schedule: "0 */4 * * *"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
host={{host}}; fail=0;
|
|
15
|
+
for p in $(echo "{{expected_open_ports}}" | tr ',' ' '); do
|
|
16
|
+
if command -v nc >/dev/null 2>&1; then
|
|
17
|
+
nc -z -w2 "$host" "$p" 2>/dev/null && echo "OK: $host:$p open" || { echo "MISSING: $host:$p expected open but isn't"; fail=1; };
|
|
18
|
+
else
|
|
19
|
+
(echo > /dev/tcp/$host/$p) 2>/dev/null && echo "OK: $host:$p open" || { echo "MISSING: $host:$p expected open but isn't"; fail=1; };
|
|
20
|
+
fi
|
|
21
|
+
done;
|
|
22
|
+
exit $fail
|
|
23
|
+
variables:
|
|
24
|
+
host:
|
|
25
|
+
default: "localhost"
|
|
26
|
+
description: Host to check.
|
|
27
|
+
expected_open_ports:
|
|
28
|
+
default: "22,80,443"
|
|
29
|
+
description: Comma-separated list of ports that should be open.
|
|
30
|
+
compatible_agents: [generic, claude, codex]
|
|
31
|
+
notes: >
|
|
32
|
+
Zero-token. Checks only the ports you list — not a full port scan. Use
|
|
33
|
+
nmap for broader unexpected-open-port discovery. The nc branch (used on
|
|
34
|
+
virtually every system) is the tested path; the /dev/tcp fallback only
|
|
35
|
+
matters if nc is missing and requires bash specifically — it won't work
|
|
36
|
+
under dash/POSIX sh.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
id: secrets-scan
|
|
2
|
+
version: 1
|
|
3
|
+
name: Committed Secrets Scan
|
|
4
|
+
description: >
|
|
5
|
+
Scans a repo for accidentally committed API keys, tokens, and private
|
|
6
|
+
keys. Use this if you want a safety net for the "oops, committed a key"
|
|
7
|
+
mistake everyone makes eventually.
|
|
8
|
+
category: security
|
|
9
|
+
tags: [security, secrets, scanning]
|
|
10
|
+
schedule: "0 3 * * *"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
cd {{repo_path}} &&
|
|
15
|
+
if command -v gitleaks >/dev/null 2>&1; then
|
|
16
|
+
gitleaks detect --source . --no-git -v;
|
|
17
|
+
else
|
|
18
|
+
echo "gitleaks not installed, falling back to basic pattern matching";
|
|
19
|
+
grep -rnE "(AKIA[0-9A-Z]{16}|-----BEGIN (RSA|EC|DSA|OPENSSH) PRIVATE KEY-----|api[_-]?key['\"]?[[:space:]]*[:=][[:space:]]*['\"][A-Za-z0-9_-]{16,})" --exclude-dir=.git --exclude-dir=node_modules . || echo "no matches found";
|
|
20
|
+
fi
|
|
21
|
+
variables:
|
|
22
|
+
repo_path:
|
|
23
|
+
default: "."
|
|
24
|
+
description: Path to the repo to scan.
|
|
25
|
+
compatible_agents: [generic, claude, codex]
|
|
26
|
+
notes: >
|
|
27
|
+
Zero-token. Prefers `gitleaks` if installed (far more accurate); the
|
|
28
|
+
fallback regex is best-effort and will miss many patterns or false-positive.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
id: sudo-usage-audit
|
|
2
|
+
version: 1
|
|
3
|
+
name: Sudo Usage Audit
|
|
4
|
+
description: >
|
|
5
|
+
Reports new sudo commands run since the last check. Use this if you
|
|
6
|
+
want to notice unexpected privilege escalation on a shared or
|
|
7
|
+
internet-facing server.
|
|
8
|
+
category: security
|
|
9
|
+
tags: [security, sudo, audit]
|
|
10
|
+
schedule: "0 * * * *"
|
|
11
|
+
timezone: "UTC"
|
|
12
|
+
runner: shell
|
|
13
|
+
command: >
|
|
14
|
+
log={{auth_log_path}}; state={{state_file}};
|
|
15
|
+
[ -f "$log" ] || { echo "ERROR: $log not found"; exit 1; };
|
|
16
|
+
total=$(grep -c "sudo:.*COMMAND=" "$log");
|
|
17
|
+
last=$(cat "$state" 2>/dev/null || echo 0);
|
|
18
|
+
new=$(( total - last ));
|
|
19
|
+
[ "$new" -lt 0 ] && new=0;
|
|
20
|
+
echo "$total" > "$state";
|
|
21
|
+
if [ "$new" -gt 0 ]; then
|
|
22
|
+
echo "New sudo commands since last check ($new):";
|
|
23
|
+
grep "sudo:.*COMMAND=" "$log" | tail -n "$new";
|
|
24
|
+
else echo "OK: no new sudo usage since last check"; fi
|
|
25
|
+
variables:
|
|
26
|
+
auth_log_path:
|
|
27
|
+
default: "/var/log/auth.log"
|
|
28
|
+
description: Path to the auth log (Linux-style).
|
|
29
|
+
state_file:
|
|
30
|
+
default: "./.sudo-audit-state"
|
|
31
|
+
description: File this job uses to remember the last count seen, so it only reports new entries.
|
|
32
|
+
compatible_agents: [generic, claude, codex]
|
|
33
|
+
notes: >
|
|
34
|
+
Linux-oriented (/var/log/auth.log with sudo entries). On macOS use
|
|
35
|
+
`log show --predicate 'process == "sudo"'` instead and adapt the command.
|