myskill 1.0.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.
@@ -0,0 +1,204 @@
1
+ import chalk from "chalk";
2
+ import path from "path";
3
+ import fs from "fs-extra";
4
+ import { format } from "prettier";
5
+ import { platforms, getPlatform, getPlatformPath } from "../platforms/index.js";
6
+ import { generateSkill } from "../templates/generateSkill.js";
7
+ import { promptWithCancellation } from "../utils/prompt.js";
8
+
9
+ export async function create(options = {}) {
10
+ let answers = { ...options };
11
+
12
+ if (!options.nonInteractive) {
13
+ console.log(chalk.blue("Interactive Skill Creator"));
14
+
15
+ // Step 1: Platform Selection
16
+ if (!answers.platform) {
17
+ const platformAnswer = await promptWithCancellation([
18
+ {
19
+ type: "list",
20
+ name: "platform",
21
+ message: "Select target platform:",
22
+ choices: Object.values(platforms).map((p) => ({
23
+ name: p.name,
24
+ value: p.id,
25
+ })),
26
+ },
27
+ ]);
28
+ answers.platform = platformAnswer.platform;
29
+ }
30
+
31
+ const platformConfig = getPlatform(answers.platform);
32
+ if (!platformConfig) {
33
+ console.error(chalk.red(`Error: Unknown platform '${answers.platform}'`));
34
+ return;
35
+ }
36
+
37
+ // Helper validation function using Zod schema
38
+ const validateField = (field, value) => {
39
+ const schema = platformConfig.schema.shape[field];
40
+ if (!schema) return true; // No validation defined
41
+ const result = schema.safeParse(value);
42
+ if (result.success) return true;
43
+ return result.error.issues[0].message;
44
+ };
45
+
46
+ // Step 2: Name & Description & Scope
47
+ const remainingPrompts = [];
48
+ if (!answers.name) {
49
+ remainingPrompts.push({
50
+ type: "input",
51
+ name: "name",
52
+ message: "Skill Name:",
53
+ validate: (input) => validateField("name", input),
54
+ });
55
+ }
56
+
57
+ if (!answers.description) {
58
+ remainingPrompts.push({
59
+ type: "input",
60
+ name: "description",
61
+ message: "Description:",
62
+ validate: (input) => validateField("description", input),
63
+ });
64
+ }
65
+
66
+ if (!answers.scope) {
67
+ remainingPrompts.push({
68
+ type: "list",
69
+ name: "scope",
70
+ message: "Where to create the skill?",
71
+ choices: [
72
+ { name: "Current Directory (Project)", value: "project" },
73
+ { name: "Global Skills Directory", value: "global" },
74
+ ],
75
+ });
76
+ }
77
+
78
+ // Platform specific prompts
79
+ if (platformConfig.prompts && platformConfig.prompts.length > 0) {
80
+ remainingPrompts.push(...platformConfig.prompts);
81
+ }
82
+
83
+ const interactiveAnswers = await promptWithCancellation(remainingPrompts);
84
+ answers = { ...answers, ...interactiveAnswers };
85
+ } else {
86
+ // Non-interactive validation
87
+ if (!answers.platform || !answers.name || !answers.description) {
88
+ console.error(
89
+ chalk.red(
90
+ "Error: --platform, --name, and --description are required in non-interactive mode.",
91
+ ),
92
+ );
93
+ if (process.env.NODE_ENV !== "test") process.exit(1);
94
+ else {
95
+ process.exit(1);
96
+ return;
97
+ }
98
+ }
99
+
100
+ const platformConfig = getPlatform(answers.platform);
101
+ if (!platformConfig) {
102
+ console.error(chalk.red(`Error: Unknown platform '${answers.platform}'`));
103
+ if (process.env.NODE_ENV !== "test") process.exit(1);
104
+ else {
105
+ process.exit(1);
106
+ return;
107
+ }
108
+ }
109
+
110
+ // Validate using schema
111
+ const result = platformConfig.schema.safeParse({
112
+ name: answers.name,
113
+ description: answers.description,
114
+ ...answers, // includes platform specific flags if any passed? Command options don't map 1:1 to flags yet except name/desc
115
+ });
116
+
117
+ if (!result.success) {
118
+ console.error(chalk.red("Validation Error:"));
119
+ result.error.issues.forEach((issue) => {
120
+ console.error(chalk.red(`- ${issue.path.join(".")}: ${issue.message}`));
121
+ });
122
+ if (process.env.NODE_ENV !== "test") process.exit(1);
123
+ else {
124
+ process.exit(1);
125
+ return;
126
+ }
127
+ }
128
+ }
129
+
130
+ // Construct Front Matter
131
+ // Filter out internal keys
132
+ const internalKeys = ["scope", "platform", "nonInteractive"];
133
+ const frontMatter = {};
134
+
135
+ // We need to merge answers into frontMatter, but ONLY fields that belong to the schema
136
+ // or platform specific prompts.
137
+ // Easiest way: take everything from answers that isn't internal.
138
+
139
+ for (const [key, value] of Object.entries(answers)) {
140
+ if (!internalKeys.includes(key)) {
141
+ frontMatter[key] = value;
142
+ }
143
+ }
144
+
145
+ // Specific cleanup
146
+ if (frontMatter.restrictTools !== undefined) delete frontMatter.restrictTools;
147
+ if (frontMatter.allowedTools && frontMatter.allowedTools.length === 0)
148
+ delete frontMatter.allowedTools;
149
+ // Also delete confirm if present from previous implementation logic, though here we moved it
150
+ if (frontMatter.confirm !== undefined) delete frontMatter.confirm;
151
+
152
+ const platformConfig = getPlatform(answers.platform); // get again to be safe
153
+
154
+ let fileContent = generateSkill(frontMatter);
155
+
156
+ // Format with Prettier
157
+ try {
158
+ fileContent = await format(fileContent, { parser: "markdown" });
159
+ } catch (e) {
160
+ // Ignore formatting errors, fallback to raw content
161
+ }
162
+
163
+ let targetDir;
164
+ if (answers.scope === "global") {
165
+ const globalPath = await getPlatformPath(answers.platform);
166
+ targetDir = path.join(globalPath, answers.name);
167
+ } else {
168
+ const localBase =
169
+ answers.platform === "opencode"
170
+ ? ".opencode/skill"
171
+ : `.${answers.platform}/skills`;
172
+ targetDir = path.join(process.cwd(), localBase, answers.name);
173
+ }
174
+
175
+ if (!options.nonInteractive) {
176
+ console.log(chalk.yellow(`\nAbout to create skill at: ${targetDir}`));
177
+ console.log(chalk.gray(fileContent));
178
+
179
+ const { confirm } = await promptWithCancellation([
180
+ {
181
+ type: "confirm",
182
+ name: "confirm",
183
+ message: "Proceed?",
184
+ default: true,
185
+ },
186
+ ]);
187
+
188
+ if (!confirm) {
189
+ console.log(chalk.red("Aborted."));
190
+ return;
191
+ }
192
+ }
193
+
194
+ try {
195
+ await fs.ensureDir(targetDir);
196
+ await fs.writeFile(path.join(targetDir, "SKILL.md"), fileContent);
197
+ await fs.ensureDir(path.join(targetDir, "scripts"));
198
+ console.log(chalk.green(`Skill created successfully at ${targetDir}`));
199
+ } catch (error) {
200
+ console.error(chalk.red(`Error creating skill: ${error.message}`));
201
+ if (process.env.NODE_ENV !== "test") process.exit(1);
202
+ else process.exit(1);
203
+ }
204
+ }
@@ -0,0 +1,60 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs-extra";
3
+ import path from "path";
4
+ import simpleGit from "simple-git";
5
+ import { platforms, getPlatformPath } from "../platforms/index.js";
6
+ import { getConfig } from "../utils/config.js";
7
+
8
+ export async function doctor() {
9
+ console.log(chalk.bold("Running diagnostics..."));
10
+ console.log("");
11
+
12
+ let issues = 0;
13
+
14
+ // 1. Check Git
15
+ try {
16
+ const git = simpleGit();
17
+ const version = await git.raw(["--version"]);
18
+ console.log(`${chalk.green("✓")} Git installed: ${version.trim()}`);
19
+ } catch (e) {
20
+ console.log(`${chalk.red("✗")} Git not found or error: ${e.message}`);
21
+ issues++;
22
+ }
23
+
24
+ // 2. Check Platform Directories
25
+ for (const platform of Object.values(platforms)) {
26
+ const pPath = await getPlatformPath(platform.id);
27
+ try {
28
+ await fs.ensureDir(pPath);
29
+ // Check write access
30
+ const testFile = path.join(pPath, ".write-test");
31
+ await fs.writeFile(testFile, "test");
32
+ await fs.remove(testFile);
33
+ console.log(
34
+ `${chalk.green("✓")} ${platform.name} directory writable: ${pPath}`,
35
+ );
36
+ } catch (e) {
37
+ console.log(
38
+ `${chalk.red("✗")} ${platform.name} directory issue: ${e.message}`,
39
+ );
40
+ issues++;
41
+ }
42
+ }
43
+
44
+ // 3. Check Config
45
+ try {
46
+ const config = await getConfig();
47
+ console.log(`${chalk.green("✓")} Config loaded: ${JSON.stringify(config)}`);
48
+ } catch (e) {
49
+ console.log(`${chalk.red("✗")} Config load error: ${e.message}`);
50
+ issues++;
51
+ }
52
+
53
+ console.log("");
54
+ if (issues === 0) {
55
+ console.log(chalk.green("All checks passed!"));
56
+ } else {
57
+ console.log(chalk.yellow(`Found ${issues} issues.`));
58
+ // Don't exit with error code, just report
59
+ }
60
+ }
@@ -0,0 +1,108 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import chalk from "chalk";
4
+ import yaml from "js-yaml";
5
+ import Fuse from "fuse.js";
6
+ import { platforms, getPlatformPath } from "../platforms/index.js";
7
+
8
+ export async function find(query, options = {}) {
9
+ let targetPlatforms = [];
10
+ if (options.platform) {
11
+ if (platforms[options.platform]) {
12
+ targetPlatforms.push(platforms[options.platform]);
13
+ } else {
14
+ console.error(chalk.red(`Error: Unknown platform '${options.platform}'`));
15
+ process.exit(1);
16
+ }
17
+ } else {
18
+ targetPlatforms = Object.values(platforms);
19
+ }
20
+
21
+ const allSkills = [];
22
+
23
+ // Gather skills
24
+ for (const platform of targetPlatforms) {
25
+ const globalPath = await getPlatformPath(platform.id);
26
+ const locations = [{ name: "Global", path: globalPath, type: "global" }];
27
+
28
+ const localBase =
29
+ platform.id === "opencode" ? ".opencode/skill" : `.${platform.id}/skills`;
30
+ locations.push({
31
+ name: "Project",
32
+ path: path.join(process.cwd(), localBase),
33
+ type: "project",
34
+ });
35
+
36
+ for (const loc of locations) {
37
+ if (await fs.pathExists(loc.path)) {
38
+ try {
39
+ const items = await fs.readdir(loc.path, { withFileTypes: true });
40
+ for (const item of items) {
41
+ if (item.isDirectory()) {
42
+ const skillPath = path.join(loc.path, item.name, "SKILL.md");
43
+ if (await fs.pathExists(skillPath)) {
44
+ try {
45
+ const content = await fs.readFile(skillPath, "utf8");
46
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
47
+ if (match) {
48
+ const fm = yaml.load(match[1]);
49
+ allSkills.push({
50
+ name: fm.name || item.name,
51
+ description: fm.description || "",
52
+ platform: platform.name,
53
+ platformId: platform.id,
54
+ location: loc.name,
55
+ path: skillPath,
56
+ });
57
+ }
58
+ } catch (e) {
59
+ // Ignore read errors
60
+ }
61
+ }
62
+ }
63
+ }
64
+ } catch (e) {
65
+ // Ignore readdir errors
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ if (allSkills.length === 0) {
72
+ console.log(chalk.yellow("No skills found."));
73
+ return;
74
+ }
75
+
76
+ let results = allSkills;
77
+
78
+ if (query) {
79
+ const fuse = new Fuse(allSkills, {
80
+ keys: ["name", "description"],
81
+ threshold: 0.4, // Fuzzy threshold (0.0 = exact match, 1.0 = match anything)
82
+ includeScore: true,
83
+ });
84
+ results = fuse.search(query).map((r) => r.item);
85
+ }
86
+
87
+ // Display results
88
+ if (results.length === 0) {
89
+ console.log(chalk.yellow(`No skills matched query '${query}'`));
90
+ return;
91
+ }
92
+
93
+ console.log(chalk.bold(`Found ${results.length} skills:`));
94
+ console.log("------------------------------------------------");
95
+
96
+ // Group by platform for cleaner output? Or just list.
97
+ // List seems better for search results.
98
+
99
+ results.forEach((skill) => {
100
+ console.log(
101
+ `${chalk.green(skill.name)} ${chalk.gray(`[${skill.platform} | ${skill.location}]`)}`,
102
+ );
103
+ if (skill.description) {
104
+ console.log(` ${skill.description}`);
105
+ }
106
+ console.log("");
107
+ });
108
+ }
@@ -0,0 +1,93 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import chalk from "chalk";
4
+ import { platforms, getPlatformPath } from "../platforms/index.js";
5
+ import { promptWithCancellation } from "../utils/prompt.js";
6
+
7
+ export async function install(sourcePath, options = {}) {
8
+ const resolvedSource = path.resolve(sourcePath);
9
+ if (!(await fs.pathExists(resolvedSource))) {
10
+ console.error(
11
+ chalk.red(`Error: Source path ${resolvedSource} does not exist`),
12
+ );
13
+ process.exit(1);
14
+ }
15
+
16
+ let platform;
17
+ if (options.platform) {
18
+ platform = platforms[options.platform];
19
+ if (!platform) {
20
+ console.error(chalk.red(`Error: Unknown platform '${options.platform}'`));
21
+ process.exit(1);
22
+ }
23
+ } else {
24
+ console.log(
25
+ chalk.yellow("Platform not specified. Attempting to detect..."),
26
+ );
27
+
28
+ const skillMdPath = path.join(resolvedSource, "SKILL.md");
29
+ if (await fs.pathExists(skillMdPath)) {
30
+ // Exists
31
+ }
32
+
33
+ const answers = await promptWithCancellation([
34
+ {
35
+ type: "list",
36
+ name: "platform",
37
+ message: "Select target platform to install to:",
38
+ choices: Object.values(platforms).map((p) => ({
39
+ name: p.name,
40
+ value: p.id,
41
+ })),
42
+ },
43
+ ]);
44
+ platform = platforms[answers.platform];
45
+ }
46
+
47
+ const skillName = path.basename(resolvedSource);
48
+ const globalPath = await getPlatformPath(platform.id);
49
+ const targetDir = path.join(globalPath, skillName);
50
+
51
+ if (await fs.pathExists(targetDir)) {
52
+ if (options.force) {
53
+ // Continue
54
+ } else if (options.nonInteractive) {
55
+ console.error(
56
+ chalk.red(
57
+ `Error: Skill already exists at ${targetDir}. Use --force to overwrite.`,
58
+ ),
59
+ );
60
+ process.exit(1);
61
+ } else {
62
+ const { overwrite } = await promptWithCancellation([
63
+ {
64
+ type: "confirm",
65
+ name: "overwrite",
66
+ message: `Skill '${skillName}' already exists in ${platform.name} global directory. Overwrite?`,
67
+ default: false,
68
+ },
69
+ ]);
70
+
71
+ if (!overwrite) {
72
+ console.log(chalk.red("Installation aborted."));
73
+ return;
74
+ }
75
+ }
76
+ }
77
+
78
+ try {
79
+ await fs.ensureDir(globalPath);
80
+ await fs.copy(resolvedSource, targetDir, {
81
+ filter: (src) => {
82
+ const basename = path.basename(src);
83
+ return basename !== "node_modules" && basename !== ".git";
84
+ },
85
+ });
86
+ console.log(
87
+ chalk.green(`Successfully installed '${skillName}' to ${targetDir}`),
88
+ );
89
+ } catch (e) {
90
+ console.error(chalk.red(`Installation failed: ${e.message}`));
91
+ process.exit(1);
92
+ }
93
+ }
@@ -0,0 +1,60 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import chalk from "chalk";
4
+ import yaml from "js-yaml";
5
+ import { platforms, getPlatformPath } from "../platforms/index.js";
6
+
7
+ export async function list(options = {}) {
8
+ let targetPlatforms = [];
9
+ if (options.platform) {
10
+ if (platforms[options.platform]) {
11
+ targetPlatforms.push(platforms[options.platform]);
12
+ } else {
13
+ console.error(chalk.red(`Error: Unknown platform '${options.platform}'`));
14
+ process.exit(1);
15
+ }
16
+ } else {
17
+ targetPlatforms = Object.values(platforms);
18
+ }
19
+
20
+ for (const platform of targetPlatforms) {
21
+ console.log(chalk.blue(`\n=== ${platform.name} Skills ===`));
22
+
23
+ const globalPath = await getPlatformPath(platform.id);
24
+ const locations = [{ name: "Global", path: globalPath }];
25
+
26
+ const localBase =
27
+ platform.id === "opencode" ? ".opencode/skill" : `.${platform.id}/skills`;
28
+ locations.push({
29
+ name: "Project",
30
+ path: path.join(process.cwd(), localBase),
31
+ });
32
+
33
+ for (const loc of locations) {
34
+ if (await fs.pathExists(loc.path)) {
35
+ const items = await fs.readdir(loc.path, { withFileTypes: true });
36
+ for (const item of items) {
37
+ if (item.isDirectory()) {
38
+ const skillPath = path.join(loc.path, item.name, "SKILL.md");
39
+ if (await fs.pathExists(skillPath)) {
40
+ try {
41
+ const content = await fs.readFile(skillPath, "utf8");
42
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
43
+ if (match) {
44
+ const fm = yaml.load(match[1]);
45
+ console.log(
46
+ `- ${chalk.bold(fm.name)} (${loc.name}): ${fm.description || "No description"}`,
47
+ );
48
+ } else {
49
+ console.log(`- ${item.name} (${loc.name}): [Invalid Format]`);
50
+ }
51
+ } catch (e) {
52
+ console.log(`- ${item.name} (${loc.name}): [Read Error]`);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,93 @@
1
+ import simpleGit from "simple-git";
2
+ import path from "path";
3
+ import fs from "fs-extra";
4
+ import chalk from "chalk";
5
+ import { platforms, getPlatform, getPlatformPath } from "../platforms/index.js";
6
+ import { promptWithCancellation } from "../utils/prompt.js";
7
+
8
+ export async function pull(repoUrl, options = {}) {
9
+ // Logic:
10
+ // 1. Identify target platform.
11
+ // 2. Identify target name (from repo name or option).
12
+ // 3. Clone or Pull.
13
+
14
+ if (!repoUrl) {
15
+ console.error(chalk.red("Error: Repository URL is required."));
16
+ process.exit(1);
17
+ }
18
+
19
+ let platformName = options.platform;
20
+ if (!platformName) {
21
+ // Try interactive
22
+ if (!options.nonInteractive) {
23
+ const answer = await promptWithCancellation([
24
+ {
25
+ type: "list",
26
+ name: "platform",
27
+ message: "Target platform for this skill:",
28
+ choices: Object.values(platforms).map((p) => ({
29
+ name: p.name,
30
+ value: p.id,
31
+ })),
32
+ },
33
+ ]);
34
+ platformName = answer.platform;
35
+ } else {
36
+ console.error(chalk.red("Error: Platform is required (--platform)."));
37
+ process.exit(1);
38
+ }
39
+ }
40
+
41
+ const platform = getPlatform(platformName);
42
+ if (!platform) {
43
+ console.error(chalk.red(`Error: Unknown platform '${platformName}'`));
44
+ process.exit(1);
45
+ }
46
+
47
+ // Determine skill name from repo URL if not provided
48
+ let skillName = options.name;
49
+ if (!skillName) {
50
+ const basename = path.basename(repoUrl, ".git");
51
+ skillName = basename;
52
+ }
53
+
54
+ // Target directory
55
+ const platformPath = await getPlatformPath(platformName);
56
+ const targetDir = path.join(platformPath, skillName);
57
+
58
+ const git = simpleGit();
59
+
60
+ // Check if git is installed
61
+ try {
62
+ await git.raw(["--version"]);
63
+ } catch (e) {
64
+ throw new Error(
65
+ "Git is not installed or not in PATH. Please install git to use this command.",
66
+ );
67
+ }
68
+
69
+ if (await fs.pathExists(targetDir)) {
70
+ // Exists, try to pull
71
+ console.log(
72
+ chalk.blue(`Skill '${skillName}' exists. Pulling latest changes...`),
73
+ );
74
+ try {
75
+ await git.cwd(targetDir).pull();
76
+ console.log(chalk.green("Successfully updated skill."));
77
+ } catch (e) {
78
+ console.error(chalk.red(`Error pulling changes: ${e.message}`));
79
+ process.exit(1);
80
+ }
81
+ } else {
82
+ // Clone
83
+ console.log(chalk.blue(`Cloning '${skillName}' to ${targetDir}...`));
84
+ try {
85
+ await fs.ensureDir(platformPath);
86
+ await git.clone(repoUrl, targetDir);
87
+ console.log(chalk.green("Successfully cloned skill."));
88
+ } catch (e) {
89
+ console.error(chalk.red(`Error cloning repo: ${e.message}`));
90
+ process.exit(1);
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,83 @@
1
+ import chalk from "chalk";
2
+ import path from "path";
3
+ import fs from "fs-extra";
4
+ import { spawn } from "child_process";
5
+
6
+ export async function run(skillName, args = [], options = {}) {
7
+ // Goal: Run the skill script.
8
+ // 1. Locate the skill (reuse find logic logic or simple path check).
9
+ // 2. Identify the entry point (e.g. scripts/run.py, scripts/index.js).
10
+ // 3. Execute it.
11
+
12
+ // For simplicity, let's look in current directory first, then use `uninstall`'s detection logic or similar.
13
+ // We'll reuse `uninstall`'s detection logic essentially? Or just rely on current dir for now as experimental.
14
+ // Let's implement a simple path resolver reusing what we learned.
15
+
16
+ let targetPath = path.resolve(skillName);
17
+
18
+ // If absolute path provided and exists
19
+ if (!(await fs.pathExists(targetPath))) {
20
+ // Try resolving as name in current dir
21
+ targetPath = path.resolve(process.cwd(), skillName);
22
+ if (!(await fs.pathExists(targetPath))) {
23
+ // TODO: Look in global paths if we want to run installed skills.
24
+ // For experimental runner, let's stick to local development context for now or global.
25
+ // Let's assume user provides path or name in current dir.
26
+ console.error(
27
+ chalk.red(`Error: Skill directory '${skillName}' not found.`),
28
+ );
29
+ process.exit(1);
30
+ return; // Ensure return for test mocks
31
+ }
32
+ }
33
+
34
+ // Look for entry point
35
+ const scriptsDir = path.join(targetPath, "scripts");
36
+ if (!(await fs.pathExists(scriptsDir))) {
37
+ console.error(
38
+ chalk.red(`Error: 'scripts' directory not found in ${targetPath}`),
39
+ );
40
+ process.exit(1);
41
+ return;
42
+ }
43
+
44
+ const potentialEntryPoints = [
45
+ { file: "index.js", cmd: "node" },
46
+ { file: "main.py", cmd: "python" },
47
+ { file: "run.py", cmd: "python" },
48
+ { file: "run.sh", cmd: "bash" },
49
+ ];
50
+
51
+ let entryPoint;
52
+ for (const ep of potentialEntryPoints) {
53
+ if (await fs.pathExists(path.join(scriptsDir, ep.file))) {
54
+ entryPoint = ep;
55
+ break;
56
+ }
57
+ }
58
+
59
+ if (!entryPoint) {
60
+ console.error(
61
+ chalk.red(
62
+ "Error: No supported entry point found (index.js, main.py, run.py, run.sh).",
63
+ ),
64
+ );
65
+ process.exit(1);
66
+ return;
67
+ }
68
+
69
+ console.log(chalk.blue(`Running ${entryPoint.file}...`));
70
+
71
+ const scriptPath = path.join(scriptsDir, entryPoint.file);
72
+ const child = spawn(entryPoint.cmd, [scriptPath, ...args], {
73
+ stdio: "inherit",
74
+ cwd: targetPath, // Run in skill root context? Or scripts? Usually root.
75
+ });
76
+
77
+ child.on("close", (code) => {
78
+ if (code !== 0) {
79
+ console.error(chalk.red(`Process exited with code ${code}`));
80
+ process.exit(code);
81
+ }
82
+ });
83
+ }