heymark 2.0.0 → 2.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.
Files changed (38) hide show
  1. package/LICENSE +21 -21
  2. package/README.ko.md +121 -123
  3. package/README.md +123 -125
  4. package/package.json +57 -56
  5. package/src/alias.js +41 -0
  6. package/src/commands/clean/index.js +17 -0
  7. package/src/commands/cleaner.js +27 -0
  8. package/src/commands/constants.js +14 -0
  9. package/src/commands/help/index.js +34 -0
  10. package/src/commands/link/flags/branch.js +18 -0
  11. package/src/commands/link/flags/folder.js +18 -0
  12. package/src/commands/link/index.js +58 -0
  13. package/src/commands/select-tools.js +35 -0
  14. package/src/commands/sync/index.js +35 -0
  15. package/src/index.js +52 -0
  16. package/src/skill-repo/cache-folder.js +77 -0
  17. package/src/skill-repo/config-file.js +87 -0
  18. package/src/skill-repo/constants.js +14 -0
  19. package/src/skill-repo/skill-file-parser.js +79 -0
  20. package/src/tools/antigravity/index.js +33 -0
  21. package/src/tools/claude-code/index.js +33 -0
  22. package/src/tools/codex/index.js +33 -0
  23. package/src/tools/constants.js +52 -0
  24. package/src/tools/copilot/index.js +40 -0
  25. package/src/tools/cursor/index.js +39 -0
  26. package/src/tools/loader.js +17 -0
  27. package/src/tools/openclaw/index.js +72 -0
  28. package/src/tools/skill-per-file.js +29 -0
  29. package/src/tools/skill-per-folder.js +18 -0
  30. package/scripts/cli.js +0 -375
  31. package/scripts/lib/config.js +0 -126
  32. package/scripts/lib/parser.js +0 -125
  33. package/scripts/lib/repo.js +0 -97
  34. package/scripts/tools/antigravity.js +0 -49
  35. package/scripts/tools/claude.js +0 -49
  36. package/scripts/tools/codex.js +0 -49
  37. package/scripts/tools/copilot.js +0 -61
  38. package/scripts/tools/cursor.js +0 -48
@@ -0,0 +1,33 @@
1
+ const { CODEX } = require("@/tools/constants");
2
+ const { generate, clean } = require("@/tools/skill-per-folder");
3
+
4
+ function createContent(skill) {
5
+ const frontmatterLines = [
6
+ "---",
7
+ `name: ${skill.name}`,
8
+ `description: "${skill.description}"`,
9
+ "---",
10
+ ];
11
+
12
+ return `${frontmatterLines.join("\n")}\n\n${skill.body}\n`;
13
+ }
14
+
15
+ module.exports = {
16
+ key: CODEX.KEY,
17
+ name: CODEX.NAME,
18
+ output: CODEX.OUTPUT_PATTERN,
19
+
20
+ generate(skills, cwd) {
21
+ return generate({
22
+ cwd,
23
+ dir: CODEX.SKILLS_DIR,
24
+ fileName: CODEX.SKILL_FILE_NAME,
25
+ skills,
26
+ createContent,
27
+ });
28
+ },
29
+
30
+ clean(skillNames, cwd) {
31
+ return clean(cwd, CODEX.SKILLS_DIR);
32
+ },
33
+ };
@@ -0,0 +1,52 @@
1
+ const path = require("path");
2
+
3
+ const ANTIGRAVITY = {
4
+ KEY: "antigravity",
5
+ NAME: "Antigravity",
6
+ SKILLS_DIR: path.join(".agent", "skills"),
7
+ SKILL_FILE_NAME: "SKILL.md",
8
+ OUTPUT_PATTERN: ".agent/skills/*/SKILL.md",
9
+ };
10
+
11
+ const CLAUDE_CODE = {
12
+ KEY: "claude-code",
13
+ NAME: "Claude Code",
14
+ SKILLS_DIR: path.join(".claude", "skills"),
15
+ SKILL_FILE_NAME: "SKILL.md",
16
+ OUTPUT_PATTERN: ".claude/skills/*/SKILL.md",
17
+ };
18
+
19
+ const CODEX = {
20
+ KEY: "codex",
21
+ NAME: "Codex",
22
+ SKILLS_DIR: path.join(".agents", "skills"),
23
+ SKILL_FILE_NAME: "SKILL.md",
24
+ OUTPUT_PATTERN: ".agents/skills/*/SKILL.md",
25
+ };
26
+
27
+ const COPILOT = {
28
+ KEY: "copilot",
29
+ NAME: "Copilot",
30
+ INSTRUCTIONS_DIR: path.join(".github", "instructions"),
31
+ FILE_SUFFIX: ".instructions.md",
32
+ DEFAULT_GLOB: "**",
33
+ OUTPUT_PATTERN: ".github/instructions/*.instructions.md",
34
+ };
35
+
36
+ const CURSOR = {
37
+ KEY: "cursor",
38
+ NAME: "Cursor",
39
+ SKILLS_DIR: path.join(".cursor", "rules"),
40
+ FILE_SUFFIX: ".mdc",
41
+ OUTPUT_PATTERN: ".cursor/rules/*.mdc",
42
+ };
43
+
44
+ const OPENCLAW = {
45
+ KEY: "openclaw",
46
+ NAME: "OpenClaw",
47
+ SKILLS_DIR: path.join(".openclaw", "skills"),
48
+ SKILL_FILE_NAME: "SKILL.md",
49
+ OUTPUT_PATTERN: "~/.openclaw/skills/*/SKILL.md",
50
+ };
51
+
52
+ module.exports = { ANTIGRAVITY, CLAUDE_CODE, CODEX, COPILOT, CURSOR, OPENCLAW };
@@ -0,0 +1,40 @@
1
+ const { COPILOT } = require("@/tools/constants");
2
+ const { generate, clean } = require("@/tools/skill-per-file");
3
+
4
+ function getFileName(skill) {
5
+ return `${skill.name}${COPILOT.FILE_SUFFIX}`;
6
+ }
7
+
8
+ function createContent(skill) {
9
+ const globs = skill.globs
10
+ ? skill.globs
11
+ .split(",")
12
+ .map((g) => g.trim())
13
+ .filter(Boolean)
14
+ : [];
15
+ const applyToLines = (globs.length > 0 ? globs : [COPILOT.DEFAULT_GLOB])
16
+ .map((glob) => ` - "${glob}"`)
17
+ .join("\n");
18
+ const header = `applyTo:\n${applyToLines}\n---`;
19
+ return `${header}\n\n${skill.body}\n`;
20
+ }
21
+
22
+ module.exports = {
23
+ key: COPILOT.KEY,
24
+ name: COPILOT.NAME,
25
+ output: COPILOT.OUTPUT_PATTERN,
26
+
27
+ generate(skills, cwd) {
28
+ return generate({
29
+ cwd,
30
+ dir: COPILOT.INSTRUCTIONS_DIR,
31
+ skills,
32
+ getFileName,
33
+ createContent,
34
+ });
35
+ },
36
+
37
+ clean(skillNames, cwd) {
38
+ return clean(cwd, COPILOT.INSTRUCTIONS_DIR);
39
+ },
40
+ };
@@ -0,0 +1,39 @@
1
+ const { CURSOR } = require("@/tools/constants");
2
+ const { generate, clean } = require("@/tools/skill-per-file");
3
+
4
+ function getFileName(skill) {
5
+ return `${skill.name}${CURSOR.FILE_SUFFIX}`;
6
+ }
7
+
8
+ function createContent(skill) {
9
+ const frontmatterLines = ["---", `description: "${skill.description}"`];
10
+
11
+ if (skill.globs) {
12
+ frontmatterLines.push(`globs: "${skill.globs}"`);
13
+ }
14
+
15
+ frontmatterLines.push(`alwaysApply: ${skill.alwaysApply}`);
16
+ frontmatterLines.push("---");
17
+
18
+ return `${frontmatterLines.join("\n")}\n\n${skill.body}\n`;
19
+ }
20
+
21
+ module.exports = {
22
+ key: CURSOR.KEY,
23
+ name: CURSOR.NAME,
24
+ output: CURSOR.OUTPUT_PATTERN,
25
+
26
+ generate(skills, cwd) {
27
+ return generate({
28
+ cwd,
29
+ dir: CURSOR.SKILLS_DIR,
30
+ skills,
31
+ getFileName,
32
+ createContent,
33
+ });
34
+ },
35
+
36
+ clean(skillNames, cwd) {
37
+ return clean(cwd, CURSOR.SKILLS_DIR);
38
+ },
39
+ };
@@ -0,0 +1,17 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const TOOLS_DIR = path.join(__dirname);
5
+
6
+ function loadTools() {
7
+ const tools = fs
8
+ .readdirSync(TOOLS_DIR)
9
+ .filter((name) => fs.statSync(path.join(TOOLS_DIR, name)).isDirectory())
10
+ .map((name) => require(path.join(TOOLS_DIR, name)));
11
+
12
+ return Object.fromEntries(tools.map((tool) => [tool.key, tool]));
13
+ }
14
+
15
+ module.exports = {
16
+ loadTools,
17
+ };
@@ -0,0 +1,72 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const os = require("os");
4
+ const { OPENCLAW } = require("@/tools/constants");
5
+
6
+ function createContent(skill) {
7
+ const frontmatterLines = [
8
+ "---",
9
+ `name: ${skill.name}`,
10
+ `description: "${skill.description}"`,
11
+ "---",
12
+ ];
13
+
14
+ return `${frontmatterLines.join("\n")}\n\n${skill.body}\n`;
15
+ }
16
+
17
+ function getSkillsDir() {
18
+ return path.join(os.homedir(), OPENCLAW.SKILLS_DIR);
19
+ }
20
+
21
+ function generate(skills) {
22
+ const skillsDir = getSkillsDir();
23
+
24
+ for (const skill of skills) {
25
+ try {
26
+ const skillDir = path.join(skillsDir, skill.name);
27
+ fs.mkdirSync(skillDir, { recursive: true });
28
+ fs.writeFileSync(path.join(skillDir, OPENCLAW.SKILL_FILE_NAME), createContent(skill), "utf8");
29
+ } catch (err) {
30
+ const skillDir = path.join(skillsDir, skill.name);
31
+ throw new Error(`OpenClaw: failed to generate skill "${skill.name}" at ${skillDir}: ${err.message}`);
32
+ }
33
+ }
34
+
35
+ return skills.length;
36
+ }
37
+
38
+ function clean(skillNames) {
39
+ const skillsDir = getSkillsDir();
40
+ if (!fs.existsSync(skillsDir)) {
41
+ return [];
42
+ }
43
+
44
+ const cleanedPaths = [];
45
+ for (const skillName of skillNames) {
46
+ const skillDir = path.join(skillsDir, skillName);
47
+ if (!fs.existsSync(skillDir)) continue;
48
+
49
+ try {
50
+ fs.rmSync(skillDir, { recursive: true, force: true });
51
+ cleanedPaths.push(path.join(skillsDir, skillName));
52
+ } catch (err) {
53
+ throw new Error(`OpenClaw: failed to clean skill "${skillName}" at ${skillDir}: ${err.message}`);
54
+ }
55
+ }
56
+
57
+ return cleanedPaths;
58
+ }
59
+
60
+ module.exports = {
61
+ key: OPENCLAW.KEY,
62
+ name: OPENCLAW.NAME,
63
+ output: OPENCLAW.OUTPUT_PATTERN,
64
+
65
+ generate(skills) {
66
+ return generate(skills);
67
+ },
68
+
69
+ clean(skillNames) {
70
+ return clean(skillNames);
71
+ },
72
+ };
@@ -0,0 +1,29 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ function generate({ cwd, dir, skills, getFileName, createContent }) {
5
+ const destDir = path.join(cwd, dir);
6
+ fs.mkdirSync(destDir, { recursive: true });
7
+
8
+ for (const skill of skills) {
9
+ const filePath = path.join(destDir, getFileName(skill));
10
+ fs.writeFileSync(filePath, createContent(skill), "utf8");
11
+ }
12
+
13
+ return skills.length;
14
+ }
15
+
16
+ function clean(cwd, dir) {
17
+ const targetPath = path.join(cwd, dir);
18
+ if (!fs.existsSync(targetPath)) {
19
+ return [];
20
+ }
21
+
22
+ fs.rmSync(targetPath, { recursive: true, force: true });
23
+ return [dir];
24
+ }
25
+
26
+ module.exports = {
27
+ generate,
28
+ clean,
29
+ };
@@ -0,0 +1,18 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { clean } = require("@/tools/skill-per-file");
4
+
5
+ function generate({ cwd, dir, fileName, skills, createContent }) {
6
+ for (const skill of skills) {
7
+ const skillDir = path.join(cwd, dir, skill.name);
8
+ fs.mkdirSync(skillDir, { recursive: true });
9
+ fs.writeFileSync(path.join(skillDir, fileName), createContent(skill), "utf8");
10
+ }
11
+
12
+ return skills.length;
13
+ }
14
+
15
+ module.exports = {
16
+ generate,
17
+ clean,
18
+ };
package/scripts/cli.js DELETED
@@ -1,375 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- const fs = require("fs");
5
- const path = require("path");
6
- const { CONFIG_RELATIVE, DEFAULT_BRANCH, loadConfig, writeConfig } = require("./lib/config");
7
- const { loadRules } = require("./lib/parser");
8
- const { CACHE_DIR_NAME, getLinkedRulesDir, sanitizeRepoName } = require("./lib/repo");
9
-
10
- const SCRIPT_DIR = __dirname;
11
- const PROJECT_ROOT = process.cwd();
12
-
13
- const COMMAND_LINK = "link";
14
- const COMMAND_SYNC = "sync";
15
- const COMMAND_CLEAN = "clean";
16
- const COMMAND_STATUS = "status";
17
- const COMMAND_HELP = "help";
18
-
19
- const OPTION_BRANCH = "--branch";
20
- const OPTION_FOLDER = "--folder";
21
- const OPTION_SAMPLES = "--samples";
22
- const SHORT_BRANCH = "-b";
23
- const SHORT_FOLDER = "-f";
24
- const TARGET_ALL = "all";
25
- const TARGET_DOT = ".";
26
- const LATEST_VERSION_COMMAND = "npx heymark@latest";
27
- const SAMPLES_REPO_URL = "https://github.com/MosslandOpenDevs/heymark.git";
28
- const SAMPLES_FOLDER = "samples";
29
-
30
- function exitWithError(message, details = []) {
31
- console.error(`[Error] ${message}`);
32
- details.forEach((detail) => console.error(` ${detail}`));
33
- process.exit(1);
34
- }
35
-
36
- function discoverTools() {
37
- const toolsDir = path.join(SCRIPT_DIR, "tools");
38
- const registry = {};
39
-
40
- fs.readdirSync(toolsDir)
41
- .filter((fileName) => fileName.endsWith(".js"))
42
- .sort()
43
- .forEach((file) => {
44
- const key = path.basename(file, ".js");
45
- registry[key] = require(path.join(toolsDir, file));
46
- });
47
-
48
- return registry;
49
- }
50
-
51
- function showHelp(tools) {
52
- const toolLines = Object.entries(tools)
53
- .map(([toolKey, toolDefinition]) => {
54
- const paddedKey = toolKey.padEnd(10);
55
- const paddedName = toolDefinition.name.padEnd(16);
56
- return ` ${paddedKey} ${paddedName} -> ${toolDefinition.output}`;
57
- })
58
- .join("\n");
59
-
60
- console.log(`
61
- Heymark CLI
62
-
63
- Reads *.md from a GitHub repository (public or private) and generates
64
- tool-specific configuration files for various AI coding assistants.
65
- Same rules everywhere: A computer, B computer, same remote repo.
66
-
67
- Usage:
68
- heymark help
69
- heymark link <repo-url> [--branch <name>] [--folder <path>]
70
- heymark link --samples
71
- heymark sync [.|<tool>...]
72
- heymark clean [.|<tool>...]
73
- heymark status
74
- heymark
75
-
76
- Options:
77
- --branch, -b <name> Branch name (used with 'link')
78
- --folder, -f <path> Subdirectory path in repository (used with 'link')
79
- --samples Use built-in sample Skill repository
80
-
81
- Targets:
82
- . All available tools
83
- <tool>... Space-separated tool names (no commas)
84
-
85
- Available tools:
86
- ${toolLines}
87
-
88
- Examples:
89
- heymark help
90
- heymark link --samples
91
- heymark link https://github.com/org/my-rules.git
92
- heymark link https://github.com/org/my-rules.git --folder rules --branch main
93
- heymark sync .
94
- heymark sync cursor claude
95
- heymark clean .
96
- heymark status
97
- heymark
98
-
99
- Latest version:
100
- ${LATEST_VERSION_COMMAND}
101
- `);
102
- }
103
-
104
- function parseLinkArgs(args) {
105
- if (args.length === 1 && args[0] === OPTION_SAMPLES) {
106
- return {
107
- repoUrl: SAMPLES_REPO_URL,
108
- branch: DEFAULT_BRANCH,
109
- folder: SAMPLES_FOLDER,
110
- };
111
- }
112
-
113
- if (args.includes(OPTION_SAMPLES)) {
114
- exitWithError("--samples cannot be combined with other link arguments.", [
115
- "Use: heymark link --samples",
116
- ]);
117
- }
118
-
119
- const repoUrl = args[0];
120
- if (!repoUrl || repoUrl.startsWith("--")) {
121
- exitWithError("link requires a GitHub repository URL.", [
122
- "Example: heymark link https://github.com/org/my-rules.git",
123
- "Example: heymark link git@github.com:org/my-rules.git",
124
- "Optional: --branch <branch> --folder <subdir> (e.g. --folder rules)",
125
- ]);
126
- }
127
-
128
- let branch = DEFAULT_BRANCH;
129
- let folder = "";
130
-
131
- for (let i = 1; i < args.length; i++) {
132
- const arg = args[i];
133
-
134
- if (arg === OPTION_BRANCH || arg === SHORT_BRANCH) {
135
- const value = args[++i];
136
- if (!value) {
137
- exitWithError("--branch requires a branch name.");
138
- }
139
- branch = value.trim();
140
- continue;
141
- }
142
-
143
- if (arg === OPTION_FOLDER || arg === SHORT_FOLDER) {
144
- const value = args[++i];
145
- if (!value) {
146
- exitWithError("--folder requires a folder path.");
147
- }
148
- folder = value.trim();
149
- continue;
150
- }
151
-
152
- exitWithError(`Unknown option for link: ${arg}`);
153
- }
154
-
155
- return { repoUrl: repoUrl.trim(), branch, folder };
156
- }
157
-
158
- function runLink(args) {
159
- const config = parseLinkArgs(args);
160
- const configPath = writeConfig(PROJECT_ROOT, config);
161
-
162
- console.log(
163
- `[Link] Linked repository saved to ${path.relative(PROJECT_ROOT, configPath) || configPath}`
164
- );
165
- console.log(` repoUrl: ${config.repoUrl}`);
166
- if (config.branch !== DEFAULT_BRANCH) {
167
- console.log(` branch: ${config.branch}`);
168
- }
169
- if (config.folder) {
170
- console.log(` folder: ${config.folder}`);
171
- }
172
- console.log("");
173
- console.log("Run 'heymark sync .' to fetch rules and generate tool configs.");
174
- }
175
-
176
- function loadLinkedConfigOrExit() {
177
- const linkedConfig = loadConfig(PROJECT_ROOT);
178
- if (linkedConfig) {
179
- return linkedConfig;
180
- }
181
-
182
- exitWithError("No linked repository found.", [
183
- `Run: heymark ${COMMAND_LINK} <github-repo-url>`,
184
- `Config: ${CONFIG_RELATIVE}`,
185
- ]);
186
- }
187
-
188
- function resolveSelectedTools(toolArgs, availableTools) {
189
- const availableToolKeys = Object.keys(availableTools);
190
- if (toolArgs.length === 0) {
191
- return availableToolKeys;
192
- }
193
-
194
- const selectedTools = toolArgs.map((tool) => tool.trim().toLowerCase()).filter(Boolean);
195
- if (selectedTools.length === 0) {
196
- return availableToolKeys;
197
- }
198
-
199
- if (selectedTools.some((tool) => tool.includes(","))) {
200
- exitWithError("Tool names must be space-separated (no commas).", [
201
- "Example: heymark sync cursor claude",
202
- ]);
203
- }
204
-
205
- const hasDotToken = selectedTools.includes(TARGET_DOT);
206
- const hasAllToken = selectedTools.includes(TARGET_ALL);
207
-
208
- if (hasAllToken) {
209
- exitWithError("'all' is not supported. Use '.' for all tools.");
210
- }
211
-
212
- if (hasDotToken) {
213
- if (selectedTools.length > 1) {
214
- exitWithError(`'${TARGET_DOT}' cannot be combined with tool names.`);
215
- }
216
- return availableToolKeys;
217
- }
218
-
219
- const invalidTools = selectedTools.filter((tool) => !availableTools[tool]);
220
- if (invalidTools.length > 0) {
221
- exitWithError(`Unknown tool(s): ${invalidTools.join(", ")}`, [
222
- `Available: ${availableToolKeys.join(", ")}`,
223
- ]);
224
- }
225
-
226
- return Array.from(new Set(selectedTools));
227
- }
228
-
229
- function cleanGeneratedFiles(tools, selectedTools, ruleNames, onlyPrintWhenDeleted) {
230
- for (const toolKey of selectedTools) {
231
- const cleanedPaths = tools[toolKey].clean(ruleNames, PROJECT_ROOT);
232
- if (onlyPrintWhenDeleted && cleanedPaths.length === 0) {
233
- continue;
234
- }
235
-
236
- cleanedPaths.forEach((filePath) => {
237
- console.log(` Deleted: ${filePath}`);
238
- });
239
- }
240
- }
241
-
242
- function loadRulesFromLinkedRepo() {
243
- const linkedConfig = loadLinkedConfigOrExit();
244
- const rulesDir = getLinkedRulesDir(PROJECT_ROOT, linkedConfig);
245
- const rules = loadRules(rulesDir);
246
- return { linkedConfig, rulesDir, rules };
247
- }
248
-
249
- function printSyncContext(selectedTools, rulesDir) {
250
- const rulesRelPath = path.relative(PROJECT_ROOT, rulesDir) || ".";
251
- console.log("[Sync] Starting convention sync...");
252
- console.log(` Source: ${rulesRelPath} (from linked repo)`);
253
- console.log(` Target: ${PROJECT_ROOT}`);
254
- console.log(` Tools: ${selectedTools.join(", ")}`);
255
- console.log("");
256
- }
257
-
258
- function runSync(toolArgs, tools) {
259
- const selectedTools = resolveSelectedTools(toolArgs, tools);
260
- const { rulesDir, rules } = loadRulesFromLinkedRepo();
261
- printSyncContext(selectedTools, rulesDir);
262
-
263
- console.log(`[Load] ${rules.length} rule(s): ${rules.map((rule) => rule.name).join(", ")}`);
264
- console.log("");
265
-
266
- const ruleNames = rules.map((rule) => rule.name);
267
-
268
- // Ensure regenerated output is always fresh.
269
- console.log("[Clean] Removing existing generated files...");
270
- cleanGeneratedFiles(tools, selectedTools, ruleNames, true);
271
- console.log("");
272
-
273
- console.log("[Generate]");
274
- for (const toolKey of selectedTools) {
275
- const toolDefinition = tools[toolKey];
276
- const count = toolDefinition.generate(rules, PROJECT_ROOT);
277
- const summary = `${toolDefinition.name.padEnd(16)} -> ${toolDefinition.output}`;
278
- console.log(` ${summary} (${count} rules)`);
279
- }
280
-
281
- console.log("");
282
- console.log(`[Done] ${selectedTools.length} tool(s) synced successfully.`);
283
- }
284
-
285
- function runClean(toolArgs, tools) {
286
- const selectedTools = resolveSelectedTools(toolArgs, tools);
287
- const { rulesDir, rules } = loadRulesFromLinkedRepo();
288
- const rulesRelPath = path.relative(PROJECT_ROOT, rulesDir) || ".";
289
-
290
- console.log("[Clean] Removing generated files...");
291
- console.log(` Source: ${rulesRelPath} (from linked repo)`);
292
- console.log(` Tools: ${selectedTools.join(", ")}`);
293
- console.log("");
294
-
295
- const ruleNames = rules.map((rule) => rule.name);
296
- cleanGeneratedFiles(tools, selectedTools, ruleNames, false);
297
-
298
- console.log("");
299
- console.log(`[Done] ${selectedTools.length} tool(s) cleaned successfully.`);
300
- }
301
-
302
- function runStatus(tools) {
303
- const linkedConfig = loadConfig(PROJECT_ROOT);
304
- const toolKeys = Object.keys(tools);
305
-
306
- console.log("[Status] Heymark");
307
- console.log(` Project: ${PROJECT_ROOT}`);
308
- console.log(` Config: ${CONFIG_RELATIVE}`);
309
- console.log(` Tools: ${toolKeys.join(", ")}`);
310
- console.log(` Latest: ${LATEST_VERSION_COMMAND}`);
311
- console.log("");
312
-
313
- if (!linkedConfig) {
314
- console.log("No repository is linked yet.");
315
- console.log(`Run: heymark ${COMMAND_LINK} <github-repo-url>`);
316
- return;
317
- }
318
-
319
- const cachePath = path.join(
320
- PROJECT_ROOT,
321
- CACHE_DIR_NAME,
322
- sanitizeRepoName(linkedConfig.repoUrl)
323
- );
324
- const cacheState = fs.existsSync(cachePath) ? "ready" : "not-fetched";
325
-
326
- console.log("Linked repository:");
327
- console.log(` repoUrl: ${linkedConfig.repoUrl}`);
328
- console.log(` branch: ${linkedConfig.branch || DEFAULT_BRANCH}`);
329
- console.log(` folder: ${linkedConfig.folder || "(root)"}`);
330
- console.log(` cache: ${cacheState}`);
331
- }
332
-
333
- function main() {
334
- const tools = discoverTools();
335
- const args = process.argv.slice(2);
336
-
337
- if (args.length === 0) {
338
- runStatus(tools);
339
- return;
340
- }
341
-
342
- const command = args[0];
343
- const commandArgs = args.slice(1);
344
-
345
- if (command === COMMAND_LINK) {
346
- runLink(commandArgs);
347
- return;
348
- }
349
- if (command === COMMAND_SYNC) {
350
- runSync(commandArgs, tools);
351
- return;
352
- }
353
- if (command === COMMAND_CLEAN) {
354
- runClean(commandArgs, tools);
355
- return;
356
- }
357
- if (command === COMMAND_STATUS) {
358
- if (commandArgs.length > 0) {
359
- exitWithError("status does not accept arguments.");
360
- }
361
- runStatus(tools);
362
- return;
363
- }
364
- if (command === COMMAND_HELP) {
365
- if (commandArgs.length > 0) {
366
- exitWithError("help does not accept arguments.");
367
- }
368
- showHelp(tools);
369
- return;
370
- }
371
-
372
- exitWithError(`Unknown command: ${command}`, ["Use 'heymark help' for usage information."]);
373
- }
374
-
375
- main();