@wonsukchoi/crondex 0.12.0 → 0.13.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 +46 -46
- package/bin/crondex.js +7 -1
- package/lib/catalog-summary.js +47 -0
- package/lib/category-descriptions.js +50 -0
- package/lib/duplicates.js +29 -0
- package/lib/shellcheck-prep.js +20 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,52 +93,52 @@ tags, variables) use `crondex list`, `crondex recommend`, or browse
|
|
|
93
93
|
<!-- BEGIN JOB SUMMARY -->
|
|
94
94
|
257 jobs across 44 categories:
|
|
95
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 |
|
|
96
|
+
| category | jobs | description |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `agriculture` | 5 | Farm operations — weather risk, irrigation, equipment, market prices. |
|
|
99
|
+
| `automotive` | 3 | Repair shop workflow — repair orders, parts, loaners. |
|
|
100
|
+
| `childcare` | 3 | Daycare compliance and ops — ratios, immunizations, tuition. |
|
|
101
|
+
| `construction` | 5 | Job site compliance — permits, inspections, change orders, rentals. |
|
|
102
|
+
| `content` | 13 | Site/content health — SEO, broken links, freshness, repurposing. |
|
|
103
|
+
| `creator` | 5 | Influencer/creator ops — content calendar, cross-posting, sponsorships. |
|
|
104
|
+
| `crypto` | 5 | Wallets, gas prices, DeFi risk, and token unlock schedules. |
|
|
105
|
+
| `devops` | 26 | Infra health — backups, deploys, dependencies, monitoring. |
|
|
106
|
+
| `ecommerce` | 5 | Storefront ops — carts, stock, returns, reviews. |
|
|
107
|
+
| `education` | 5 | Classroom ops — grading, attendance, deadlines, permission slips. |
|
|
108
|
+
| `events` | 5 | Event planning — budget, RSVPs, staffing, vendors, day-of check-in. |
|
|
109
|
+
| `finance` | 10 | Personal/business finance — budgets, invoices, taxes, subscriptions. |
|
|
110
|
+
| `fitness` | 3 | Gym/studio ops — memberships, class utilization, equipment. |
|
|
111
|
+
| `fleet` | 5 | Vehicle fleet compliance — maintenance, registration, licenses, fuel. |
|
|
112
|
+
| `gaming` | 5 | Streaming and community server ops — schedules, patches, tournaments. |
|
|
113
|
+
| `government` | 3 | Public-sector ops — records requests, permits, constituent casework. |
|
|
114
|
+
| `growth` | 5 | Lifecycle marketing — churn, trials, onboarding, reviews. |
|
|
115
|
+
| `healthcare` | 5 | Clinic ops — appointments, recalls, licenses, lab results. |
|
|
116
|
+
| `hiring` | 5 | Recruiting pipeline — candidates, offers, interviews, reqs. |
|
|
117
|
+
| `home` | 5 | Household reminders — maintenance, warranties, plants, safety. |
|
|
118
|
+
| `hospitality` | 5 | Hotel ops — guest reviews, inspections, maintenance requests. |
|
|
119
|
+
| `hr` | 5 | People ops — payroll, onboarding, benefits, reviews, offboarding. |
|
|
120
|
+
| `insurance` | 5 | Policy tracking — renewals, claims, coverage, certificates. |
|
|
121
|
+
| `inventory` | 5 | Stock accuracy — counts, shrinkage, expiry, overstock. |
|
|
122
|
+
| `investing` | 5 | Portfolio tracking — prices, dividends, rebalancing, taxes. |
|
|
123
|
+
| `learning` | 5 | Personal learning — certs, courses, flashcards, reading. |
|
|
124
|
+
| `legal` | 5 | Contracts and deadlines — NDAs, trademarks, court, compliance filings. |
|
|
125
|
+
| `logistics` | 5 | Shipping ops — customs, freight, delays, fees. |
|
|
126
|
+
| `manufacturing` | 5 | Production ops — downtime, defects, maintenance, suppliers, materials. |
|
|
127
|
+
| `marketing` | 5 | Campaign ops — ad spend, SEO rank, deliverability, competitors. |
|
|
128
|
+
| `nonprofit` | 5 | Fundraising ops — grants, donors, volunteers, board follow-ups. |
|
|
129
|
+
| `personal` | 9 | Daily life reminders — bills, habits, meals, screen time. |
|
|
130
|
+
| `podcast` | 5 | Show ops — publish cadence, guests, sponsors, ratings. |
|
|
131
|
+
| `productivity` | 9 | Work habits — inbox, standups, focus, meetings, reports. |
|
|
132
|
+
| `realestate` | 5 | Property management — leases, rent, vacancy, inspections, tax. |
|
|
133
|
+
| `restaurant` | 5 | Kitchen/FOH ops — food cost, labor cost, waste, inspections. |
|
|
134
|
+
| `retail` | 3 | Store ops — till reconciliation, opening checklist, scheduling. |
|
|
135
|
+
| `sales` | 5 | Pipeline ops — leads, deals, quota, CRM sync. |
|
|
136
|
+
| `security` | 14 | Security posture — keys, certs, access, scans, firewalls. |
|
|
137
|
+
| `support` | 5 | Helpdesk ops — SLA, backlog, CSAT, agent workload. |
|
|
138
|
+
| `team` | 5 | Team ops — 1:1s, on-call, PTO, anniversaries. |
|
|
139
|
+
| `travel` | 5 | Trip logistics — flights, passports, visas, insurance, miles. |
|
|
140
|
+
| `veterinary` | 3 | Clinic ops for animals — vaccines, controlled substances, boarding. |
|
|
141
|
+
| `warehousing` | 3 | Warehouse ops — dock scheduling, pick/pack errors, climate control. |
|
|
142
142
|
<!-- END JOB SUMMARY -->
|
|
143
143
|
|
|
144
144
|
---
|
package/bin/crondex.js
CHANGED
|
@@ -4,6 +4,7 @@ import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import yaml from "js-yaml";
|
|
6
6
|
import { tokenize, rankJobs } from "../lib/recommend.js";
|
|
7
|
+
import { CATEGORY_DESCRIPTIONS } from "../lib/category-descriptions.js";
|
|
7
8
|
|
|
8
9
|
const ROOT = new URL("..", import.meta.url).pathname;
|
|
9
10
|
const CATALOG = JSON.parse(readFileSync(join(ROOT, "catalog.json"), "utf8"));
|
|
@@ -134,11 +135,16 @@ function categories() {
|
|
|
134
135
|
const counts = {};
|
|
135
136
|
for (const j of CATALOG.jobs) counts[j.category] = (counts[j.category] ?? 0) + 1;
|
|
136
137
|
const sorted = Object.entries(counts).sort(([a], [b]) => a.localeCompare(b));
|
|
137
|
-
if (hasFlag("json"))
|
|
138
|
+
if (hasFlag("json")) {
|
|
139
|
+
return printJson(
|
|
140
|
+
sorted.map(([category, count]) => ({ category, count, description: CATEGORY_DESCRIPTIONS[category] ?? "" }))
|
|
141
|
+
);
|
|
142
|
+
}
|
|
138
143
|
console.log(catalogInfoLine());
|
|
139
144
|
console.log();
|
|
140
145
|
for (const [cat, n] of sorted) {
|
|
141
146
|
console.log(`${cat} (${n})`);
|
|
147
|
+
if (CATEGORY_DESCRIPTIONS[cat]) console.log(` ${CATEGORY_DESCRIPTIONS[cat]}`);
|
|
142
148
|
}
|
|
143
149
|
}
|
|
144
150
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Catalog-building logic behind scripts/build-catalog.js — pulled out so it's unit
|
|
2
|
+
// testable directly (see test/catalog-summary.test.js).
|
|
3
|
+
|
|
4
|
+
export const MODES_BY_RUNNER = {
|
|
5
|
+
"agent-prompt": ["agent-prompt"],
|
|
6
|
+
shell: ["script"],
|
|
7
|
+
hybrid: ["script", "agent-prompt"],
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function modesForRunner(runner) {
|
|
11
|
+
return MODES_BY_RUNNER[runner] ?? [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function countByCategory(jobs) {
|
|
15
|
+
const counts = new Map();
|
|
16
|
+
for (const j of jobs) counts.set(j.category, (counts.get(j.category) ?? 0) + 1);
|
|
17
|
+
return counts;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function findMissingDescriptions(categories, categoryDescriptions) {
|
|
21
|
+
return categories.filter((c) => !categoryDescriptions[c]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Builds the markdown lines that go between the BEGIN/END JOB SUMMARY markers.
|
|
25
|
+
export function buildSummaryLines(jobs, categoryDescriptions) {
|
|
26
|
+
const byCategory = countByCategory(jobs);
|
|
27
|
+
const categories = [...byCategory.keys()].sort();
|
|
28
|
+
return [
|
|
29
|
+
`${jobs.length} jobs across ${categories.length} categories:`,
|
|
30
|
+
"",
|
|
31
|
+
"| category | jobs | description |",
|
|
32
|
+
"|---|---|---|",
|
|
33
|
+
...categories.map((c) => `| \`${c}\` | ${byCategory.get(c)} | ${categoryDescriptions[c] ?? ""} |`),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const BEGIN = "<!-- BEGIN JOB SUMMARY -->";
|
|
38
|
+
const END = "<!-- END JOB SUMMARY -->";
|
|
39
|
+
|
|
40
|
+
// Splices summaryLines between the BEGIN/END markers in readmeText. Returns null if
|
|
41
|
+
// the markers aren't found (caller decides how to warn/handle that).
|
|
42
|
+
export function spliceReadmeSummary(readmeText, summaryLines) {
|
|
43
|
+
const start = readmeText.indexOf(BEGIN);
|
|
44
|
+
const end = readmeText.indexOf(END);
|
|
45
|
+
if (start === -1 || end === -1) return null;
|
|
46
|
+
return readmeText.slice(0, start + BEGIN.length) + "\n" + summaryLines.join("\n") + "\n" + readmeText.slice(end);
|
|
47
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// One-line description per job category, for the README table and `crondex categories`.
|
|
2
|
+
// Keep entries short — this is a browsing aid, not a spec. Add a new entry whenever a
|
|
3
|
+
// job introduces a category that isn't listed here (a missing entry is a soft warning
|
|
4
|
+
// from scripts/build-catalog.js, not a hard failure).
|
|
5
|
+
export const CATEGORY_DESCRIPTIONS = {
|
|
6
|
+
agriculture: "Farm operations — weather risk, irrigation, equipment, market prices.",
|
|
7
|
+
automotive: "Repair shop workflow — repair orders, parts, loaners.",
|
|
8
|
+
childcare: "Daycare compliance and ops — ratios, immunizations, tuition.",
|
|
9
|
+
construction: "Job site compliance — permits, inspections, change orders, rentals.",
|
|
10
|
+
content: "Site/content health — SEO, broken links, freshness, repurposing.",
|
|
11
|
+
creator: "Influencer/creator ops — content calendar, cross-posting, sponsorships.",
|
|
12
|
+
crypto: "Wallets, gas prices, DeFi risk, and token unlock schedules.",
|
|
13
|
+
devops: "Infra health — backups, deploys, dependencies, monitoring.",
|
|
14
|
+
ecommerce: "Storefront ops — carts, stock, returns, reviews.",
|
|
15
|
+
education: "Classroom ops — grading, attendance, deadlines, permission slips.",
|
|
16
|
+
events: "Event planning — budget, RSVPs, staffing, vendors, day-of check-in.",
|
|
17
|
+
finance: "Personal/business finance — budgets, invoices, taxes, subscriptions.",
|
|
18
|
+
fitness: "Gym/studio ops — memberships, class utilization, equipment.",
|
|
19
|
+
fleet: "Vehicle fleet compliance — maintenance, registration, licenses, fuel.",
|
|
20
|
+
gaming: "Streaming and community server ops — schedules, patches, tournaments.",
|
|
21
|
+
government: "Public-sector ops — records requests, permits, constituent casework.",
|
|
22
|
+
growth: "Lifecycle marketing — churn, trials, onboarding, reviews.",
|
|
23
|
+
healthcare: "Clinic ops — appointments, recalls, licenses, lab results.",
|
|
24
|
+
hiring: "Recruiting pipeline — candidates, offers, interviews, reqs.",
|
|
25
|
+
home: "Household reminders — maintenance, warranties, plants, safety.",
|
|
26
|
+
hospitality: "Hotel ops — guest reviews, inspections, maintenance requests.",
|
|
27
|
+
hr: "People ops — payroll, onboarding, benefits, reviews, offboarding.",
|
|
28
|
+
insurance: "Policy tracking — renewals, claims, coverage, certificates.",
|
|
29
|
+
inventory: "Stock accuracy — counts, shrinkage, expiry, overstock.",
|
|
30
|
+
investing: "Portfolio tracking — prices, dividends, rebalancing, taxes.",
|
|
31
|
+
learning: "Personal learning — certs, courses, flashcards, reading.",
|
|
32
|
+
legal: "Contracts and deadlines — NDAs, trademarks, court, compliance filings.",
|
|
33
|
+
logistics: "Shipping ops — customs, freight, delays, fees.",
|
|
34
|
+
manufacturing: "Production ops — downtime, defects, maintenance, suppliers, materials.",
|
|
35
|
+
marketing: "Campaign ops — ad spend, SEO rank, deliverability, competitors.",
|
|
36
|
+
nonprofit: "Fundraising ops — grants, donors, volunteers, board follow-ups.",
|
|
37
|
+
personal: "Daily life reminders — bills, habits, meals, screen time.",
|
|
38
|
+
podcast: "Show ops — publish cadence, guests, sponsors, ratings.",
|
|
39
|
+
productivity: "Work habits — inbox, standups, focus, meetings, reports.",
|
|
40
|
+
realestate: "Property management — leases, rent, vacancy, inspections, tax.",
|
|
41
|
+
restaurant: "Kitchen/FOH ops — food cost, labor cost, waste, inspections.",
|
|
42
|
+
retail: "Store ops — till reconciliation, opening checklist, scheduling.",
|
|
43
|
+
sales: "Pipeline ops — leads, deals, quota, CRM sync.",
|
|
44
|
+
security: "Security posture — keys, certs, access, scans, firewalls.",
|
|
45
|
+
support: "Helpdesk ops — SLA, backlog, CSAT, agent workload.",
|
|
46
|
+
team: "Team ops — 1:1s, on-call, PTO, anniversaries.",
|
|
47
|
+
travel: "Trip logistics — flights, passports, visas, insurance, miles.",
|
|
48
|
+
veterinary: "Clinic ops for animals — vaccines, controlled substances, boarding.",
|
|
49
|
+
warehousing: "Warehouse ops — dock scheduling, pick/pack errors, climate control.",
|
|
50
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Near-duplicate detection logic behind scripts/check-duplicates.js — pulled out so
|
|
2
|
+
// it's unit testable directly (see test/duplicates.test.js).
|
|
3
|
+
|
|
4
|
+
export function jaccard(a, b) {
|
|
5
|
+
if (a.size === 0 && b.size === 0) return 0;
|
|
6
|
+
let intersection = 0;
|
|
7
|
+
for (const x of a) if (b.has(x)) intersection++;
|
|
8
|
+
const union = a.size + b.size - intersection;
|
|
9
|
+
return union === 0 ? 0 : intersection / union;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// jobs: array of { id, tags: Set<string>, description: Set<string>, ...rest }.
|
|
13
|
+
// A pair is flagged only when BOTH tag and description overlap clear their
|
|
14
|
+
// threshold — tag overlap alone false-positives on jobs that just share a couple
|
|
15
|
+
// of generic tags, and description overlap alone false-positives on jobs
|
|
16
|
+
// following the same wording template for genuinely different systems.
|
|
17
|
+
export function findDuplicates(jobs, { tagThreshold = 0.6, descThreshold = 0.5 } = {}) {
|
|
18
|
+
const flagged = [];
|
|
19
|
+
for (let i = 0; i < jobs.length; i++) {
|
|
20
|
+
for (let j = i + 1; j < jobs.length; j++) {
|
|
21
|
+
const a = jobs[i];
|
|
22
|
+
const b = jobs[j];
|
|
23
|
+
const tagSim = jaccard(a.tags, b.tags);
|
|
24
|
+
const descSim = jaccard(a.description, b.description);
|
|
25
|
+
if (tagSim >= tagThreshold && descSim >= descThreshold) flagged.push({ a, b, tagSim, descSim });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return flagged.sort((x, y) => y.tagSim + y.descSim - (x.tagSim + x.descSim));
|
|
29
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Turns a crondex `command` field (with {{placeholder}} templating) into a script
|
|
2
|
+
// shellcheck can actually parse — pulled out of scripts/lint-shell.js so the
|
|
3
|
+
// substitution itself is unit testable (see test/shellcheck-prep.test.js).
|
|
4
|
+
//
|
|
5
|
+
// crondex substitutes {{placeholders}} as literal text before bash ever sees the
|
|
6
|
+
// script, so each placeholder is stood in as a real (braced) bash variable — braced
|
|
7
|
+
// so a literal suffix right after it (e.g. "{{days}}d") doesn't get glued into the
|
|
8
|
+
// variable name — just to let shellcheck catch actual quoting/syntax bugs.
|
|
9
|
+
const PLACEHOLDER_RE = /\{\{\s*(\w+)\s*\}\}/g;
|
|
10
|
+
|
|
11
|
+
export function extractPlaceholders(command) {
|
|
12
|
+
return [...new Set([...command.matchAll(PLACEHOLDER_RE)].map((m) => m[1]))];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function buildShellcheckScript(command) {
|
|
16
|
+
const placeholders = extractPlaceholders(command);
|
|
17
|
+
const header = placeholders.map((name) => `${name}="42"`).join("\n");
|
|
18
|
+
const body = command.replace(PLACEHOLDER_RE, "$${$1}");
|
|
19
|
+
return `#!/usr/bin/env bash\n${header}\n${body}`;
|
|
20
|
+
}
|