@wonsukchoi/crondex 0.12.0 → 0.14.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
@@ -25,7 +25,7 @@ access.
25
25
  ```bash
26
26
  npx @wonsukchoi/crondex recommend "warn me before my SSL cert expires"
27
27
  npx @wonsukchoi/crondex show ssl-cert-expiry-check
28
- npx @wonsukchoi/crondex add ssl-cert-expiry-check --dest ./cron/ssl-cert-expiry-check.yaml
28
+ npx @wonsukchoi/crondex deploy ssl-cert-expiry-check --var host=example.com
29
29
  ```
30
30
 
31
31
  - `recommend "<what you want>"` — find the closest matching job (zero
@@ -35,6 +35,14 @@ npx @wonsukchoi/crondex add ssl-cert-expiry-check --dest ./cron/ssl-cert-expiry-
35
35
  - `show <id>` — print a job's full YAML
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
+ - `deploy <id> [--target crontab|github-actions] [--var name=value ...]`
39
+ — turn a job into something that actually runs: prints a ready crontab
40
+ line (or installs it into your own crontab with `--install`), or writes
41
+ a scheduled GitHub Actions workflow file. `--var` overrides a variable's
42
+ default (repeatable). `hybrid` jobs deploy their `command` by default;
43
+ pass `--mode prompt` to deploy the `prompt` side instead — for a
44
+ crontab target that means wiring in your own agent CLI via a
45
+ `CRONDEX_AGENT_CLI` env var, since crondex can't guess its syntax.
38
46
 
39
47
  Add `--json` to `list`/`categories`/`show`/`recommend` for machine-readable
40
48
  output — useful when an agent is parsing the result programmatically
@@ -91,54 +99,54 @@ tags, variables) use `crondex list`, `crondex recommend`, or browse
91
99
  `jobs/<category>/` directly.
92
100
 
93
101
  <!-- BEGIN JOB SUMMARY -->
94
- 257 jobs across 44 categories:
95
-
96
- | category | jobs |
97
- |---|---|
98
- | `agriculture` | 5 |
99
- | `automotive` | 3 |
100
- | `childcare` | 3 |
101
- | `construction` | 5 |
102
- | `content` | 13 |
103
- | `creator` | 5 |
104
- | `crypto` | 5 |
105
- | `devops` | 26 |
106
- | `ecommerce` | 5 |
107
- | `education` | 5 |
108
- | `events` | 5 |
109
- | `finance` | 10 |
110
- | `fitness` | 3 |
111
- | `fleet` | 5 |
112
- | `gaming` | 5 |
113
- | `government` | 3 |
114
- | `growth` | 5 |
115
- | `healthcare` | 5 |
116
- | `hiring` | 5 |
117
- | `home` | 5 |
118
- | `hospitality` | 5 |
119
- | `hr` | 5 |
120
- | `insurance` | 5 |
121
- | `inventory` | 5 |
122
- | `investing` | 5 |
123
- | `learning` | 5 |
124
- | `legal` | 5 |
125
- | `logistics` | 5 |
126
- | `manufacturing` | 5 |
127
- | `marketing` | 5 |
128
- | `nonprofit` | 5 |
129
- | `personal` | 9 |
130
- | `podcast` | 5 |
131
- | `productivity` | 9 |
132
- | `realestate` | 5 |
133
- | `restaurant` | 5 |
134
- | `retail` | 3 |
135
- | `sales` | 5 |
136
- | `security` | 14 |
137
- | `support` | 5 |
138
- | `team` | 5 |
139
- | `travel` | 5 |
140
- | `veterinary` | 3 |
141
- | `warehousing` | 3 |
102
+ 271 jobs across 44 categories:
103
+
104
+ | category | jobs | description |
105
+ |---|---|---|
106
+ | `agriculture` | 5 | Farm operations — weather risk, irrigation, equipment, market prices. |
107
+ | `automotive` | 5 | Repair shop workflow — repair orders, parts, loaners. |
108
+ | `childcare` | 5 | Daycare compliance and ops — ratios, immunizations, tuition. |
109
+ | `construction` | 5 | Job site compliance — permits, inspections, change orders, rentals. |
110
+ | `content` | 13 | Site/content health — SEO, broken links, freshness, repurposing. |
111
+ | `creator` | 5 | Influencer/creator ops — content calendar, cross-posting, sponsorships. |
112
+ | `crypto` | 5 | Wallets, gas prices, DeFi risk, and token unlock schedules. |
113
+ | `devops` | 26 | Infra health — backups, deploys, dependencies, monitoring. |
114
+ | `ecommerce` | 5 | Storefront ops — carts, stock, returns, reviews. |
115
+ | `education` | 5 | Classroom ops — grading, attendance, deadlines, permission slips. |
116
+ | `events` | 5 | Event planning — budget, RSVPs, staffing, vendors, day-of check-in. |
117
+ | `finance` | 10 | Personal/business finance — budgets, invoices, taxes, subscriptions. |
118
+ | `fitness` | 5 | Gym/studio ops — memberships, class utilization, equipment. |
119
+ | `fleet` | 5 | Vehicle fleet compliance — maintenance, registration, licenses, fuel. |
120
+ | `gaming` | 5 | Streaming and community server ops — schedules, patches, tournaments. |
121
+ | `government` | 5 | Public-sector ops — records requests, permits, constituent casework. |
122
+ | `growth` | 5 | Lifecycle marketing — churn, trials, onboarding, reviews. |
123
+ | `healthcare` | 5 | Clinic ops — appointments, recalls, licenses, lab results. |
124
+ | `hiring` | 5 | Recruiting pipeline — candidates, offers, interviews, reqs. |
125
+ | `home` | 5 | Household reminders — maintenance, warranties, plants, safety. |
126
+ | `hospitality` | 5 | Hotel ops — guest reviews, inspections, maintenance requests. |
127
+ | `hr` | 5 | People ops — payroll, onboarding, benefits, reviews, offboarding. |
128
+ | `insurance` | 5 | Policy tracking — renewals, claims, coverage, certificates. |
129
+ | `inventory` | 5 | Stock accuracy — counts, shrinkage, expiry, overstock. |
130
+ | `investing` | 5 | Portfolio tracking — prices, dividends, rebalancing, taxes. |
131
+ | `learning` | 5 | Personal learning — certs, courses, flashcards, reading. |
132
+ | `legal` | 5 | Contracts and deadlines — NDAs, trademarks, court, compliance filings. |
133
+ | `logistics` | 5 | Shipping ops — customs, freight, delays, fees. |
134
+ | `manufacturing` | 5 | Production ops — downtime, defects, maintenance, suppliers, materials. |
135
+ | `marketing` | 5 | Campaign ops — ad spend, SEO rank, deliverability, competitors. |
136
+ | `nonprofit` | 5 | Fundraising ops — grants, donors, volunteers, board follow-ups. |
137
+ | `personal` | 9 | Daily life reminders — bills, habits, meals, screen time. |
138
+ | `podcast` | 5 | Show ops — publish cadence, guests, sponsors, ratings. |
139
+ | `productivity` | 9 | Work habits — inbox, standups, focus, meetings, reports. |
140
+ | `realestate` | 5 | Property management — leases, rent, vacancy, inspections, tax. |
141
+ | `restaurant` | 5 | Kitchen/FOH ops — food cost, labor cost, waste, inspections. |
142
+ | `retail` | 5 | Store ops — till reconciliation, opening checklist, scheduling. |
143
+ | `sales` | 5 | Pipeline ops — leads, deals, quota, CRM sync. |
144
+ | `security` | 14 | Security posture — keys, certs, access, scans, firewalls. |
145
+ | `support` | 5 | Helpdesk ops — SLA, backlog, CSAT, agent workload. |
146
+ | `team` | 5 | Team ops — 1:1s, on-call, PTO, anniversaries. |
147
+ | `travel` | 5 | Trip logistics — flights, passports, visas, insurance, miles. |
148
+ | `veterinary` | 5 | Clinic ops for animals — vaccines, controlled substances, boarding. |
149
+ | `warehousing` | 5 | Warehouse ops — dock scheduling, pick/pack errors, climate control. |
142
150
  <!-- END JOB SUMMARY -->
143
151
 
144
152
  ---
@@ -148,12 +156,12 @@ tags, variables) use `crondex list`, `crondex recommend`, or browse
148
156
  ```
149
157
  crondex/
150
158
  ├── llms.txt agent-discovery manifest (llms.txt convention)
151
- ├── bin/crondex.js CLI: list / categories / show / add / recommend / init
152
- ├── lib/recommend.js recommend's scoring logic (unit tested in test/)
159
+ ├── bin/crondex.js CLI: list / categories / show / add / recommend / init / deploy
160
+ ├── lib/ recommend, deploy, and catalog-building logic (unit tested in test/)
153
161
  ├── catalog.json generated index of every job — read this first
154
162
  ├── schema/job.schema.json spec every job file follows
155
163
  ├── jobs/ one YAML per job, grouped by category subdirectory
156
- └── scripts/ build-catalog.js, validate-jobs.js, lint-shell.js
164
+ └── scripts/ build-catalog.js, validate-jobs.js, lint-shell.js, check-duplicates.js, smoke-test.js
157
165
  ```
158
166
 
159
167
  ---
package/bin/crondex.js CHANGED
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  // CLI over catalog.json — browse jobs, read one, or pull one into your project.
3
- import { readFileSync, writeFileSync, existsSync } from "node:fs";
4
- import { join } from "node:path";
3
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
4
+ import { join, dirname } from "node:path";
5
+ import { execFileSync } from "node:child_process";
5
6
  import yaml from "js-yaml";
6
7
  import { tokenize, rankJobs } from "../lib/recommend.js";
8
+ import { CATEGORY_DESCRIPTIONS } from "../lib/category-descriptions.js";
9
+ import { resolveVariables, substitutePlaceholders, pickMode, buildCrontabLine, buildGithubActionsWorkflow } from "../lib/deploy.js";
7
10
 
8
11
  const ROOT = new URL("..", import.meta.url).pathname;
9
12
  const CATALOG = JSON.parse(readFileSync(join(ROOT, "catalog.json"), "utf8"));
@@ -20,6 +23,19 @@ function hasFlag(name) {
20
23
  return args.includes(`--${name}`);
21
24
  }
22
25
 
26
+ // Collects every `--var name=value` occurrence (the flag can repeat).
27
+ function varOverrides() {
28
+ const overrides = {};
29
+ for (let i = 0; i < args.length; i++) {
30
+ if (args[i] !== "--var") continue;
31
+ const pair = args[i + 1] ?? "";
32
+ const eq = pair.indexOf("=");
33
+ if (eq === -1) continue;
34
+ overrides[pair.slice(0, eq)] = pair.slice(eq + 1);
35
+ }
36
+ return overrides;
37
+ }
38
+
23
39
  function printJson(value) {
24
40
  console.log(JSON.stringify(value, null, 2));
25
41
  }
@@ -43,6 +59,8 @@ Usage:
43
59
  crondex add <id> [--dest <path>]
44
60
  crondex recommend "<what you want done>" [--limit <n>] [--json]
45
61
  crondex init <id> [--category <name>] [--dest <path>]
62
+ crondex deploy <id> [--target crontab|github-actions] [--mode script|prompt]
63
+ [--var name=value ...] [--dest <path>] [--install]
46
64
 
47
65
  Examples:
48
66
  crondex list --category devops
@@ -51,10 +69,19 @@ Examples:
51
69
  crondex add backup-reminder --dest ./cron/backup-reminder.yaml
52
70
  crondex recommend "warn me before my SSL cert expires"
53
71
  crondex init ssl-cert-expiry-check --category security
72
+ crondex deploy ssl-cert-expiry-check --var host=example.com --var port=443
73
+ crondex deploy repo-health-check --target github-actions
54
74
 
55
75
  Add --json to list/categories/show/recommend for machine-readable output —
56
76
  useful when an agent is parsing crondex's output programmatically instead
57
77
  of a human reading it.
78
+
79
+ deploy turns a job into something you can actually run: --target crontab
80
+ (default) prints a ready crontab line, or installs it into your own
81
+ crontab with --install; --target github-actions writes a scheduled
82
+ workflow file (default .github/workflows/<id>.yml). --var overrides a
83
+ job's variable defaults (repeatable). hybrid jobs default to --mode script
84
+ (zero tokens); pass --mode prompt to deploy the agent-prompt side instead.
58
85
  `);
59
86
  }
60
87
 
@@ -134,11 +161,16 @@ function categories() {
134
161
  const counts = {};
135
162
  for (const j of CATALOG.jobs) counts[j.category] = (counts[j.category] ?? 0) + 1;
136
163
  const sorted = Object.entries(counts).sort(([a], [b]) => a.localeCompare(b));
137
- if (hasFlag("json")) return printJson(sorted.map(([category, count]) => ({ category, count })));
164
+ if (hasFlag("json")) {
165
+ return printJson(
166
+ sorted.map(([category, count]) => ({ category, count, description: CATEGORY_DESCRIPTIONS[category] ?? "" }))
167
+ );
168
+ }
138
169
  console.log(catalogInfoLine());
139
170
  console.log();
140
171
  for (const [cat, n] of sorted) {
141
172
  console.log(`${cat} (${n})`);
173
+ if (CATEGORY_DESCRIPTIONS[cat]) console.log(` ${CATEGORY_DESCRIPTIONS[cat]}`);
142
174
  }
143
175
  }
144
176
 
@@ -162,6 +194,66 @@ function init(id) {
162
194
  console.log(`wrote ${dest} — fill in the fields, then \`npm run validate\` (see CONTRIBUTING.md to submit it upstream).`);
163
195
  }
164
196
 
197
+ function installCrontabLine(id, line) {
198
+ let existing = "";
199
+ try {
200
+ existing = execFileSync("crontab", ["-l"], { encoding: "utf8" });
201
+ } catch {
202
+ existing = "";
203
+ }
204
+ const marker = `# crondex:${id}`;
205
+ const kept = existing
206
+ .split("\n")
207
+ .filter((l) => l.trim().length > 0 && !l.includes(marker));
208
+ const updated = [...kept, line].join("\n") + "\n";
209
+ execFileSync("crontab", ["-"], { input: updated });
210
+ }
211
+
212
+ function deploy(id) {
213
+ const meta = findJob(id);
214
+ const doc = yaml.load(readFileSync(join(ROOT, meta.path), "utf8"));
215
+ doc.path = meta.path;
216
+ const target = flag("target") ?? "crontab";
217
+ const mode = pickMode(doc, flag("mode"));
218
+
219
+ if (mode === "prompt" && !doc.prompt) {
220
+ console.error(`"${id}" has no prompt to deploy (runner: ${doc.runner}). Try without --mode prompt.`);
221
+ process.exit(1);
222
+ }
223
+ if (mode === "script" && !doc.command) {
224
+ console.error(`"${id}" has no command to deploy (runner: ${doc.runner}). Try --mode prompt.`);
225
+ process.exit(1);
226
+ }
227
+
228
+ const values = resolveVariables(doc, varOverrides());
229
+ const command = doc.command ? substitutePlaceholders(doc.command, values) : undefined;
230
+ const prompt = doc.prompt ? substitutePlaceholders(doc.prompt, values) : undefined;
231
+
232
+ if (target === "crontab") {
233
+ const line = buildCrontabLine(doc, mode === "prompt" ? prompt : command, mode === "prompt");
234
+ if (hasFlag("install")) {
235
+ installCrontabLine(id, line);
236
+ console.log(`installed into your crontab (replacing any previous "${id}" entry):`);
237
+ console.log(line);
238
+ } else {
239
+ console.log(line);
240
+ }
241
+ } else if (target === "github-actions") {
242
+ const workflow = buildGithubActionsWorkflow(doc, { command, prompt, mode });
243
+ const dest = flag("dest") ?? join(".github/workflows", `${id}.yml`);
244
+ if (existsSync(dest)) {
245
+ console.error(`${dest} already exists — refusing to overwrite. Pass --dest to choose another path.`);
246
+ process.exit(1);
247
+ }
248
+ mkdirSync(dirname(dest), { recursive: true });
249
+ writeFileSync(dest, workflow);
250
+ console.log(`wrote ${dest}`);
251
+ } else {
252
+ console.error(`unknown --target "${target}" — use "crontab" or "github-actions".`);
253
+ process.exit(1);
254
+ }
255
+ }
256
+
165
257
  function add(id) {
166
258
  const meta = findJob(id);
167
259
  const dest = flag("dest") ?? `./${id}.yaml`;
@@ -208,6 +300,13 @@ switch (cmd) {
208
300
  }
209
301
  recommend(args[0]);
210
302
  break;
303
+ case "deploy":
304
+ if (!args[0]) {
305
+ console.error("usage: crondex deploy <id> [--target crontab|github-actions] [--mode script|prompt] [--var name=value ...] [--dest <path>] [--install]");
306
+ process.exit(1);
307
+ }
308
+ deploy(args[0]);
309
+ break;
211
310
  default:
212
311
  printHelp();
213
312
  }