@wonsukchoi/crondex 0.10.0 → 0.11.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
CHANGED
|
@@ -36,6 +36,10 @@ npx @wonsukchoi/crondex add ssl-cert-expiry-check --dest ./cron/ssl-cert-expiry-
|
|
|
36
36
|
- `add <id> [--dest path]` — copy it into your project to edit
|
|
37
37
|
- `init <id> [--category x]` — scaffold a brand-new job from the template
|
|
38
38
|
|
|
39
|
+
Add `--json` to `list`/`categories`/`show`/`recommend` for machine-readable
|
|
40
|
+
output — useful when an agent is parsing the result programmatically
|
|
41
|
+
instead of a human reading it.
|
|
42
|
+
|
|
39
43
|
No install needed — `npx` always runs against the latest catalog. Prefer
|
|
40
44
|
installing once? `npm install -g @wonsukchoi/crondex`, then drop the `npx`
|
|
41
45
|
prefix (run `npm update -g` later to pick up new jobs). Prefer no npm at
|
|
@@ -87,7 +91,7 @@ tags, variables) use `crondex list`, `crondex recommend`, or browse
|
|
|
87
91
|
`jobs/<category>/` directly.
|
|
88
92
|
|
|
89
93
|
<!-- BEGIN JOB SUMMARY -->
|
|
90
|
-
|
|
94
|
+
189 jobs across 37 categories:
|
|
91
95
|
|
|
92
96
|
| category | jobs |
|
|
93
97
|
|---|---|
|
|
@@ -113,7 +117,7 @@ tags, variables) use `crondex list`, `crondex recommend`, or browse
|
|
|
113
117
|
| `inventory` | 3 |
|
|
114
118
|
| `investing` | 3 |
|
|
115
119
|
| `learning` | 5 |
|
|
116
|
-
| `legal` |
|
|
120
|
+
| `legal` | 4 |
|
|
117
121
|
| `logistics` | 3 |
|
|
118
122
|
| `manufacturing` | 3 |
|
|
119
123
|
| `marketing` | 4 |
|
|
@@ -121,7 +125,7 @@ tags, variables) use `crondex list`, `crondex recommend`, or browse
|
|
|
121
125
|
| `personal` | 9 |
|
|
122
126
|
| `podcast` | 3 |
|
|
123
127
|
| `productivity` | 9 |
|
|
124
|
-
| `realestate` |
|
|
128
|
+
| `realestate` | 4 |
|
|
125
129
|
| `restaurant` | 3 |
|
|
126
130
|
| `sales` | 5 |
|
|
127
131
|
| `security` | 14 |
|
|
@@ -136,6 +140,7 @@ tags, variables) use `crondex list`, `crondex recommend`, or browse
|
|
|
136
140
|
|
|
137
141
|
```
|
|
138
142
|
crondex/
|
|
143
|
+
├── llms.txt agent-discovery manifest (llms.txt convention)
|
|
139
144
|
├── bin/crondex.js CLI: list / categories / show / add / recommend / init
|
|
140
145
|
├── lib/recommend.js recommend's scoring logic (unit tested in test/)
|
|
141
146
|
├── catalog.json generated index of every job — read this first
|
package/bin/crondex.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// CLI over catalog.json — browse jobs, read one, or pull one into your project.
|
|
3
3
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import yaml from "js-yaml";
|
|
5
6
|
import { tokenize, rankJobs } from "../lib/recommend.js";
|
|
6
7
|
|
|
7
8
|
const ROOT = new URL("..", import.meta.url).pathname;
|
|
@@ -15,6 +16,14 @@ function flag(name) {
|
|
|
15
16
|
return i === -1 ? undefined : args[i + 1];
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
function hasFlag(name) {
|
|
20
|
+
return args.includes(`--${name}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function printJson(value) {
|
|
24
|
+
console.log(JSON.stringify(value, null, 2));
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
function findJob(id) {
|
|
19
28
|
const meta = CATALOG.jobs.find((j) => j.id === id);
|
|
20
29
|
if (!meta) {
|
|
@@ -28,11 +37,11 @@ function printHelp() {
|
|
|
28
37
|
console.log(`crondex — browse and pull pre-made cron jobs
|
|
29
38
|
|
|
30
39
|
Usage:
|
|
31
|
-
crondex list [--category <name>] [--tag <name>]
|
|
32
|
-
crondex categories
|
|
33
|
-
crondex show <id>
|
|
40
|
+
crondex list [--category <name>] [--tag <name>] [--json]
|
|
41
|
+
crondex categories [--json]
|
|
42
|
+
crondex show <id> [--json]
|
|
34
43
|
crondex add <id> [--dest <path>]
|
|
35
|
-
crondex recommend "<what you want done>" [--limit <n>]
|
|
44
|
+
crondex recommend "<what you want done>" [--limit <n>] [--json]
|
|
36
45
|
crondex init <id> [--category <name>] [--dest <path>]
|
|
37
46
|
|
|
38
47
|
Examples:
|
|
@@ -42,20 +51,39 @@ Examples:
|
|
|
42
51
|
crondex add backup-reminder --dest ./cron/backup-reminder.yaml
|
|
43
52
|
crondex recommend "warn me before my SSL cert expires"
|
|
44
53
|
crondex init ssl-cert-expiry-check --category security
|
|
54
|
+
|
|
55
|
+
Add --json to list/categories/show/recommend for machine-readable output —
|
|
56
|
+
useful when an agent is parsing crondex's output programmatically instead
|
|
57
|
+
of a human reading it.
|
|
45
58
|
`);
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
function recommend(queryText) {
|
|
49
62
|
const limit = Number(flag("limit")) || 5;
|
|
63
|
+
const json = hasFlag("json");
|
|
50
64
|
if (!tokenize(queryText).length) {
|
|
65
|
+
if (json) return printJson([]);
|
|
51
66
|
console.log("query too vague to match on — describe what you want the job to check or remind you about.");
|
|
52
67
|
return;
|
|
53
68
|
}
|
|
54
69
|
const ranked = rankJobs(CATALOG.jobs, queryText, limit);
|
|
55
70
|
if (!ranked.length) {
|
|
71
|
+
if (json) return printJson([]);
|
|
56
72
|
console.log(`no confident match for "${queryText}". Run "crondex list" to browse everything.`);
|
|
57
73
|
return;
|
|
58
74
|
}
|
|
75
|
+
if (json) {
|
|
76
|
+
return printJson(
|
|
77
|
+
ranked.map((r) => ({
|
|
78
|
+
id: r.job.id,
|
|
79
|
+
category: r.job.category,
|
|
80
|
+
score: r.score,
|
|
81
|
+
matched_terms: r.matchedTerms,
|
|
82
|
+
modes: r.job.modes,
|
|
83
|
+
description: r.job.description,
|
|
84
|
+
}))
|
|
85
|
+
);
|
|
86
|
+
}
|
|
59
87
|
console.log(`top match${ranked.length > 1 ? "es" : ""} for "${queryText}":`);
|
|
60
88
|
console.log();
|
|
61
89
|
for (const r of ranked) {
|
|
@@ -77,9 +105,11 @@ function catalogInfoLine() {
|
|
|
77
105
|
function list() {
|
|
78
106
|
const category = flag("category");
|
|
79
107
|
const tag = flag("tag");
|
|
108
|
+
const json = hasFlag("json");
|
|
80
109
|
const jobs = CATALOG.jobs.filter(
|
|
81
110
|
(j) => (!category || j.category === category) && (!tag || j.tags.includes(tag))
|
|
82
111
|
);
|
|
112
|
+
if (json) return printJson(jobs);
|
|
83
113
|
console.log(catalogInfoLine());
|
|
84
114
|
console.log();
|
|
85
115
|
if (!jobs.length) {
|
|
@@ -94,15 +124,20 @@ function list() {
|
|
|
94
124
|
}
|
|
95
125
|
|
|
96
126
|
function show(id) {
|
|
97
|
-
|
|
127
|
+
const meta = findJob(id);
|
|
128
|
+
const raw = readFileSync(join(ROOT, meta.path), "utf8");
|
|
129
|
+
if (hasFlag("json")) return printJson(yaml.load(raw));
|
|
130
|
+
console.log(raw);
|
|
98
131
|
}
|
|
99
132
|
|
|
100
133
|
function categories() {
|
|
101
134
|
const counts = {};
|
|
102
135
|
for (const j of CATALOG.jobs) counts[j.category] = (counts[j.category] ?? 0) + 1;
|
|
136
|
+
const sorted = Object.entries(counts).sort(([a], [b]) => a.localeCompare(b));
|
|
137
|
+
if (hasFlag("json")) return printJson(sorted.map(([category, count]) => ({ category, count })));
|
|
103
138
|
console.log(catalogInfoLine());
|
|
104
139
|
console.log();
|
|
105
|
-
for (const [cat, n] of
|
|
140
|
+
for (const [cat, n] of sorted) {
|
|
106
141
|
console.log(`${cat} (${n})`);
|
|
107
142
|
}
|
|
108
143
|
}
|
package/catalog.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"generated_by": "scripts/build-catalog.js",
|
|
3
3
|
"schema": "schema/job.schema.json",
|
|
4
|
-
"count":
|
|
4
|
+
"count": 189,
|
|
5
5
|
"jobs": [
|
|
6
6
|
{
|
|
7
7
|
"id": "1on1-prep-reminder",
|
|
@@ -809,6 +809,31 @@
|
|
|
809
809
|
],
|
|
810
810
|
"path": "jobs/learning/course-progress-checkin.yaml"
|
|
811
811
|
},
|
|
812
|
+
{
|
|
813
|
+
"id": "court-deadline-reminder",
|
|
814
|
+
"version": 1,
|
|
815
|
+
"name": "Court Deadline Reminder",
|
|
816
|
+
"description": "Checks a tracked list of court/filing deadlines and warns before any come due. Use this if a filing deadline has ever been missed because it lived only in one person's head or a sticky note instead of a shared, checked calendar.",
|
|
817
|
+
"category": "legal",
|
|
818
|
+
"tags": [
|
|
819
|
+
"legal",
|
|
820
|
+
"court",
|
|
821
|
+
"deadline",
|
|
822
|
+
"compliance"
|
|
823
|
+
],
|
|
824
|
+
"schedule": "0 7 * * *",
|
|
825
|
+
"timezone": "UTC",
|
|
826
|
+
"runner": "shell",
|
|
827
|
+
"modes": [
|
|
828
|
+
"script"
|
|
829
|
+
],
|
|
830
|
+
"compatible_agents": [
|
|
831
|
+
"generic",
|
|
832
|
+
"claude",
|
|
833
|
+
"codex"
|
|
834
|
+
],
|
|
835
|
+
"path": "jobs/legal/court-deadline-reminder.yaml"
|
|
836
|
+
},
|
|
812
837
|
{
|
|
813
838
|
"id": "coverage-gap-audit",
|
|
814
839
|
"version": 1,
|
|
@@ -2385,12 +2410,12 @@
|
|
|
2385
2410
|
"id": "low-stock-alert",
|
|
2386
2411
|
"version": 1,
|
|
2387
2412
|
"name": "Low Stock Alert",
|
|
2388
|
-
"description": "Checks inventory
|
|
2413
|
+
"description": "Checks a general-purpose inventory database (warehouse, retail backroom, restaurant supplies — anything with a quantity and a set reorder point) against that fixed threshold and flags what's running low. Use this for physical stock tracked in a database, not tied to online sales; if you specifically need SKU reorder timing driven by e-commerce sales velocity and supplier lead time, see `low-stock-reorder-alert` instead.",
|
|
2389
2414
|
"category": "inventory",
|
|
2390
2415
|
"tags": [
|
|
2391
2416
|
"inventory",
|
|
2392
2417
|
"stock",
|
|
2393
|
-
"
|
|
2418
|
+
"warehouse"
|
|
2394
2419
|
],
|
|
2395
2420
|
"schedule": "0 7 * * *",
|
|
2396
2421
|
"timezone": "UTC",
|
|
@@ -3032,6 +3057,31 @@
|
|
|
3032
3057
|
],
|
|
3033
3058
|
"path": "jobs/manufacturing/production-line-downtime-watch.yaml"
|
|
3034
3059
|
},
|
|
3060
|
+
{
|
|
3061
|
+
"id": "property-maintenance-inspection-reminder",
|
|
3062
|
+
"version": 1,
|
|
3063
|
+
"name": "Property Maintenance Inspection Reminder",
|
|
3064
|
+
"description": "Tracks when each property was last inspected and flags any that are due for their next routine walkthrough. Use this if a property has ever gone a year or more without a proper inspection because nothing forced anyone to schedule one.",
|
|
3065
|
+
"category": "realestate",
|
|
3066
|
+
"tags": [
|
|
3067
|
+
"realestate",
|
|
3068
|
+
"maintenance",
|
|
3069
|
+
"inspection",
|
|
3070
|
+
"property"
|
|
3071
|
+
],
|
|
3072
|
+
"schedule": "0 8 * * 1",
|
|
3073
|
+
"timezone": "UTC",
|
|
3074
|
+
"runner": "shell",
|
|
3075
|
+
"modes": [
|
|
3076
|
+
"script"
|
|
3077
|
+
],
|
|
3078
|
+
"compatible_agents": [
|
|
3079
|
+
"generic",
|
|
3080
|
+
"claude",
|
|
3081
|
+
"codex"
|
|
3082
|
+
],
|
|
3083
|
+
"path": "jobs/realestate/property-maintenance-inspection-reminder.yaml"
|
|
3084
|
+
},
|
|
3035
3085
|
{
|
|
3036
3086
|
"id": "pto-balance-check",
|
|
3037
3087
|
"version": 1,
|
|
@@ -2,11 +2,14 @@ id: low-stock-alert
|
|
|
2
2
|
version: 1
|
|
3
3
|
name: Low Stock Alert
|
|
4
4
|
description: >
|
|
5
|
-
Checks
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Checks a general-purpose inventory database (warehouse, retail backroom,
|
|
6
|
+
restaurant supplies — anything with a quantity and a set reorder point)
|
|
7
|
+
against that fixed threshold and flags what's running low. Use this for
|
|
8
|
+
physical stock tracked in a database, not tied to online sales; if you
|
|
9
|
+
specifically need SKU reorder timing driven by e-commerce sales velocity
|
|
10
|
+
and supplier lead time, see `low-stock-reorder-alert` instead.
|
|
8
11
|
category: inventory
|
|
9
|
-
tags: [inventory, stock,
|
|
12
|
+
tags: [inventory, stock, warehouse]
|
|
10
13
|
schedule: "0 7 * * *"
|
|
11
14
|
timezone: "UTC"
|
|
12
15
|
runner: shell
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
id: court-deadline-reminder
|
|
2
|
+
version: 1
|
|
3
|
+
name: Court Deadline Reminder
|
|
4
|
+
description: >
|
|
5
|
+
Checks a tracked list of court/filing deadlines and warns before any come
|
|
6
|
+
due. Use this if a filing deadline has ever been missed because it lived
|
|
7
|
+
only in one person's head or a sticky note instead of a shared, checked
|
|
8
|
+
calendar.
|
|
9
|
+
category: legal
|
|
10
|
+
tags: [legal, court, deadline, compliance]
|
|
11
|
+
schedule: "0 7 * * *"
|
|
12
|
+
timezone: "UTC"
|
|
13
|
+
runner: shell
|
|
14
|
+
command: >
|
|
15
|
+
awk -F',' -v days="{{warn_days_before}}" '
|
|
16
|
+
NR==1 {next}
|
|
17
|
+
{
|
|
18
|
+
cmd = "date -d \"" $3 "\" +%s 2>/dev/null || date -j -f %Y-%m-%d \"" $3 "\" +%s 2>/dev/null";
|
|
19
|
+
cmd | getline due_epoch; close(cmd);
|
|
20
|
+
"date +%s" | getline now_epoch; close("date +%s");
|
|
21
|
+
diff_days = (due_epoch - now_epoch) / 86400;
|
|
22
|
+
if (diff_days <= days && diff_days >= 0) {
|
|
23
|
+
printf "DUE SOON: %s — %s due %s (%d days)\n", $1, $2, $3, diff_days;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
' "{{deadlines_csv_path}}"
|
|
27
|
+
variables:
|
|
28
|
+
deadlines_csv_path:
|
|
29
|
+
default: "./court-deadlines.csv"
|
|
30
|
+
description: "Path to a CSV with columns: matter_name,deadline_type,due_date (YYYY-MM-DD) — e.g. one row per filing/response/statute-of-limitations deadline."
|
|
31
|
+
warn_days_before:
|
|
32
|
+
default: 14
|
|
33
|
+
description: How many days before a deadline to start warning.
|
|
34
|
+
compatible_agents: [generic, claude, codex]
|
|
35
|
+
notes: >
|
|
36
|
+
This is a plain date-threshold check, not a rules-based calendaring
|
|
37
|
+
engine — it won't calculate deadlines from court rules the way dedicated
|
|
38
|
+
legal calendaring software does. Export computed due dates from that
|
|
39
|
+
system into the CSV, or maintain them manually, and use this as the
|
|
40
|
+
"did anyone actually notice" backstop.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
id: property-maintenance-inspection-reminder
|
|
2
|
+
version: 1
|
|
3
|
+
name: Property Maintenance Inspection Reminder
|
|
4
|
+
description: >
|
|
5
|
+
Tracks when each property was last inspected and flags any that are due
|
|
6
|
+
for their next routine walkthrough. Use this if a property has ever gone
|
|
7
|
+
a year or more without a proper inspection because nothing forced anyone
|
|
8
|
+
to schedule one.
|
|
9
|
+
category: realestate
|
|
10
|
+
tags: [realestate, maintenance, inspection, property]
|
|
11
|
+
schedule: "0 8 * * 1"
|
|
12
|
+
timezone: "UTC"
|
|
13
|
+
runner: shell
|
|
14
|
+
command: >
|
|
15
|
+
awk -F',' -v days="{{inspection_interval_days}}" '
|
|
16
|
+
NR==1 {next}
|
|
17
|
+
{
|
|
18
|
+
cmd = "date -d \"" $2 "\" +%s 2>/dev/null || date -j -f %Y-%m-%d \"" $2 "\" +%s 2>/dev/null";
|
|
19
|
+
cmd | getline last_epoch; close(cmd);
|
|
20
|
+
"date +%s" | getline now_epoch; close("date +%s");
|
|
21
|
+
days_since = (now_epoch - last_epoch) / 86400;
|
|
22
|
+
if (days_since >= days) {
|
|
23
|
+
printf "DUE: %s — last inspected %s (%d days ago)\n", $1, $2, days_since;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
' "{{properties_csv_path}}"
|
|
27
|
+
variables:
|
|
28
|
+
properties_csv_path:
|
|
29
|
+
default: "./property-inspections.csv"
|
|
30
|
+
description: "Path to a CSV with columns: property_name,last_inspection_date (YYYY-MM-DD)."
|
|
31
|
+
inspection_interval_days:
|
|
32
|
+
default: 180
|
|
33
|
+
description: How often each property should be inspected, in days.
|
|
34
|
+
compatible_agents: [generic, claude, codex]
|
|
35
|
+
notes: Assumes a maintained CSV of last-inspection dates per property — export it from whatever property management system you use, or update it manually after each walkthrough.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wonsukchoi/crondex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "A public directory of pre-made, agent-editable cron jobs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,10 +35,13 @@
|
|
|
35
35
|
"build-catalog": "node scripts/build-catalog.js",
|
|
36
36
|
"validate": "node scripts/validate-jobs.js",
|
|
37
37
|
"lint-shell": "node scripts/lint-shell.js",
|
|
38
|
+
"check-duplicates": "node scripts/check-duplicates.js",
|
|
38
39
|
"test": "node --test"
|
|
39
40
|
},
|
|
40
|
-
"
|
|
41
|
-
"ajv": "^8.17.1",
|
|
41
|
+
"dependencies": {
|
|
42
42
|
"js-yaml": "^4.1.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"ajv": "^8.17.1"
|
|
43
46
|
}
|
|
44
47
|
}
|