@skillmarkdown/cli 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SkillMarkdown
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @skillmarkdown/cli
2
+
3
+ `@skillmarkdown/cli` is the official CLI for scaffolding, validating, and packaging `SKILL.md`-based AI skills. It provides the `skillmd` command, starting with deterministic, spec-aligned skill scaffolding via `skillmd init`.
4
+
5
+ ## Status
6
+
7
+ Early development. v0 focuses on `skillmd init` and `skillmd validate`. Docs are intentionally lightweight and may evolve.
8
+
9
+ ## Install
10
+
11
+ ### Global install
12
+
13
+ ```bash
14
+ npm i -g @skillmarkdown/cli
15
+ ```
16
+
17
+ ### Run without installing
18
+
19
+ ```bash
20
+ npx @skillmarkdown/cli init
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### Initialize a skill folder
26
+
27
+ ```bash
28
+ skillmd init
29
+ ```
30
+
31
+ This scaffolds a spec-aligned skill structure including `SKILL.md` and optional directories (`scripts/`, `references/`, `assets/`), then runs local validation.
32
+
33
+ To skip validation during init:
34
+
35
+ ```bash
36
+ skillmd init --no-validate
37
+ ```
38
+
39
+ ### Validate a skill folder
40
+
41
+ ```bash
42
+ skillmd validate
43
+ ```
44
+
45
+ You can also pass an explicit path:
46
+
47
+ ```bash
48
+ skillmd validate /path/to/skill
49
+ ```
50
+
51
+ Run additional scaffold/template checks:
52
+
53
+ ```bash
54
+ skillmd validate --strict
55
+ ```
56
+
57
+ Compare local validation with `skills-ref` (when installed):
58
+
59
+ ```bash
60
+ skillmd validate --parity
61
+ ```
62
+
63
+ ## Development
64
+
65
+ - Local testing guide: `docs/testing.md`
66
+ - CI check script: `npm run ci:check`
67
+ - Packed tarball smoke test: `npm run smoke:pack`
68
+ - Optional npm link smoke test: `npm run smoke:link`
69
+
70
+ ## Links
71
+
72
+ - Agent Skills spec: https://agentskills.io/specification
73
+
74
+ ## License
75
+
76
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const init_1 = require("./commands/init");
5
+ const validate_1 = require("./commands/validate");
6
+ function main() {
7
+ const args = process.argv.slice(2);
8
+ if (args.length === 0) {
9
+ console.error("skillmd: no command provided");
10
+ console.error("Usage: skillmd <init|validate>");
11
+ process.exitCode = 1;
12
+ return;
13
+ }
14
+ if (args[0] === "init") {
15
+ process.exitCode = (0, init_1.runInitCommand)(args.slice(1));
16
+ return;
17
+ }
18
+ if (args[0] === "validate") {
19
+ process.exitCode = (0, validate_1.runValidateCommand)(args.slice(1));
20
+ return;
21
+ }
22
+ console.error(`skillmd: unknown command '${args[0]}'`);
23
+ console.error("Usage: skillmd <init|validate>");
24
+ process.exitCode = 1;
25
+ }
26
+ main();
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runInitCommand = runInitCommand;
4
+ const scaffold_1 = require("../lib/scaffold");
5
+ const validator_1 = require("../lib/validator");
6
+ function runInitCommand(args, options = {}) {
7
+ const cwd = options.cwd ?? process.cwd();
8
+ const validateSkillFn = options.validateSkill ?? ((targetDir) => (0, validator_1.validateSkill)(targetDir, { strict: true }));
9
+ const skipValidation = args.includes("--no-validate");
10
+ const hasUnsupportedArgs = args.length > 1 || (args.length === 1 && args[0] !== "--no-validate");
11
+ if (hasUnsupportedArgs) {
12
+ console.error("skillmd init: unsupported argument(s)");
13
+ console.error("Usage: skillmd init [--no-validate]");
14
+ return 1;
15
+ }
16
+ try {
17
+ const result = (0, scaffold_1.scaffoldSkillInDirectory)(cwd);
18
+ console.log(`Initialized skill '${result.skillName}' in ${cwd}`);
19
+ if (skipValidation) {
20
+ console.log("Validation skipped (--no-validate).");
21
+ return 0;
22
+ }
23
+ const validation = validateSkillFn(cwd);
24
+ if (validation.status === "passed") {
25
+ console.log(`Validation passed: ${validation.message}`);
26
+ return 0;
27
+ }
28
+ console.error(`Validation failed: ${validation.message}`);
29
+ console.error("Run 'skillmd validate' after fixing issues.");
30
+ return 1;
31
+ }
32
+ catch (error) {
33
+ const message = error instanceof Error ? error.message : "Unknown error";
34
+ console.error(`skillmd init: ${message}`);
35
+ return 1;
36
+ }
37
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runValidateCommand = runValidateCommand;
4
+ const node_path_1 = require("node:path");
5
+ const validator_1 = require("../lib/validator");
6
+ const upstream_validator_1 = require("../lib/upstream-validator");
7
+ function runValidateCommand(args, options = {}) {
8
+ const cwd = options.cwd ?? process.cwd();
9
+ const validateLocal = options.validateLocal ?? validator_1.validateSkill;
10
+ const validateUpstream = options.validateUpstream ?? upstream_validator_1.validateWithSkillsRef;
11
+ let strict = false;
12
+ let parity = false;
13
+ let pathArg;
14
+ for (const arg of args) {
15
+ if (arg === "--strict") {
16
+ strict = true;
17
+ continue;
18
+ }
19
+ if (arg === "--parity") {
20
+ parity = true;
21
+ continue;
22
+ }
23
+ if (arg.startsWith("-")) {
24
+ console.error(`skillmd validate: unsupported flag '${arg}'`);
25
+ console.error("Usage: skillmd validate [path] [--strict] [--parity]");
26
+ return 1;
27
+ }
28
+ if (pathArg) {
29
+ console.error("skillmd validate: accepts at most one path argument");
30
+ console.error("Usage: skillmd validate [path] [--strict] [--parity]");
31
+ return 1;
32
+ }
33
+ pathArg = arg;
34
+ }
35
+ const targetDir = pathArg ? (0, node_path_1.resolve)(cwd, pathArg) : cwd;
36
+ const validation = validateLocal(targetDir, { strict });
37
+ if (parity) {
38
+ const upstream = validateUpstream(targetDir);
39
+ if (upstream.status === "unavailable") {
40
+ console.error(`Validation parity unavailable: ${upstream.message}. Install skills-ref to use --parity.`);
41
+ return 1;
42
+ }
43
+ if (validation.status === "passed" && upstream.status !== "passed") {
44
+ console.error("Validation parity mismatch: local validation passed but skills-ref failed.");
45
+ console.error(`skills-ref: ${upstream.message}`);
46
+ return 1;
47
+ }
48
+ if (validation.status === "failed" && upstream.status !== "failed") {
49
+ console.error("Validation parity mismatch: local validation failed but skills-ref passed.");
50
+ return 1;
51
+ }
52
+ }
53
+ if (validation.status === "passed") {
54
+ console.log(`Validation passed: ${validation.message}`);
55
+ if (parity) {
56
+ console.log("Validation parity passed (skills-ref).");
57
+ }
58
+ return 0;
59
+ }
60
+ console.error(`Validation failed: ${validation.message}`);
61
+ if (parity) {
62
+ console.error("Validation parity matched (skills-ref also failed).");
63
+ }
64
+ return 1;
65
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeSkillName = normalizeSkillName;
4
+ exports.getMaxSkillNameLength = getMaxSkillNameLength;
5
+ const MAX_SKILL_NAME_LENGTH = 64;
6
+ function normalizeSkillName(input) {
7
+ const normalized = input
8
+ .trim()
9
+ .toLowerCase()
10
+ .replace(/[^a-z0-9]+/g, "-")
11
+ .replace(/-+/g, "-")
12
+ .replace(/^-|-$/g, "");
13
+ if (normalized.length === 0) {
14
+ throw new Error("skill name is empty after normalization; use letters/numbers and optional hyphens");
15
+ }
16
+ if (normalized.length > MAX_SKILL_NAME_LENGTH) {
17
+ throw new Error(`skill name must be at most ${MAX_SKILL_NAME_LENGTH} characters`);
18
+ }
19
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(normalized)) {
20
+ throw new Error("skill name must use lowercase letters, numbers, and single hyphens only");
21
+ }
22
+ return normalized;
23
+ }
24
+ function getMaxSkillNameLength() {
25
+ return MAX_SKILL_NAME_LENGTH;
26
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scaffoldSkillInDirectory = scaffoldSkillInDirectory;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const normalize_name_1 = require("./normalize-name");
7
+ const templates_1 = require("./templates");
8
+ function assertDirectoryEmpty(targetDir) {
9
+ const entries = (0, node_fs_1.readdirSync)(targetDir);
10
+ if (entries.length > 0) {
11
+ throw new Error(`target directory is not empty (${entries.length} item(s) found); run 'skillmd init' in an empty directory`);
12
+ }
13
+ }
14
+ function assertDirectoryNameMatchesNormalized(targetDir) {
15
+ const dirName = (0, node_path_1.basename)(targetDir);
16
+ const normalizedName = (0, normalize_name_1.normalizeSkillName)(dirName);
17
+ if (dirName !== normalizedName) {
18
+ throw new Error(`directory name '${dirName}' must already be normalized. Rename it to '${normalizedName}' and retry`);
19
+ }
20
+ return normalizedName;
21
+ }
22
+ function scaffoldSkillInDirectory(targetDir) {
23
+ const skillName = assertDirectoryNameMatchesNormalized(targetDir);
24
+ assertDirectoryEmpty(targetDir);
25
+ const directories = ["scripts", "references", "assets"];
26
+ for (const directory of directories) {
27
+ const fullPath = (0, node_path_1.join)(targetDir, directory);
28
+ (0, node_fs_1.mkdirSync)(fullPath, { recursive: true });
29
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(fullPath, ".gitkeep"), "", "utf8");
30
+ }
31
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(targetDir, "SKILL.md"), (0, templates_1.buildSkillMarkdown)(skillName), "utf8");
32
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(targetDir, ".gitignore"), (0, templates_1.buildGitignore)(), "utf8");
33
+ return { skillName };
34
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildSkillMarkdown = buildSkillMarkdown;
4
+ exports.buildGitignore = buildGitignore;
5
+ function buildSkillMarkdown(name) {
6
+ return `---
7
+ name: ${name}
8
+ description: "TODO: Describe what this skill does and when to use it."
9
+ license: TODO
10
+ ---
11
+
12
+ ## Scope
13
+ TODO
14
+
15
+ ## When to use
16
+ TODO
17
+
18
+ ## Inputs
19
+ TODO
20
+
21
+ ## Outputs
22
+ TODO
23
+
24
+ ## Steps / Procedure
25
+ TODO
26
+
27
+ ## Examples
28
+ TODO
29
+
30
+ ## Limitations / Failure modes
31
+ TODO
32
+
33
+ ## Security / Tool access
34
+ TODO
35
+ `;
36
+ }
37
+ function buildGitignore() {
38
+ return `node_modules/
39
+ dist/
40
+ .DS_Store
41
+ npm-debug.log*
42
+ `;
43
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateWithSkillsRef = validateWithSkillsRef;
4
+ const node_child_process_1 = require("node:child_process");
5
+ function formatOutput(stdout, stderr) {
6
+ return [stdout.trim(), stderr.trim()].filter((part) => part.length > 0).join("\n");
7
+ }
8
+ function validateWithSkillsRef(targetDir) {
9
+ const result = (0, node_child_process_1.spawnSync)("skills-ref", ["validate", targetDir], {
10
+ encoding: "utf8",
11
+ });
12
+ if (result.error) {
13
+ if ("code" in result.error && result.error.code === "ENOENT") {
14
+ return {
15
+ status: "unavailable",
16
+ message: "skills-ref is not installed or not on PATH",
17
+ };
18
+ }
19
+ return {
20
+ status: "unavailable",
21
+ message: `skills-ref execution failed: ${result.error.message}`,
22
+ };
23
+ }
24
+ const output = formatOutput(result.stdout ?? "", result.stderr ?? "");
25
+ if (result.status === 0) {
26
+ return {
27
+ status: "passed",
28
+ message: output || "skills-ref validation passed",
29
+ };
30
+ }
31
+ return {
32
+ status: "failed",
33
+ message: output || "skills-ref validation failed",
34
+ };
35
+ }
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateSkill = validateSkill;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const yaml_1 = require("yaml");
7
+ const STRICT_REQUIRED_FILES = [
8
+ ".gitignore",
9
+ "scripts/.gitkeep",
10
+ "references/.gitkeep",
11
+ "assets/.gitkeep",
12
+ ];
13
+ const REQUIRED_SECTIONS = [
14
+ "## Scope",
15
+ "## When to use",
16
+ "## Inputs",
17
+ "## Outputs",
18
+ "## Steps / Procedure",
19
+ "## Examples",
20
+ "## Limitations / Failure modes",
21
+ "## Security / Tool access",
22
+ ];
23
+ function stripUtf8Bom(content) {
24
+ return content.replace(/^\uFEFF/, "");
25
+ }
26
+ function extractFrontmatter(content) {
27
+ const normalizedContent = stripUtf8Bom(content);
28
+ const match = normalizedContent.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
29
+ if (!match || typeof match[1] !== "string") {
30
+ return null;
31
+ }
32
+ let parsed;
33
+ try {
34
+ parsed = (0, yaml_1.parse)(match[1]);
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
40
+ return null;
41
+ }
42
+ return {
43
+ frontmatter: parsed,
44
+ };
45
+ }
46
+ function isValidSkillName(name) {
47
+ if (name.length === 0 || name.length > 64) {
48
+ return false;
49
+ }
50
+ return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name);
51
+ }
52
+ function isStringMap(value) {
53
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
54
+ return false;
55
+ }
56
+ return Object.values(value).every((entry) => typeof entry === "string");
57
+ }
58
+ function collectSpecErrors(targetDir) {
59
+ const errors = [];
60
+ const skillPath = (0, node_path_1.join)(targetDir, "SKILL.md");
61
+ if (!(0, node_fs_1.existsSync)(skillPath)) {
62
+ errors.push("missing required file: SKILL.md");
63
+ return errors;
64
+ }
65
+ const skillContent = (0, node_fs_1.readFileSync)(skillPath, "utf8");
66
+ const parsedSkill = extractFrontmatter(skillContent);
67
+ if (!parsedSkill) {
68
+ errors.push("SKILL.md must start with valid YAML frontmatter");
69
+ return errors;
70
+ }
71
+ const { frontmatter } = parsedSkill;
72
+ const name = frontmatter.name;
73
+ const description = frontmatter.description;
74
+ const license = frontmatter.license;
75
+ const compatibility = frontmatter.compatibility;
76
+ const metadata = frontmatter.metadata;
77
+ const allowedTools = frontmatter["allowed-tools"];
78
+ const directoryName = (0, node_path_1.basename)(targetDir);
79
+ if (typeof name !== "string" || name.length === 0) {
80
+ errors.push("frontmatter is missing required string field: name");
81
+ }
82
+ else {
83
+ if (!isValidSkillName(name)) {
84
+ errors.push("frontmatter 'name' must be 1-64 chars using lowercase letters, numbers, and single hyphens");
85
+ }
86
+ if (name !== directoryName) {
87
+ errors.push(`frontmatter 'name' (${name}) must match directory name (${directoryName})`);
88
+ }
89
+ }
90
+ if (typeof description !== "string" || description.trim().length === 0) {
91
+ errors.push("frontmatter is missing required non-empty string field: description");
92
+ }
93
+ else if (description.length > 1024) {
94
+ errors.push("frontmatter 'description' must be at most 1024 characters");
95
+ }
96
+ if (license !== undefined && typeof license !== "string") {
97
+ errors.push("frontmatter 'license' must be a string when provided");
98
+ }
99
+ if (compatibility !== undefined) {
100
+ if (typeof compatibility !== "string") {
101
+ errors.push("frontmatter 'compatibility' must be a string when provided");
102
+ }
103
+ else if (compatibility.length < 1 || compatibility.length > 500) {
104
+ errors.push("frontmatter 'compatibility' must be 1-500 characters");
105
+ }
106
+ }
107
+ if (metadata !== undefined && !isStringMap(metadata)) {
108
+ errors.push("frontmatter 'metadata' must be a mapping of string keys to string values");
109
+ }
110
+ if (allowedTools !== undefined && typeof allowedTools !== "string") {
111
+ errors.push("frontmatter 'allowed-tools' must be a string when provided");
112
+ }
113
+ return errors;
114
+ }
115
+ function collectStrictScaffoldErrors(targetDir) {
116
+ const errors = [];
117
+ for (const requiredFile of STRICT_REQUIRED_FILES) {
118
+ if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(targetDir, requiredFile))) {
119
+ errors.push(`missing strict scaffold file: ${requiredFile}`);
120
+ }
121
+ }
122
+ const skillPath = (0, node_path_1.join)(targetDir, "SKILL.md");
123
+ if (!(0, node_fs_1.existsSync)(skillPath)) {
124
+ return errors;
125
+ }
126
+ const skillContent = (0, node_fs_1.readFileSync)(skillPath, "utf8");
127
+ for (const section of REQUIRED_SECTIONS) {
128
+ if (!hasHeadingOutsideFencedCode(skillContent, section)) {
129
+ errors.push(`SKILL.md is missing strict section: ${section}`);
130
+ }
131
+ }
132
+ return errors;
133
+ }
134
+ function hasHeadingOutsideFencedCode(content, heading) {
135
+ const lines = content.split(/\r?\n/);
136
+ const headingPattern = new RegExp(`^\\s{0,3}${escapeRegExp(heading)}\\s*$`);
137
+ let activeFence = null;
138
+ for (const line of lines) {
139
+ const trimmed = line.trim();
140
+ if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) {
141
+ const fence = trimmed.startsWith("```") ? "```" : "~~~";
142
+ if (activeFence === null) {
143
+ activeFence = fence;
144
+ continue;
145
+ }
146
+ if (activeFence === fence) {
147
+ activeFence = null;
148
+ continue;
149
+ }
150
+ }
151
+ if (activeFence) {
152
+ continue;
153
+ }
154
+ if (headingPattern.test(line.replace(/\r$/, ""))) {
155
+ return true;
156
+ }
157
+ }
158
+ return false;
159
+ }
160
+ function escapeRegExp(value) {
161
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
162
+ }
163
+ function validateSkill(targetDir, options = {}) {
164
+ const errors = collectSpecErrors(targetDir);
165
+ if (options.strict) {
166
+ errors.push(...collectStrictScaffoldErrors(targetDir));
167
+ }
168
+ if (errors.length === 0) {
169
+ return {
170
+ status: "passed",
171
+ message: options.strict
172
+ ? "Spec and strict scaffold validation passed."
173
+ : "Spec validation passed.",
174
+ };
175
+ }
176
+ return {
177
+ status: "failed",
178
+ message: errors.join("; "),
179
+ };
180
+ }
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@skillmarkdown/cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for scaffolding SKILL.md-based AI skills",
5
+ "license": "MIT",
6
+ "type": "commonjs",
7
+ "bin": {
8
+ "skillmd": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "main": "dist/cli.js",
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.json",
18
+ "ci:check": "npm run format:check && npm run lint && npm test",
19
+ "clean": "rm -rf dist",
20
+ "lint": "eslint .",
21
+ "lint:fix": "eslint . --fix",
22
+ "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.js\" \"*.{json,mjs}\"",
23
+ "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.js\" \"*.{json,mjs}\"",
24
+ "prepare": "husky",
25
+ "prepublishOnly": "npm run clean && npm run build",
26
+ "smoke:link": "bash ./scripts/smoke-link.sh",
27
+ "smoke:pack": "bash ./scripts/smoke-pack.sh",
28
+ "test": "npm run build && node --test tests/*.test.js"
29
+ },
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/skillmarkdown/cli.git"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/skillmarkdown/cli/issues"
39
+ },
40
+ "homepage": "https://github.com/skillmarkdown/cli#readme",
41
+ "keywords": [
42
+ "skillmarkdown",
43
+ "skillmd",
44
+ "cli"
45
+ ],
46
+ "devDependencies": {
47
+ "@eslint/js": "^10.0.1",
48
+ "@types/node": "^25.3.2",
49
+ "@typescript-eslint/eslint-plugin": "^8.56.1",
50
+ "@typescript-eslint/parser": "^8.56.1",
51
+ "eslint": "^10.0.2",
52
+ "eslint-config-prettier": "^10.1.8",
53
+ "globals": "^17.3.0",
54
+ "husky": "^9.1.7",
55
+ "lint-staged": "^16.3.0",
56
+ "prettier": "^3.8.1",
57
+ "typescript": "^5.9.3"
58
+ },
59
+ "lint-staged": {
60
+ "*.{ts,js}": [
61
+ "eslint --fix --max-warnings=0",
62
+ "prettier --write"
63
+ ],
64
+ "*.{json,mjs,md,yml,yaml}": [
65
+ "prettier --write"
66
+ ]
67
+ },
68
+ "dependencies": {
69
+ "yaml": "^2.8.2"
70
+ }
71
+ }