@hasna/skills 0.1.19 → 0.1.21
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 +136 -18
- package/bin/index.js +33877 -33924
- package/bin/mcp.js +196 -95
- package/dist/cli/commands/completion.d.ts +5 -0
- package/dist/cli/commands/create-sync-config.d.ts +5 -0
- package/dist/cli/commands/diagnostic.d.ts +5 -0
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/install.d.ts +5 -0
- package/dist/cli/commands/introspect.d.ts +5 -0
- package/dist/cli/commands/list.d.ts +5 -0
- package/dist/cli/commands/runtime.d.ts +5 -0
- package/dist/cli/commands/schedule.d.ts +5 -0
- package/dist/index.js +197 -97
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/registry.d.ts +1 -11
- package/dist/lib/scheduler.d.ts +1 -1
- package/dist/lib/scheduler.test.d.ts +4 -0
- package/dist/lib/search.d.ts +17 -0
- package/package.json +1 -1
- package/skills/skill-commitpush/SKILL.md +57 -0
- package/skills/skill-commitpush/package.json +34 -0
- package/skills/skill-commitpush/src/index.ts +34 -0
- package/skills/skill-commitpush/tsconfig.json +17 -0
- package/skills/skill-commitpushpr/SKILL.md +55 -0
- package/skills/skill-commitpushpr/package.json +34 -0
- package/skills/skill-commitpushpr/src/index.ts +34 -0
- package/skills/skill-commitpushpr/tsconfig.json +17 -0
- package/skills/skill-monitor/SKILL.md +69 -0
- package/skills/skill-monitor/package.json +34 -0
- package/skills/skill-monitor/src/index.ts +34 -0
- package/skills/skill-monitor/tsconfig.json +17 -0
- package/skills/skill-read-csv/SKILL.md +62 -0
- package/skills/skill-read-csv/package.json +38 -0
- package/skills/skill-read-csv/src/index.ts +331 -0
- package/skills/skill-read-csv/tsconfig.json +17 -0
- package/skills/skill-read-excel/SKILL.md +64 -0
- package/skills/skill-read-excel/package.json +37 -0
- package/skills/skill-read-excel/src/index.ts +253 -0
- package/skills/skill-read-excel/tsconfig.json +17 -0
- package/skills/skill-read-image/SKILL.md +47 -0
- package/skills/skill-read-image/package.json +34 -0
- package/skills/skill-read-image/src/index.ts +264 -0
- package/skills/skill-read-image/tsconfig.json +17 -0
- package/skills/skill-read-pdf/SKILL.md +52 -0
- package/skills/skill-read-pdf/package.json +37 -0
- package/skills/skill-read-pdf/src/index.ts +376 -0
- package/skills/skill-read-pdf/tsconfig.json +17 -0
- package/skills/skill-tmux-session/SKILL.md +109 -0
- package/skills/skill-tmux-session/package.json +34 -0
- package/skills/skill-tmux-session/src/index.ts +34 -0
- package/skills/skill-tmux-session/tsconfig.json +17 -0
package/bin/mcp.js
CHANGED
|
@@ -38746,7 +38746,7 @@ init_adapter();
|
|
|
38746
38746
|
// package.json
|
|
38747
38747
|
var package_default = {
|
|
38748
38748
|
name: "@hasna/skills",
|
|
38749
|
-
version: "0.1.
|
|
38749
|
+
version: "0.1.21",
|
|
38750
38750
|
description: "Skills library for AI coding agents",
|
|
38751
38751
|
type: "module",
|
|
38752
38752
|
bin: {
|
|
@@ -38835,6 +38835,94 @@ var package_default = {
|
|
|
38835
38835
|
import { existsSync as existsSync4, readFileSync as readFileSync2, readdirSync as readdirSync3 } from "fs";
|
|
38836
38836
|
import { join as join5 } from "path";
|
|
38837
38837
|
import { homedir as homedir6 } from "os";
|
|
38838
|
+
|
|
38839
|
+
// src/lib/search.ts
|
|
38840
|
+
function editDistance(a, b) {
|
|
38841
|
+
if (a === b)
|
|
38842
|
+
return 0;
|
|
38843
|
+
if (a.length === 0)
|
|
38844
|
+
return b.length;
|
|
38845
|
+
if (b.length === 0)
|
|
38846
|
+
return a.length;
|
|
38847
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
38848
|
+
const curr = new Array(b.length + 1);
|
|
38849
|
+
for (let i = 1;i <= a.length; i++) {
|
|
38850
|
+
curr[0] = i;
|
|
38851
|
+
for (let j = 1;j <= b.length; j++) {
|
|
38852
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
38853
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
38854
|
+
}
|
|
38855
|
+
prev.splice(0, prev.length, ...curr);
|
|
38856
|
+
}
|
|
38857
|
+
return prev[b.length];
|
|
38858
|
+
}
|
|
38859
|
+
function fuzzyMatchScore(word, target) {
|
|
38860
|
+
if (target.includes(word))
|
|
38861
|
+
return 1;
|
|
38862
|
+
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
38863
|
+
for (const token of tokens) {
|
|
38864
|
+
if (token.startsWith(word))
|
|
38865
|
+
return 0.6;
|
|
38866
|
+
}
|
|
38867
|
+
if (word.length >= 3) {
|
|
38868
|
+
const maxDist = word.length <= 3 ? 1 : 2;
|
|
38869
|
+
for (const token of tokens) {
|
|
38870
|
+
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
38871
|
+
const dist = editDistance(word, token);
|
|
38872
|
+
if (dist <= maxDist)
|
|
38873
|
+
return 0.4;
|
|
38874
|
+
}
|
|
38875
|
+
}
|
|
38876
|
+
}
|
|
38877
|
+
return 0;
|
|
38878
|
+
}
|
|
38879
|
+
function searchSkills(query) {
|
|
38880
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
38881
|
+
if (words.length === 0)
|
|
38882
|
+
return [];
|
|
38883
|
+
const scored = [];
|
|
38884
|
+
for (const skill of loadRegistry()) {
|
|
38885
|
+
const nameLower = skill.name.toLowerCase();
|
|
38886
|
+
const displayNameLower = skill.displayName.toLowerCase();
|
|
38887
|
+
const descriptionLower = skill.description.toLowerCase();
|
|
38888
|
+
const tagsLower = skill.tags.map((t) => t.toLowerCase());
|
|
38889
|
+
const tagsCombined = tagsLower.join(" ");
|
|
38890
|
+
let score = 0;
|
|
38891
|
+
let allWordsMatch = true;
|
|
38892
|
+
for (const word of words) {
|
|
38893
|
+
let wordScore = 0;
|
|
38894
|
+
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
38895
|
+
if (nameMatch > 0)
|
|
38896
|
+
wordScore += 10 * nameMatch;
|
|
38897
|
+
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
38898
|
+
if (displayMatch > 0)
|
|
38899
|
+
wordScore += 7 * displayMatch;
|
|
38900
|
+
const tagMatch = Math.max(...tagsLower.map((t) => fuzzyMatchScore(word, t)), fuzzyMatchScore(word, tagsCombined));
|
|
38901
|
+
if (tagMatch > 0)
|
|
38902
|
+
wordScore += 5 * tagMatch;
|
|
38903
|
+
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
38904
|
+
if (descMatch > 0)
|
|
38905
|
+
wordScore += 2 * descMatch;
|
|
38906
|
+
if (wordScore === 0) {
|
|
38907
|
+
allWordsMatch = false;
|
|
38908
|
+
break;
|
|
38909
|
+
}
|
|
38910
|
+
score += wordScore;
|
|
38911
|
+
}
|
|
38912
|
+
if (allWordsMatch && score > 0) {
|
|
38913
|
+
scored.push({ skill, score });
|
|
38914
|
+
}
|
|
38915
|
+
}
|
|
38916
|
+
scored.sort((a, b) => b.score - a.score);
|
|
38917
|
+
return scored.map((s) => s.skill);
|
|
38918
|
+
}
|
|
38919
|
+
function findSimilarSkills(query, maxResults = 3) {
|
|
38920
|
+
const q = query.toLowerCase();
|
|
38921
|
+
const scored = loadRegistry().map((s) => ({ name: s.name, dist: editDistance(q, s.name.toLowerCase()) })).filter((s) => s.dist <= Math.max(3, Math.floor(q.length / 2))).sort((a, b) => a.dist - b.dist);
|
|
38922
|
+
return scored.slice(0, maxResults).map((s) => s.name);
|
|
38923
|
+
}
|
|
38924
|
+
|
|
38925
|
+
// src/lib/registry.ts
|
|
38838
38926
|
var CATEGORIES = [
|
|
38839
38927
|
"Development Tools",
|
|
38840
38928
|
"Business & Marketing",
|
|
@@ -38876,6 +38964,21 @@ var SKILLS = [
|
|
|
38876
38964
|
category: "Development Tools",
|
|
38877
38965
|
tags: ["code", "linting", "formatting", "quality"]
|
|
38878
38966
|
},
|
|
38967
|
+
{
|
|
38968
|
+
name: "commitpush",
|
|
38969
|
+
displayName: "Commit Push",
|
|
38970
|
+
description: "Create logical commits from repo changes and push directly to the main branch",
|
|
38971
|
+
category: "Development Tools",
|
|
38972
|
+
tags: ["git", "commit", "push", "automation"]
|
|
38973
|
+
},
|
|
38974
|
+
{
|
|
38975
|
+
name: "commitpushpr",
|
|
38976
|
+
displayName: "Commit Push PR",
|
|
38977
|
+
description: "Create logical commits, push a feature branch, and open a GitHub pull request",
|
|
38978
|
+
category: "Development Tools",
|
|
38979
|
+
tags: ["git", "commit", "pull-request", "github", "automation"],
|
|
38980
|
+
dependencies: ["commitpush"]
|
|
38981
|
+
},
|
|
38879
38982
|
{
|
|
38880
38983
|
name: "consolelog",
|
|
38881
38984
|
displayName: "Console Log",
|
|
@@ -39037,6 +39140,13 @@ var SKILLS = [
|
|
|
39037
39140
|
category: "Development Tools",
|
|
39038
39141
|
tags: ["mcp", "builder", "scaffold", "server"]
|
|
39039
39142
|
},
|
|
39143
|
+
{
|
|
39144
|
+
name: "monitor",
|
|
39145
|
+
displayName: "Monitor",
|
|
39146
|
+
description: "Operate the open-monitor MCP for machine health, processes, cron jobs, and cleanup workflows",
|
|
39147
|
+
category: "Development Tools",
|
|
39148
|
+
tags: ["monitoring", "mcp", "processes", "operations"]
|
|
39149
|
+
},
|
|
39040
39150
|
{
|
|
39041
39151
|
name: "npmpublish",
|
|
39042
39152
|
displayName: "NPM Publish",
|
|
@@ -39087,6 +39197,13 @@ var SKILLS = [
|
|
|
39087
39197
|
category: "Development Tools",
|
|
39088
39198
|
tags: ["terraform", "iac", "infrastructure", "devops"]
|
|
39089
39199
|
},
|
|
39200
|
+
{
|
|
39201
|
+
name: "tmux-session",
|
|
39202
|
+
displayName: "Tmux Session",
|
|
39203
|
+
description: "Create and manage grouped tmux sessions with workspace-aware naming and window layout guidance",
|
|
39204
|
+
category: "Development Tools",
|
|
39205
|
+
tags: ["tmux", "terminal", "sessions", "workspace"]
|
|
39206
|
+
},
|
|
39090
39207
|
{
|
|
39091
39208
|
name: "validate-config",
|
|
39092
39209
|
displayName: "Validate Config",
|
|
@@ -39689,6 +39806,34 @@ var SKILLS = [
|
|
|
39689
39806
|
category: "Data & Analysis",
|
|
39690
39807
|
tags: ["kpi", "digest", "metrics", "reporting"]
|
|
39691
39808
|
},
|
|
39809
|
+
{
|
|
39810
|
+
name: "read-csv",
|
|
39811
|
+
displayName: "Read CSV",
|
|
39812
|
+
description: "Parse CSV files into structured JSON with delimiter and encoding detection",
|
|
39813
|
+
category: "Data & Analysis",
|
|
39814
|
+
tags: ["csv", "parsing", "tabular", "data"]
|
|
39815
|
+
},
|
|
39816
|
+
{
|
|
39817
|
+
name: "read-excel",
|
|
39818
|
+
displayName: "Read Excel",
|
|
39819
|
+
description: "Parse XLS and XLSX workbooks into structured JSON with sheet and formatted cell metadata",
|
|
39820
|
+
category: "Data & Analysis",
|
|
39821
|
+
tags: ["excel", "spreadsheet", "xlsx", "data"]
|
|
39822
|
+
},
|
|
39823
|
+
{
|
|
39824
|
+
name: "read-image",
|
|
39825
|
+
displayName: "Read Image",
|
|
39826
|
+
description: "Analyze local or remote images with Claude vision and extract visible text and visual structure",
|
|
39827
|
+
category: "Data & Analysis",
|
|
39828
|
+
tags: ["image", "vision", "ocr", "analysis"]
|
|
39829
|
+
},
|
|
39830
|
+
{
|
|
39831
|
+
name: "read-pdf",
|
|
39832
|
+
displayName: "Read PDF",
|
|
39833
|
+
description: "Extract text and structured content from PDF files with chunked Claude document analysis",
|
|
39834
|
+
category: "Data & Analysis",
|
|
39835
|
+
tags: ["pdf", "documents", "extraction", "analysis"]
|
|
39836
|
+
},
|
|
39692
39837
|
{
|
|
39693
39838
|
name: "spreadsheet-cleanroom",
|
|
39694
39839
|
displayName: "Spreadsheet Cleanroom",
|
|
@@ -40370,103 +40515,9 @@ function loadRegistry(cwd) {
|
|
|
40370
40515
|
function getSkillsByCategory(category) {
|
|
40371
40516
|
return loadRegistry().filter((s) => s.category === category);
|
|
40372
40517
|
}
|
|
40373
|
-
function editDistance(a, b) {
|
|
40374
|
-
if (a === b)
|
|
40375
|
-
return 0;
|
|
40376
|
-
if (a.length === 0)
|
|
40377
|
-
return b.length;
|
|
40378
|
-
if (b.length === 0)
|
|
40379
|
-
return a.length;
|
|
40380
|
-
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
40381
|
-
const curr = new Array(b.length + 1);
|
|
40382
|
-
for (let i = 1;i <= a.length; i++) {
|
|
40383
|
-
curr[0] = i;
|
|
40384
|
-
for (let j = 1;j <= b.length; j++) {
|
|
40385
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
40386
|
-
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
40387
|
-
}
|
|
40388
|
-
prev.splice(0, prev.length, ...curr);
|
|
40389
|
-
}
|
|
40390
|
-
return prev[b.length];
|
|
40391
|
-
}
|
|
40392
|
-
function fuzzyMatchScore(word, target) {
|
|
40393
|
-
if (target.includes(word))
|
|
40394
|
-
return 1;
|
|
40395
|
-
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
40396
|
-
for (const token of tokens) {
|
|
40397
|
-
if (token.startsWith(word))
|
|
40398
|
-
return 0.6;
|
|
40399
|
-
}
|
|
40400
|
-
if (word.length >= 3) {
|
|
40401
|
-
const maxDist = word.length <= 3 ? 1 : 2;
|
|
40402
|
-
for (const token of tokens) {
|
|
40403
|
-
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
40404
|
-
const dist = editDistance(word, token);
|
|
40405
|
-
if (dist <= maxDist)
|
|
40406
|
-
return 0.4;
|
|
40407
|
-
}
|
|
40408
|
-
}
|
|
40409
|
-
}
|
|
40410
|
-
return 0;
|
|
40411
|
-
}
|
|
40412
|
-
function searchSkills(query) {
|
|
40413
|
-
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
40414
|
-
if (words.length === 0)
|
|
40415
|
-
return [];
|
|
40416
|
-
const scored = [];
|
|
40417
|
-
for (const skill of loadRegistry()) {
|
|
40418
|
-
const nameLower = skill.name.toLowerCase();
|
|
40419
|
-
const displayNameLower = skill.displayName.toLowerCase();
|
|
40420
|
-
const descriptionLower = skill.description.toLowerCase();
|
|
40421
|
-
const tagsLower = skill.tags.map((t) => t.toLowerCase());
|
|
40422
|
-
const tagsCombined = tagsLower.join(" ");
|
|
40423
|
-
let score = 0;
|
|
40424
|
-
let allWordsMatch = true;
|
|
40425
|
-
for (const word of words) {
|
|
40426
|
-
let wordScore = 0;
|
|
40427
|
-
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
40428
|
-
if (nameMatch > 0)
|
|
40429
|
-
wordScore += 10 * nameMatch;
|
|
40430
|
-
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
40431
|
-
if (displayMatch > 0)
|
|
40432
|
-
wordScore += 7 * displayMatch;
|
|
40433
|
-
const tagMatch = Math.max(...tagsLower.map((t) => fuzzyMatchScore(word, t)), fuzzyMatchScore(word, tagsCombined));
|
|
40434
|
-
if (tagMatch > 0)
|
|
40435
|
-
wordScore += 5 * tagMatch;
|
|
40436
|
-
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
40437
|
-
if (descMatch > 0)
|
|
40438
|
-
wordScore += 2 * descMatch;
|
|
40439
|
-
if (wordScore === 0) {
|
|
40440
|
-
allWordsMatch = false;
|
|
40441
|
-
break;
|
|
40442
|
-
}
|
|
40443
|
-
score += wordScore;
|
|
40444
|
-
}
|
|
40445
|
-
if (allWordsMatch && score > 0) {
|
|
40446
|
-
scored.push({ skill, score });
|
|
40447
|
-
}
|
|
40448
|
-
}
|
|
40449
|
-
scored.sort((a, b) => b.score - a.score);
|
|
40450
|
-
return scored.map((s) => s.skill);
|
|
40451
|
-
}
|
|
40452
40518
|
function getSkill(name) {
|
|
40453
40519
|
return loadRegistry().find((s) => s.name === name);
|
|
40454
40520
|
}
|
|
40455
|
-
function levenshtein(a, b) {
|
|
40456
|
-
const m = a.length, n = b.length;
|
|
40457
|
-
const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0));
|
|
40458
|
-
for (let i = 1;i <= m; i++) {
|
|
40459
|
-
for (let j = 1;j <= n; j++) {
|
|
40460
|
-
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
40461
|
-
}
|
|
40462
|
-
}
|
|
40463
|
-
return dp[m][n];
|
|
40464
|
-
}
|
|
40465
|
-
function findSimilarSkills(query, maxResults = 3) {
|
|
40466
|
-
const q = query.toLowerCase();
|
|
40467
|
-
const scored = loadRegistry().map((s) => ({ name: s.name, dist: levenshtein(q, s.name.toLowerCase()) })).filter((s) => s.dist <= Math.max(3, Math.floor(q.length / 2))).sort((a, b) => a.dist - b.dist);
|
|
40468
|
-
return scored.slice(0, maxResults).map((s) => s.name);
|
|
40469
|
-
}
|
|
40470
40521
|
|
|
40471
40522
|
// src/lib/installer.ts
|
|
40472
40523
|
import { existsSync as existsSync6, cpSync, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, rmSync, readdirSync as readdirSync5, statSync, readFileSync as readFileSync3, accessSync, constants } from "fs";
|
|
@@ -41063,11 +41114,61 @@ function saveSchedules(data, targetDir = process.cwd()) {
|
|
|
41063
41114
|
mkdirSync5(dir, { recursive: true });
|
|
41064
41115
|
writeFileSync3(path, JSON.stringify(data, null, 2));
|
|
41065
41116
|
}
|
|
41117
|
+
function validateCronField(expr, min, max, label) {
|
|
41118
|
+
for (const part of expr.split(",")) {
|
|
41119
|
+
if (part === "*")
|
|
41120
|
+
continue;
|
|
41121
|
+
let valuePart = part;
|
|
41122
|
+
if (part.includes("/")) {
|
|
41123
|
+
const slashIdx = part.indexOf("/");
|
|
41124
|
+
valuePart = part.slice(0, slashIdx);
|
|
41125
|
+
const stepStr = part.slice(slashIdx + 1);
|
|
41126
|
+
const step = parseInt(stepStr);
|
|
41127
|
+
if (isNaN(step) || step < 1)
|
|
41128
|
+
return { valid: false, error: `Invalid step value in "${part}" in ${label}` };
|
|
41129
|
+
}
|
|
41130
|
+
if (valuePart === "*")
|
|
41131
|
+
continue;
|
|
41132
|
+
if (valuePart.includes("-")) {
|
|
41133
|
+
const rangeParts = valuePart.split("-");
|
|
41134
|
+
if (rangeParts.length !== 2)
|
|
41135
|
+
return { valid: false, error: `Invalid range expression "${valuePart}" in ${label}` };
|
|
41136
|
+
const lo = parseInt(rangeParts[0]);
|
|
41137
|
+
const hi = parseInt(rangeParts[1]);
|
|
41138
|
+
if (isNaN(lo) || isNaN(hi))
|
|
41139
|
+
return { valid: false, error: `Invalid range "${valuePart}" in ${label}` };
|
|
41140
|
+
if (lo < min || hi > max || lo > hi) {
|
|
41141
|
+
return { valid: false, error: `Range ${lo}-${hi} outside valid ${min}-${max} in ${label}` };
|
|
41142
|
+
}
|
|
41143
|
+
continue;
|
|
41144
|
+
}
|
|
41145
|
+
const n = parseInt(valuePart);
|
|
41146
|
+
if (isNaN(n))
|
|
41147
|
+
return { valid: false, error: `Invalid value "${valuePart}" in ${label}` };
|
|
41148
|
+
if (n < min || n > max) {
|
|
41149
|
+
return { valid: false, error: `Value ${n} outside valid ${min}-${max} in ${label}` };
|
|
41150
|
+
}
|
|
41151
|
+
}
|
|
41152
|
+
return { valid: true };
|
|
41153
|
+
}
|
|
41066
41154
|
function validateCron(expr) {
|
|
41067
41155
|
const fields = expr.trim().split(/\s+/);
|
|
41068
41156
|
if (fields.length !== 5) {
|
|
41069
41157
|
return { valid: false, error: `Expected 5 fields, got ${fields.length}. Format: "minute hour day-of-month month day-of-week"` };
|
|
41070
41158
|
}
|
|
41159
|
+
const [minuteF, hourF, domF, monthF, dowF] = fields;
|
|
41160
|
+
const checks4 = [
|
|
41161
|
+
{ expr: minuteF, min: 0, max: 59, label: "minute" },
|
|
41162
|
+
{ expr: hourF, min: 0, max: 23, label: "hour" },
|
|
41163
|
+
{ expr: domF, min: 1, max: 31, label: "day-of-month" },
|
|
41164
|
+
{ expr: monthF, min: 1, max: 12, label: "month" },
|
|
41165
|
+
{ expr: dowF, min: 0, max: 6, label: "day-of-week" }
|
|
41166
|
+
];
|
|
41167
|
+
for (const { expr: f, min, max, label } of checks4) {
|
|
41168
|
+
const result = validateCronField(f, min, max, label);
|
|
41169
|
+
if (!result.valid)
|
|
41170
|
+
return result;
|
|
41171
|
+
}
|
|
41071
41172
|
return { valid: true };
|
|
41072
41173
|
}
|
|
41073
41174
|
function getNextRun(cron, from = new Date) {
|