install-agent-skill 1.2.3

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.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @fileoverview Doctor command - Health check
3
+ */
4
+
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { resolveScope, merkleHash, loadSkillLock } from "../helpers.js";
8
+ import { step, stepLine, S, c } from "../ui.js";
9
+ import { STRICT, FIX, DRY, cwd } from "../config.js";
10
+
11
+ /**
12
+ * Run health check on installed skills
13
+ */
14
+ export async function run() {
15
+ const scope = resolveScope();
16
+
17
+ if (!fs.existsSync(scope)) {
18
+ stepLine();
19
+ step("No skills directory found", S.diamond);
20
+ return;
21
+ }
22
+
23
+ stepLine();
24
+ step(c.bold("Health Check"), S.diamondFilled, "cyan");
25
+ stepLine();
26
+
27
+ let errors = 0, warnings = 0;
28
+ const lock = fs.existsSync(path.join(cwd, ".agent", "skill-lock.json")) ? loadSkillLock() : null;
29
+
30
+ for (const name of fs.readdirSync(scope)) {
31
+ const dir = path.join(scope, name);
32
+ if (!fs.statSync(dir).isDirectory()) continue;
33
+
34
+ // Check SKILL.md
35
+ if (!fs.existsSync(path.join(dir, "SKILL.md"))) {
36
+ step(`${name}: ${c.red("missing SKILL.md")}`, S.cross, "red");
37
+ errors++;
38
+ continue;
39
+ }
40
+
41
+ // Check metadata
42
+ const mf = path.join(dir, ".skill-source.json");
43
+ if (!fs.existsSync(mf)) {
44
+ step(`${name}: ${c.red("missing metadata")}`, S.cross, "red");
45
+ errors++;
46
+ continue;
47
+ }
48
+
49
+ const m = JSON.parse(fs.readFileSync(mf, "utf-8"));
50
+ const actual = merkleHash(dir);
51
+
52
+ // Check checksum
53
+ if (actual !== m.checksum) {
54
+ if (FIX && !DRY) {
55
+ m.checksum = actual;
56
+ fs.writeFileSync(mf, JSON.stringify(m, null, 2));
57
+ step(`${name}: ${c.yellow("checksum fixed")}`, S.diamond, "yellow");
58
+ } else {
59
+ step(`${name}: ${c.yellow("checksum drift")}`, S.diamond, "yellow");
60
+ STRICT ? errors++ : warnings++;
61
+ }
62
+ } else if (lock && !lock.skills[name]) {
63
+ step(`${name}: ${c.yellow("not in lock")}`, S.diamond, "yellow");
64
+ STRICT ? errors++ : warnings++;
65
+ } else {
66
+ step(`${name}: ${c.green("healthy")}`, S.check, "green");
67
+ }
68
+ }
69
+
70
+ stepLine();
71
+ console.log(` ${c.gray(S.branch)} Errors: ${errors}, Warnings: ${warnings}`);
72
+ stepLine();
73
+
74
+ if (STRICT && errors) process.exit(1);
75
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @fileoverview Help command
3
+ */
4
+
5
+ import { c } from "../ui.js";
6
+ import { VERSION } from "../config.js";
7
+
8
+ /**
9
+ * Show help
10
+ */
11
+ export async function run() {
12
+ console.log(`
13
+ ${c.bold("add-skill")} ${c.dim("v" + VERSION)}
14
+
15
+ ${c.bold("Usage")}
16
+ $ add-skill <command> [options]
17
+
18
+ ${c.bold("Commands")}
19
+ <org/repo> Install all skills from repository
20
+ <org/repo#skill> Install specific skill
21
+ list List installed skills
22
+ uninstall <skill> Remove a skill
23
+ update <skill> Update a skill
24
+ verify Verify checksums
25
+ doctor Check health
26
+ lock Generate skill-lock.json
27
+ init Initialize skills directory
28
+ validate [skill] Validate against Antigravity spec
29
+ analyze <skill> Analyze skill structure
30
+ cache [info|clear] Manage cache
31
+ info <skill> Show skill info
32
+
33
+ ${c.bold("Options")}
34
+ --global, -g Use global scope
35
+ --force, -f Force operation
36
+ --strict Fail on violations
37
+ --fix Auto-fix issues
38
+ --dry-run Preview only
39
+ --verbose, -v Detailed output
40
+ --json JSON output
41
+ `);
42
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @fileoverview Info command
3
+ */
4
+
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { resolveScope, formatDate } from "../helpers.js";
8
+ import { step, stepLine, S, c, fatal } from "../ui.js";
9
+
10
+ /**
11
+ * Show skill info
12
+ * @param {string} name
13
+ */
14
+ export async function run(name) {
15
+ if (!name) fatal("Missing skill name");
16
+
17
+ const scope = resolveScope();
18
+ const localDir = path.join(scope, name);
19
+
20
+ stepLine();
21
+
22
+ if (fs.existsSync(localDir)) {
23
+ step(`${c.bold(name)} ${c.green("(installed)")}`, S.diamondFilled, "cyan");
24
+ console.log(` ${c.gray(S.branch)} ${c.dim("Path: " + localDir)}`);
25
+
26
+ const mf = path.join(localDir, ".skill-source.json");
27
+ if (fs.existsSync(mf)) {
28
+ const m = JSON.parse(fs.readFileSync(mf, "utf-8"));
29
+ console.log(` ${c.gray(S.branch)} Repo: ${m.repo || "local"}`);
30
+ console.log(` ${c.gray(S.branch)} Installed: ${formatDate(m.installedAt)}`);
31
+ }
32
+ stepLine();
33
+ return;
34
+ }
35
+
36
+ step(`Skill not installed: ${name}`, S.diamond, "yellow");
37
+ stepLine();
38
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @fileoverview Init command
3
+ */
4
+
5
+ import fs from "fs";
6
+ import { step, stepLine, S, success } from "../ui.js";
7
+ import { GLOBAL, GLOBAL_DIR, WORKSPACE, DRY, cwd } from "../config.js";
8
+ import path from "path";
9
+
10
+ /**
11
+ * Initialize skills directory
12
+ */
13
+ export async function run() {
14
+ stepLine();
15
+
16
+ const targetDir = GLOBAL ? GLOBAL_DIR : WORKSPACE;
17
+
18
+ if (fs.existsSync(targetDir)) {
19
+ step(`Skills directory already exists: ${targetDir}`, S.check, "green");
20
+ return;
21
+ }
22
+
23
+ if (DRY) {
24
+ step(`Would create: ${targetDir}`, S.diamond);
25
+ return;
26
+ }
27
+
28
+ fs.mkdirSync(targetDir, { recursive: true });
29
+
30
+ // Create .gitignore if workspace
31
+ if (!GLOBAL) {
32
+ const gi = path.join(cwd, ".agent", ".gitignore");
33
+ if (!fs.existsSync(gi)) {
34
+ fs.writeFileSync(gi, "# Skill caches\nskills/*/.skill-source.json\n");
35
+ }
36
+ }
37
+
38
+ success(`Initialized: ${targetDir}`);
39
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @fileoverview Install command - Interactive skill installation
3
+ */
4
+
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import os from "os";
8
+ import { execSync } from "child_process";
9
+ import prompts from "prompts";
10
+ import ora from "ora";
11
+ import boxen from "boxen";
12
+ import { parseSkillSpec, merkleHash } from "../helpers.js";
13
+ import { parseSkillMdFrontmatter } from "../skills.js";
14
+ import { step, stepLine, S, c, fatal } from "../ui.js";
15
+ import { WORKSPACE, GLOBAL_DIR } from "../config.js";
16
+
17
+ /**
18
+ * Install skills from repository
19
+ * @param {string} spec - Skill spec (org/repo or org/repo#skill)
20
+ */
21
+ export async function run(spec) {
22
+ if (!spec) {
23
+ fatal("Missing skill spec. Usage: add-skill <org/repo>");
24
+ return;
25
+ }
26
+
27
+ const { org, repo, skill: singleSkill, ref } = parseSkillSpec(spec);
28
+
29
+ if (!org || !repo) {
30
+ fatal("Invalid spec. Format: org/repo or org/repo#skill");
31
+ return;
32
+ }
33
+
34
+ const url = `https://github.com/${org}/${repo}.git`;
35
+
36
+ stepLine();
37
+ step("Source: " + c.cyan(url), S.diamond);
38
+
39
+ const spinner = ora({
40
+ text: "Cloning repository",
41
+ prefixText: ` ${c.gray(S.branch)} ${c.cyan(S.diamondFilled)} `,
42
+ color: "cyan"
43
+ }).start();
44
+
45
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "add-skill-"));
46
+
47
+ try {
48
+ execSync(`git clone --depth=1 ${url} "${tmp}"`, { stdio: "pipe" });
49
+ if (ref) execSync(`git -C "${tmp}" checkout ${ref}`, { stdio: "pipe" });
50
+ } catch {
51
+ spinner.stop();
52
+ step(c.red("Failed to clone"), S.cross, "red");
53
+ fs.rmSync(tmp, { recursive: true, force: true });
54
+ return;
55
+ }
56
+
57
+ spinner.stop();
58
+ step("Repository cloned", S.check, "green");
59
+
60
+ // Find skills in repo
61
+ const skillsInRepo = [];
62
+ for (const e of fs.readdirSync(tmp)) {
63
+ const sp = path.join(tmp, e);
64
+ if (fs.statSync(sp).isDirectory() && fs.existsSync(path.join(sp, "SKILL.md"))) {
65
+ const m = parseSkillMdFrontmatter(path.join(sp, "SKILL.md"));
66
+ skillsInRepo.push({
67
+ title: e + (m.description ? c.dim(` (${m.description.substring(0, 40)}...)`) : ""),
68
+ value: e,
69
+ selected: singleSkill ? e === singleSkill : true
70
+ });
71
+ }
72
+ }
73
+
74
+ if (skillsInRepo.length === 0) {
75
+ step(c.yellow("No valid skills found"), S.diamond, "yellow");
76
+ fs.rmSync(tmp, { recursive: true, force: true });
77
+ return;
78
+ }
79
+
80
+ stepLine();
81
+ step(`Found ${skillsInRepo.length} skill${skillsInRepo.length > 1 ? "s" : ""}`, S.diamond);
82
+
83
+ let selectedSkills = [];
84
+
85
+ // If single skill specified via #skill_name, auto-select it
86
+ if (singleSkill) {
87
+ const found = skillsInRepo.find(s => s.value === singleSkill);
88
+ if (!found) {
89
+ stepLine();
90
+ step(c.red(`Skill '${singleSkill}' not found in repository`), S.cross, "red");
91
+ fs.rmSync(tmp, { recursive: true, force: true });
92
+ return;
93
+ }
94
+ selectedSkills = [singleSkill];
95
+ stepLine();
96
+ step(`Auto-selected: ${c.cyan(singleSkill)}`, S.check, "green");
97
+ } else {
98
+ // Show selection prompt for multiple skills
99
+ stepLine();
100
+ step("Select skills to install", S.diamond, "cyan");
101
+
102
+ // Display choices with custom formatting
103
+ const skillsR = await prompts({
104
+ type: "multiselect",
105
+ name: "skills",
106
+ message: "Select skills to install",
107
+ choices: skillsInRepo.map(s => ({
108
+ title: s.title,
109
+ value: s.value,
110
+ selected: true // Pre-select all by default
111
+ })),
112
+ hint: "- Space to toggle. Return to submit",
113
+ instructions: false
114
+ });
115
+
116
+ if (!skillsR.skills || skillsR.skills.length === 0) {
117
+ console.log(`\n ${c.yellow("Cancelled.")}`);
118
+ fs.rmSync(tmp, { recursive: true, force: true });
119
+ return;
120
+ }
121
+
122
+ selectedSkills = skillsR.skills;
123
+ }
124
+
125
+ // Scope selection
126
+ stepLine();
127
+ const scopeR = await prompts({
128
+ type: "select",
129
+ name: "scope",
130
+ message: "Installation scope",
131
+ choices: [
132
+ { title: "Project (current directory)", value: "project" },
133
+ { title: "Global", value: "global" }
134
+ ],
135
+ initial: 0
136
+ });
137
+
138
+ const targetScope = scopeR.scope === "global" ? GLOBAL_DIR : WORKSPACE;
139
+
140
+ // Summary
141
+ stepLine();
142
+ step("Installation Summary", S.diamond);
143
+ stepLine();
144
+
145
+ let boxContent = "";
146
+ for (const sn of selectedSkills) boxContent += `${c.cyan(sn)}\n ${c.dim(targetScope)}\n\n`;
147
+ const box = boxen(boxContent.trim(), { padding: 1, borderStyle: "round", borderColor: "gray", dimBorder: true });
148
+ box.split("\n").forEach(l => console.log(` ${c.gray(S.branch)} ${l}`));
149
+
150
+ stepLine();
151
+
152
+ // Confirmation
153
+ const confirm = await prompts({ type: "confirm", name: "value", message: "Proceed?", initial: true });
154
+ if (!confirm.value) {
155
+ console.log(`\n ${c.yellow("Cancelled.")}`);
156
+ fs.rmSync(tmp, { recursive: true, force: true });
157
+ return;
158
+ }
159
+
160
+ // Install
161
+ stepLine();
162
+ fs.mkdirSync(targetScope, { recursive: true });
163
+
164
+ for (const sn of selectedSkills) {
165
+ const src = path.join(tmp, sn);
166
+ const dest = path.join(targetScope, sn);
167
+
168
+ if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true });
169
+ fs.cpSync(src, dest, { recursive: true });
170
+
171
+ const hash = merkleHash(dest);
172
+ fs.writeFileSync(path.join(dest, ".skill-source.json"), JSON.stringify({
173
+ repo: `${org}/${repo}`,
174
+ skill: sn,
175
+ ref: ref || null,
176
+ checksum: hash,
177
+ installedAt: new Date().toISOString()
178
+ }, null, 2));
179
+
180
+ step(`Installed: ${c.bold(sn)}`, S.check, "green");
181
+ }
182
+
183
+ fs.rmSync(tmp, { recursive: true, force: true });
184
+
185
+ stepLine();
186
+ console.log(` ${c.cyan("Done!")}`);
187
+ console.log();
188
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @fileoverview List command
3
+ */
4
+
5
+ import { getInstalledSkills } from "../skills.js";
6
+ import { resolveScope, formatBytes } from "../helpers.js";
7
+ import { step, stepLine, S, c, outputJSON } from "../ui.js";
8
+ import { VERBOSE, JSON_OUTPUT } from "../config.js";
9
+
10
+ /**
11
+ * List installed skills
12
+ */
13
+ export async function run() {
14
+ stepLine();
15
+ step(c.bold("Installed Skills"), S.diamondFilled, "cyan");
16
+ console.log(` ${c.gray(S.branch)} ${c.dim("Location: " + resolveScope())}`);
17
+ stepLine();
18
+
19
+ const skills = getInstalledSkills();
20
+
21
+ if (skills.length === 0) {
22
+ step(c.dim("No skills installed."), S.diamond);
23
+ stepLine();
24
+ return;
25
+ }
26
+
27
+ if (JSON_OUTPUT) {
28
+ outputJSON({ skills }, true);
29
+ return;
30
+ }
31
+
32
+ for (const s of skills) {
33
+ const icon = s.hasSkillMd ? c.green(S.check) : c.yellow(S.diamond);
34
+ console.log(` ${c.gray(S.branch)} ${icon} ${c.bold(s.name)} ${c.dim("v" + s.version)} ${c.dim("(" + formatBytes(s.size) + ")")}`);
35
+ if (s.description && VERBOSE) {
36
+ console.log(` ${c.gray(S.branch)} ${c.dim(s.description.substring(0, 60))}`);
37
+ }
38
+ }
39
+
40
+ stepLine();
41
+ console.log(` ${c.gray(S.branch)} ${c.dim("Total: " + skills.length + " skill(s)")}`);
42
+ stepLine();
43
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @fileoverview Lock command - Generate skill-lock.json
3
+ */
4
+
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { step, stepLine, success, fatal, outputJSON } from "../ui.js";
8
+ import { WORKSPACE, DRY, cwd, VERSION } from "../config.js";
9
+
10
+ /**
11
+ * Generate skill-lock.json
12
+ */
13
+ export async function run() {
14
+ if (!fs.existsSync(WORKSPACE)) {
15
+ fatal("No .agent/skills directory");
16
+ return;
17
+ }
18
+
19
+ stepLine();
20
+
21
+ const skills = {};
22
+ for (const name of fs.readdirSync(WORKSPACE)) {
23
+ const dir = path.join(WORKSPACE, name);
24
+ if (!fs.statSync(dir).isDirectory()) continue;
25
+
26
+ const mf = path.join(dir, ".skill-source.json");
27
+ if (!fs.existsSync(mf)) continue;
28
+
29
+ const m = JSON.parse(fs.readFileSync(mf, "utf-8"));
30
+ skills[name] = {
31
+ repo: m.repo,
32
+ skill: m.skill,
33
+ ref: m.ref,
34
+ checksum: `sha256:${m.checksum}`,
35
+ publisher: m.publisher || null
36
+ };
37
+ }
38
+
39
+ const lock = {
40
+ lockVersion: 1,
41
+ generatedAt: new Date().toISOString(),
42
+ generator: `@dataguruin/add-skill@${VERSION}`,
43
+ skills
44
+ };
45
+
46
+ if (DRY) {
47
+ step("Would generate skill-lock.json");
48
+ outputJSON(lock, true);
49
+ return;
50
+ }
51
+
52
+ fs.mkdirSync(path.join(cwd, ".agent"), { recursive: true });
53
+ fs.writeFileSync(path.join(cwd, ".agent", "skill-lock.json"), JSON.stringify(lock, null, 2));
54
+
55
+ success("skill-lock.json generated");
56
+ stepLine();
57
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @fileoverview Uninstall command
3
+ */
4
+
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import prompts from "prompts";
8
+ import { resolveScope, createBackup } from "../helpers.js";
9
+ import { step, stepLine, success, fatal, c } from "../ui.js";
10
+ import { DRY } from "../config.js";
11
+
12
+ /**
13
+ * Remove a skill
14
+ * @param {string} skillName
15
+ */
16
+ export async function run(skillName) {
17
+ if (!skillName) fatal("Missing skill name");
18
+
19
+ const scope = resolveScope();
20
+ const targetDir = path.join(scope, skillName);
21
+
22
+ if (!fs.existsSync(targetDir)) fatal(`Skill not found: ${skillName}`);
23
+
24
+ stepLine();
25
+
26
+ const confirm = await prompts({
27
+ type: "confirm",
28
+ name: "value",
29
+ message: `Remove skill "${skillName}"?`,
30
+ initial: false
31
+ });
32
+
33
+ if (!confirm.value) {
34
+ step("Cancelled");
35
+ return;
36
+ }
37
+
38
+ if (DRY) {
39
+ step(`Would remove: ${targetDir}`);
40
+ return;
41
+ }
42
+
43
+ createBackup(targetDir, skillName);
44
+ fs.rmSync(targetDir, { recursive: true, force: true });
45
+
46
+ success(`Removed: ${skillName}`);
47
+ stepLine();
48
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @fileoverview Update command
3
+ */
4
+
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import ora from "ora";
8
+ import { resolveScope, createBackup } from "../helpers.js";
9
+ import { step, stepLine, S, c, fatal } from "../ui.js";
10
+ import { DRY } from "../config.js";
11
+
12
+ /**
13
+ * Update a skill
14
+ * @param {string} skillName
15
+ */
16
+ export async function run(skillName) {
17
+ if (!skillName) fatal("Missing skill name");
18
+
19
+ const scope = resolveScope();
20
+ const targetDir = path.join(scope, skillName);
21
+
22
+ if (!fs.existsSync(targetDir)) fatal(`Skill not found: ${skillName}`);
23
+
24
+ const metaFile = path.join(targetDir, ".skill-source.json");
25
+ if (!fs.existsSync(metaFile)) fatal("Skill metadata not found");
26
+
27
+ const meta = JSON.parse(fs.readFileSync(metaFile, "utf-8"));
28
+ if (!meta.repo || meta.repo === "local") fatal("Cannot update local skill");
29
+
30
+ stepLine();
31
+
32
+ const spinner = ora({
33
+ text: `Updating ${skillName}`,
34
+ prefixText: ` ${c.gray(S.branch)} ${c.cyan(S.diamondFilled)} `,
35
+ color: "cyan"
36
+ }).start();
37
+
38
+ try {
39
+ if (!DRY) {
40
+ createBackup(targetDir, skillName);
41
+ fs.rmSync(targetDir, { recursive: true, force: true });
42
+ }
43
+
44
+ const spec = `${meta.repo}#${meta.skill}${meta.ref ? "@" + meta.ref : ""}`;
45
+
46
+ if (DRY) {
47
+ spinner.stop();
48
+ step(`Would update: ${skillName}`);
49
+ } else {
50
+ // Dynamically import install command
51
+ const { run: install } = await import("./install.js");
52
+ await install(spec);
53
+ }
54
+ } catch (err) {
55
+ spinner.stop();
56
+ step(`Failed: ${err.message}`, S.cross, "red");
57
+ }
58
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @fileoverview Validate command - Antigravity spec validation
3
+ */
4
+
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { getInstalledSkills, parseSkillMdFrontmatter } from "../skills.js";
8
+ import { resolveScope } from "../helpers.js";
9
+ import { step, stepLine, S, c, fatal } from "../ui.js";
10
+ import { VERBOSE, STRICT } from "../config.js";
11
+
12
+ /**
13
+ * Validate skills against Antigravity spec
14
+ * @param {string} [skillName] - Optional specific skill
15
+ */
16
+ export async function run(skillName) {
17
+ const scope = resolveScope();
18
+ let skillsToValidate = [];
19
+
20
+ if (skillName) {
21
+ const sd = path.join(scope, skillName);
22
+ if (!fs.existsSync(sd)) fatal(`Skill not found: ${skillName}`);
23
+ skillsToValidate = [{ name: skillName, path: sd }];
24
+ } else {
25
+ skillsToValidate = getInstalledSkills();
26
+ }
27
+
28
+ if (skillsToValidate.length === 0) {
29
+ stepLine();
30
+ step("No skills to validate", S.diamond);
31
+ return;
32
+ }
33
+
34
+ stepLine();
35
+ step(c.bold("Antigravity Validation"), S.diamondFilled, "cyan");
36
+ stepLine();
37
+
38
+ let totalErrors = 0, totalWarnings = 0;
39
+
40
+ for (const skill of skillsToValidate) {
41
+ const errors = [], warnings = [];
42
+ const smp = path.join(skill.path, "SKILL.md");
43
+
44
+ if (!fs.existsSync(smp)) {
45
+ errors.push("Missing SKILL.md");
46
+ } else {
47
+ const m = parseSkillMdFrontmatter(smp);
48
+ if (!m.description) errors.push("Missing description");
49
+ else if (m.description.length < 50) warnings.push("Description too short");
50
+ }
51
+
52
+ const status = errors.length > 0 ? c.red("FAIL") : warnings.length > 0 ? c.yellow("WARN") : c.green("PASS");
53
+ console.log(` ${c.gray(S.branch)} ${status} ${c.bold(skill.name)}`);
54
+
55
+ if (VERBOSE || errors.length || warnings.length) {
56
+ errors.forEach(e => console.log(` ${c.gray(S.branch)} ${c.red("ERROR: " + e)}`));
57
+ warnings.forEach(w => console.log(` ${c.gray(S.branch)} ${c.yellow("WARN: " + w)}`));
58
+ }
59
+
60
+ totalErrors += errors.length;
61
+ totalWarnings += warnings.length;
62
+ }
63
+
64
+ stepLine();
65
+ console.log(` ${c.gray(S.branch)} Total: ${skillsToValidate.length}, Errors: ${totalErrors}, Warnings: ${totalWarnings}`);
66
+ stepLine();
67
+
68
+ if (STRICT && totalErrors > 0) process.exit(1);
69
+ }