heymark 2.0.0 → 2.0.1

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.ko.md CHANGED
@@ -26,17 +26,17 @@ _프롬프트 문맥에 맞는 Skill을 자동 로드하는 모습입니다._
26
26
  - 단일 소스 관리: Markdown 기반 Skill을 한 곳에서 관리
27
27
  - 자동 형식 변환: 도구별 형식으로 Skill 자동 생성
28
28
  - 선택 동기화: 전체 또는 특정 도구만 동기화
29
- - 샘플 Skill 즉시 사용: Skill 저장소 준비 없이 `npx heymark link --samples`로 바로 시작
29
+ - 샘플 Skill 즉시 사용: heymark 샘플 Skill 저장소로 바로 시작
30
30
 
31
31
  ## Supported Tools
32
32
 
33
- | Tool | Output Format |
34
- | :------------- | :--------------------------------------- |
35
- | Cursor | `.cursor/rules/*.mdc` |
36
- | Claude Code | `.claude/skills/*/SKILL.md` |
37
- | GitHub Copilot | `.github/instructions/*.instructions.md` |
38
- | OpenAI Codex | `.agents/skills/*/SKILL.md` |
39
- | Antigravity | `.agent/skills/*/SKILL.md` |
33
+ | Tool | CLI usage | Output Format |
34
+ | :---------- | :------------ | :--------------------------------------- |
35
+ | Cursor | `cursor` | `.cursor/rules/*.mdc` |
36
+ | Claude Code | `claude-code` | `.claude/skills/*/SKILL.md` |
37
+ | Copilot | `copilot` | `.github/instructions/*.instructions.md` |
38
+ | Codex | `codex` | `.agents/skills/*/SKILL.md` |
39
+ | Antigravity | `antigravity` | `.agent/skills/*/SKILL.md` |
40
40
 
41
41
  ## How to Use
42
42
 
@@ -68,7 +68,7 @@ Skill content...
68
68
  ### Quick Start
69
69
 
70
70
  ```bash
71
- npx heymark link --samples # 샘플 Skill 바로 연결
71
+ npx heymark link https://github.com/MosslandOpenDevs/heymark.git --folder skill-samples
72
72
  npx heymark sync .
73
73
  ```
74
74
 
@@ -79,20 +79,17 @@ _`link`와 `sync` 실행 후, 각 도구가 요구하는 디렉터리에 Skill
79
79
  ### Commands
80
80
 
81
81
  ```bash
82
- npx heymark link --samples # 샘플 Skill 바로 연결
82
+ npx heymark link <GitHub-저장소-URL>
83
+ npx heymark link <GitHub-저장소-URL> --folder <folder-name> # 하위 폴더 사용 시
84
+ npx heymark link <GitHub-저장소-URL> --branch <branch-name> # 다른 브랜치 사용 시
83
85
 
84
- npx heymark link <GitHub-저장소-URL> # Skill 저장소 연결
85
- npx heymark link <GitHub-저장소-URL> --folder <folder-name> # 하위 폴더 사용 시
86
- npx heymark link <GitHub-저장소-URL> --branch <branch-name> # 다른 브랜치 사용 시
86
+ npx heymark sync . # 전체 동기화
87
+ npx heymark sync cursor claude-code # 일부 도구만 동기화
87
88
 
88
- npx heymark sync . # 전체 동기화
89
- npx heymark sync cursor claude # 일부 도구만 동기화
89
+ npx heymark clean . # 전체 정리
90
+ npx heymark clean cursor claude-code # 일부 도구만 정리
90
91
 
91
- npx heymark clean . # 전체 정리
92
- npx heymark clean cursor claude # 일부 도구만 정리
93
-
94
- npx heymark status # 상태 확인 (status 생략 가능)
95
- npx heymark help # 명령어 확인
92
+ npx heymark help
96
93
  ```
97
94
 
98
95
  ## How to Dev
@@ -105,7 +102,7 @@ npx heymark help # 명령어 확인
105
102
 
106
103
  ### Local Development
107
104
 
108
- `How to Use` 섹션의 `npx heymark`를 `node scripts/cli.js`로 바꿔 실행하면 됩니다.
105
+ `How to Use` 섹션의 `npx heymark`를 `node src/index.js`로 바꿔 실행하면 됩니다.
109
106
 
110
107
  ### Release
111
108
 
package/README.md CHANGED
@@ -28,17 +28,17 @@ _This shows Skills being automatically loaded based on prompt context._
28
28
  - Single source management: Manage Markdown-based Skills in one place
29
29
  - Automatic format conversion: Generate Skill outputs in each tool's required format
30
30
  - Selective sync: Sync all tools or only selected tools
31
- - Instant sample usage: Start immediately with `npx heymark link --samples` without preparing a Skill repo
31
+ - Sample skills: Try instantly with the heymark sample skill repo
32
32
 
33
33
  ## Supported Tools
34
34
 
35
- | Tool | Output Format |
36
- | :------------- | :--------------------------------------- |
37
- | Cursor | `.cursor/rules/*.mdc` |
38
- | Claude Code | `.claude/skills/*/SKILL.md` |
39
- | GitHub Copilot | `.github/instructions/*.instructions.md` |
40
- | OpenAI Codex | `.agents/skills/*/SKILL.md` |
41
- | Antigravity | `.agent/skills/*/SKILL.md` |
35
+ | Tool | CLI usage | Output Format |
36
+ | :---------- | :------------ | :--------------------------------------- |
37
+ | Cursor | `cursor` | `.cursor/rules/*.mdc` |
38
+ | Claude Code | `claude-code` | `.claude/skills/*/SKILL.md` |
39
+ | Copilot | `copilot` | `.github/instructions/*.instructions.md` |
40
+ | Codex | `codex` | `.agents/skills/*/SKILL.md` |
41
+ | Antigravity | `antigravity` | `.agent/skills/*/SKILL.md` |
42
42
 
43
43
  ## How to Use
44
44
 
@@ -70,7 +70,7 @@ Skill content...
70
70
  ### Quick Start
71
71
 
72
72
  ```bash
73
- npx heymark link --samples # quickly link sample Skills
73
+ npx heymark link https://github.com/MosslandOpenDevs/heymark.git --folder skill-samples
74
74
  npx heymark sync .
75
75
  ```
76
76
 
@@ -81,20 +81,17 @@ _After `link` and `sync`, Skill files are generated in each tool's expected dire
81
81
  ### Commands
82
82
 
83
83
  ```bash
84
- npx heymark link --samples # quickly link sample Skills
84
+ npx heymark link <GitHub-Repository-URL>
85
+ npx heymark link <GitHub-Repository-URL> --folder <folder-name>
86
+ npx heymark link <GitHub-Repository-URL> --branch <branch-name>
85
87
 
86
- npx heymark link <GitHub-Repository-URL> # link a Skill repository
87
- npx heymark link <GitHub-Repository-URL> --folder <folder-name> # when using a subfolder
88
- npx heymark link <GitHub-Repository-URL> --branch <branch-name> # when using another branch
88
+ npx heymark sync . # sync all tools
89
+ npx heymark sync cursor claude-code # sync selected tools
89
90
 
90
- npx heymark sync . # sync all tools
91
- npx heymark sync cursor claude # sync selected tools
91
+ npx heymark clean . # clean all generated outputs
92
+ npx heymark clean cursor claude-code # clean selected tool outputs
92
93
 
93
- npx heymark clean . # clean all generated outputs
94
- npx heymark clean cursor claude # clean selected tool outputs
95
-
96
- npx heymark status # check status (same as running npx heymark)
97
- npx heymark help # show command help
94
+ npx heymark help
98
95
  ```
99
96
 
100
97
  ## How to Dev
@@ -107,7 +104,7 @@ npx heymark help # show command help
107
104
 
108
105
  ### Local Development
109
106
 
110
- Replace `npx heymark` in the `How to Use` section with `node scripts/cli.js` for local runs.
107
+ Replace `npx heymark` in the `How to Use` section with `node src/index.js` for local runs.
111
108
 
112
109
  ### Release
113
110
 
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "heymark",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Rules & Skills Hub for AI Coding Assistants",
5
5
  "type": "commonjs",
6
- "main": "scripts/cli.js",
7
- "bin": "scripts/cli.js",
6
+ "main": "src/index.js",
7
+ "bin": "src/index.js",
8
8
  "keywords": [
9
9
  "ai-coding-assistant",
10
10
  "ai-agent",
@@ -44,7 +44,7 @@
44
44
  "node": ">=14.0.0"
45
45
  },
46
46
  "files": [
47
- "scripts/"
47
+ "src/"
48
48
  ],
49
49
  "repository": {
50
50
  "type": "git",
package/src/alias.js ADDED
@@ -0,0 +1,41 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const Module = require("module");
4
+
5
+ const PREFIX = "@/";
6
+ const REGISTERED_KEY = "__heymark_alias_registered__";
7
+ const ROOT = __dirname;
8
+
9
+ function getPath(request) {
10
+ if (!request.startsWith(PREFIX)) {
11
+ return null;
12
+ }
13
+
14
+ const rest = request.slice(PREFIX.length);
15
+ const base = path.join(ROOT, rest);
16
+ const candidates = [`${base}.js`, path.join(base, "index.js"), base];
17
+
18
+ const found = candidates.find((c) => fs.existsSync(c));
19
+ return found || base;
20
+ }
21
+
22
+ function registerPath() {
23
+ const g = /** @type {Record<string, unknown>} */ (globalThis);
24
+ if (g[REGISTERED_KEY] === true) {
25
+ return;
26
+ }
27
+
28
+ const original = Module._resolveFilename;
29
+
30
+ Module._resolveFilename = function (request, parent, isMain, options) {
31
+ const resolved = typeof request === "string" ? getPath(request) : null;
32
+ if (resolved) {
33
+ return original.call(this, resolved, parent, isMain, options);
34
+ }
35
+ return original.call(this, request, parent, isMain, options);
36
+ };
37
+
38
+ g[REGISTERED_KEY] = true;
39
+ }
40
+
41
+ registerPath();
@@ -0,0 +1,17 @@
1
+ const { cleaner } = require("@/commands/cleaner");
2
+ const { selectTools } = require("@/commands/select-tools");
3
+ const { readCache } = require("@/skill-repo/cache-folder");
4
+
5
+ function runClean(flags, context) {
6
+ const selectedTools = selectTools(flags, context.tools);
7
+ const { skills } = readCache(context.cwd);
8
+
9
+ const skillNames = skills.map((s) => s.name);
10
+ const cleanedCount = cleaner(context.tools, selectedTools, skillNames, context.cwd);
11
+
12
+ console.log(`[Done] ${cleanedCount} tools cleaned.`);
13
+ }
14
+
15
+ module.exports = {
16
+ runClean,
17
+ };
@@ -0,0 +1,27 @@
1
+ function cleaner(tools, selectedTools, skillNames, cwd) {
2
+ let headerPrinted = false;
3
+ let cleanedCount = 0;
4
+
5
+ for (const toolKey of selectedTools) {
6
+ const cleanedPaths = tools[toolKey].clean(skillNames, cwd);
7
+ if (cleanedPaths.length === 0) continue;
8
+
9
+ if (!headerPrinted) {
10
+ console.log("[Clean]");
11
+ headerPrinted = true;
12
+ }
13
+
14
+ cleanedPaths.forEach((p) => console.log(` Removed: ${p}`));
15
+ cleanedCount++;
16
+ }
17
+
18
+ if (headerPrinted) {
19
+ console.log("");
20
+ }
21
+
22
+ return cleanedCount;
23
+ }
24
+
25
+ module.exports = {
26
+ cleaner,
27
+ };
@@ -0,0 +1,14 @@
1
+ const COMMAND_LINK = "link";
2
+ const COMMAND_SYNC = "sync";
3
+ const COMMAND_CLEAN = "clean";
4
+ const COMMAND_HELP = "help";
5
+
6
+ const LATEST_VERSION_COMMAND = "npx heymark@latest";
7
+
8
+ module.exports = {
9
+ COMMAND_LINK,
10
+ COMMAND_SYNC,
11
+ COMMAND_CLEAN,
12
+ COMMAND_HELP,
13
+ LATEST_VERSION_COMMAND,
14
+ };
@@ -0,0 +1,34 @@
1
+ const { LATEST_VERSION_COMMAND } = require("@/commands/constants");
2
+
3
+ function runHelp(flags, context) {
4
+ if (flags.length > 0) {
5
+ console.error(`[Error] Unknown: ${flags.join(", ")}. help takes no arguments.`);
6
+ process.exit(1);
7
+ }
8
+
9
+ const toolLines = Object.entries(context.tools)
10
+ .map(([key, tool]) => ` ${key.padEnd(14)} ${tool.output}`)
11
+ .join("\n");
12
+
13
+ console.log(`
14
+ Usage:
15
+ heymark link <repo-url>
16
+ heymark sync .
17
+ heymark sync <tool1> <tool2> ...
18
+ heymark clean .
19
+ heymark clean <tool1> <tool2> ...
20
+
21
+ Link flags:
22
+ --branch | -b
23
+ --folder | -f
24
+
25
+ Supported tools:
26
+ ${toolLines}
27
+
28
+ Update: ${LATEST_VERSION_COMMAND}
29
+ `);
30
+ }
31
+
32
+ module.exports = {
33
+ runHelp,
34
+ };
@@ -0,0 +1,18 @@
1
+ const key = "branch";
2
+ const FLAG = "--branch";
3
+ const SHORT_FLAG = "-b";
4
+
5
+ function is(flag) {
6
+ return flag === FLAG || flag === SHORT_FLAG;
7
+ }
8
+
9
+ function parse(flags, index) {
10
+ const value = flags[index + 1];
11
+ if (!value) {
12
+ console.error("[Error] --branch needs a value.");
13
+ process.exit(1);
14
+ }
15
+ return { value: value.trim(), advance: 1 };
16
+ }
17
+
18
+ module.exports = { key, is, parse };
@@ -0,0 +1,18 @@
1
+ const key = "folder";
2
+ const FLAG = "--folder";
3
+ const SHORT_FLAG = "-f";
4
+
5
+ function is(flag) {
6
+ return flag === FLAG || flag === SHORT_FLAG;
7
+ }
8
+
9
+ function parse(flags, index) {
10
+ const value = flags[index + 1];
11
+ if (!value) {
12
+ console.error("[Error] --folder needs a value.");
13
+ process.exit(1);
14
+ }
15
+ return { value: value.trim(), advance: 1 };
16
+ }
17
+
18
+ module.exports = { key, is, parse };
@@ -0,0 +1,58 @@
1
+ const path = require("path");
2
+ const { writeConfig } = require("@/skill-repo/config-file");
3
+ const { SKILL_REPO_DEFAULT_BRANCH } = require("@/skill-repo/constants");
4
+ const branch = require("@/commands/link/flags/branch");
5
+ const folder = require("@/commands/link/flags/folder");
6
+
7
+ function parseFlags(flags, handlers) {
8
+ const result = {};
9
+ for (let i = 0; i < flags.length; i++) {
10
+ const flag = flags[i];
11
+ const handler = handlers.find((h) => h.is(flag));
12
+ if (handler) {
13
+ const parsed = handler.parse(flags, i);
14
+ result[handler.key] = parsed.value;
15
+ i += parsed.advance;
16
+ continue;
17
+ }
18
+ console.error(`[Error] Unknown flag: ${flag}`);
19
+ process.exit(1);
20
+ }
21
+ return result;
22
+ }
23
+
24
+ function parseConfig(flags) {
25
+ const repoUrl = flags[0];
26
+ if (!repoUrl || repoUrl.startsWith("--")) {
27
+ console.error(
28
+ "[Error] Provide a repo URL. Example: heymark link https://github.com/org/repo.git"
29
+ );
30
+ process.exit(1);
31
+ }
32
+
33
+ const parsed = parseFlags(flags.slice(1), [branch, folder]);
34
+
35
+ return {
36
+ repoUrl: repoUrl.trim(),
37
+ branch: parsed.branch || SKILL_REPO_DEFAULT_BRANCH,
38
+ folder: parsed.folder || "",
39
+ };
40
+ }
41
+
42
+ function runLink(flags, context) {
43
+ const config = parseConfig(flags);
44
+ const configPath = writeConfig(context.cwd, config);
45
+
46
+ console.log(`[Link] Saved to ${path.relative(context.cwd, configPath) || configPath}`);
47
+ console.log(` repo: ${config.repoUrl}`);
48
+ if (config.branch !== SKILL_REPO_DEFAULT_BRANCH) {
49
+ console.log(` branch: ${config.branch}`);
50
+ }
51
+ if (config.folder) {
52
+ console.log(` folder: ${config.folder}`);
53
+ }
54
+ }
55
+
56
+ module.exports = {
57
+ runLink,
58
+ };
@@ -0,0 +1,35 @@
1
+ const ALL_TOOLS_TOKEN = ".";
2
+
3
+ function selectTools(flags, availableTools) {
4
+ const availableToolKeys = Object.keys(availableTools);
5
+ if (flags.length === 0) {
6
+ return availableToolKeys;
7
+ }
8
+
9
+ if (flags.some((tool) => tool.includes(","))) {
10
+ console.error("[Error] Use spaces between tools, not commas.");
11
+ process.exit(1);
12
+ }
13
+
14
+ if (flags.includes(ALL_TOOLS_TOKEN)) {
15
+ if (flags.length > 1) {
16
+ console.error("[Error] Use '.' alone for all tools.");
17
+ process.exit(1);
18
+ }
19
+ return availableToolKeys;
20
+ }
21
+
22
+ const invalid = flags.filter((tool) => !availableTools[tool]);
23
+ if (invalid.length > 0) {
24
+ console.error(
25
+ `[Error] Unknown: ${invalid.join(", ")}. Available: ${availableToolKeys.join(", ")}`
26
+ );
27
+ process.exit(1);
28
+ }
29
+
30
+ return Array.from(new Set(flags));
31
+ }
32
+
33
+ module.exports = {
34
+ selectTools,
35
+ };
@@ -0,0 +1,35 @@
1
+ const { cleaner } = require("@/commands/cleaner");
2
+ const { selectTools } = require("@/commands/select-tools");
3
+ const { readCache } = require("@/skill-repo/cache-folder");
4
+ const { readConfig } = require("@/skill-repo/config-file");
5
+ const { SKILL_REPO_DEFAULT_BRANCH } = require("@/skill-repo/constants");
6
+
7
+ function runSync(flags, context) {
8
+ const selectedTools = selectTools(flags, context.tools);
9
+ const { skills } = readCache(context.cwd);
10
+ const config = readConfig(context.cwd);
11
+
12
+ console.log("[Sync]");
13
+ if (config) {
14
+ console.log(` repo: ${config.repoUrl}`);
15
+ if (config.folder) console.log(` folder: ${config.folder}`);
16
+ if (config.branch !== SKILL_REPO_DEFAULT_BRANCH) console.log(` branch: ${config.branch}`);
17
+ }
18
+ console.log("");
19
+
20
+ const skillNames = skills.map((s) => s.name);
21
+ cleaner(context.tools, selectedTools, skillNames, context.cwd);
22
+
23
+ for (const toolKey of selectedTools) {
24
+ const tool = context.tools[toolKey];
25
+ const count = tool.generate(skills, context.cwd);
26
+ console.log(` ${tool.name.padEnd(16)} -> ${tool.output} (${count} skills)`);
27
+ }
28
+
29
+ console.log("");
30
+ console.log(`[Done] ${selectedTools.length} tools synced.`);
31
+ }
32
+
33
+ module.exports = {
34
+ runSync,
35
+ };
package/src/index.js ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+
3
+ require("./alias.js");
4
+
5
+ const { COMMAND_LINK, COMMAND_SYNC, COMMAND_CLEAN, COMMAND_HELP } = require("@/commands/constants");
6
+ const { runLink } = require("@/commands/link");
7
+ const { runSync } = require("@/commands/sync");
8
+ const { runClean } = require("@/commands/clean");
9
+ const { runHelp } = require("@/commands/help");
10
+ const { loadTools } = require("@/tools/loader");
11
+
12
+ function main() {
13
+ const context = {
14
+ cwd: process.cwd(),
15
+ tools: loadTools(),
16
+ };
17
+
18
+ const args = process.argv.slice(2);
19
+
20
+ if (args.length === 0) {
21
+ runHelp([], context);
22
+ return;
23
+ }
24
+
25
+ const command = args[0];
26
+ const flags = args.slice(1);
27
+
28
+ if (command === COMMAND_LINK) {
29
+ runLink(flags, context);
30
+ return;
31
+ }
32
+
33
+ if (command === COMMAND_SYNC) {
34
+ runSync(flags, context);
35
+ return;
36
+ }
37
+
38
+ if (command === COMMAND_CLEAN) {
39
+ runClean(flags, context);
40
+ return;
41
+ }
42
+
43
+ if (command === COMMAND_HELP) {
44
+ runHelp(flags, context);
45
+ return;
46
+ }
47
+
48
+ console.error(`[Error] Unknown command: ${command}. Run: heymark help`);
49
+ process.exit(1);
50
+ }
51
+
52
+ main();
@@ -0,0 +1,77 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execSync } = require("child_process");
4
+ const { HEYMARK, SKILL_REPO_DEFAULT_BRANCH } = require("@/skill-repo/constants");
5
+ const { readConfig } = require("@/skill-repo/config-file");
6
+ const { readSkillFiles } = require("@/skill-repo/skill-file-parser");
7
+
8
+ function getCloneFolderPath(cwd, repoUrl) {
9
+ const name = repoUrl.trim().split("/").pop() || "repo";
10
+ const dirName = name.endsWith(".git") ? name.slice(0, -4) : name;
11
+ return path.join(cwd, HEYMARK.DIR, HEYMARK.CACHE_DIR, dirName);
12
+ }
13
+
14
+ function gitClone(cwd, dir, branch, repoUrl) {
15
+ execSync(`git clone --depth 1 --branch "${branch}" "${repoUrl}" "${dir}"`, {
16
+ stdio: "inherit",
17
+ cwd,
18
+ });
19
+ }
20
+
21
+ function gitPull(dir, branch) {
22
+ execSync(`git fetch origin && git checkout --quiet . && git pull --quiet origin "${branch}"`, {
23
+ stdio: "pipe",
24
+ cwd: dir,
25
+ });
26
+ }
27
+
28
+ function writeCache(cwd) {
29
+ const config = readConfig(cwd);
30
+ if (!config) {
31
+ console.error(
32
+ `[Error] Not linked. Run: heymark link <repo-url> (config: ${HEYMARK.DIR}/${HEYMARK.CONFIG_FILE})`
33
+ );
34
+ process.exit(1);
35
+ }
36
+
37
+ const branch = config.branch || SKILL_REPO_DEFAULT_BRANCH;
38
+ const cacheBase = path.join(cwd, HEYMARK.DIR, HEYMARK.CACHE_DIR);
39
+ const cloneFolderPath = getCloneFolderPath(cwd, config.repoUrl);
40
+
41
+ if (!fs.existsSync(cloneFolderPath)) {
42
+ fs.mkdirSync(cacheBase, { recursive: true });
43
+ try {
44
+ gitClone(cwd, cloneFolderPath, branch, config.repoUrl);
45
+ } catch {
46
+ console.error("[Error] Clone failed. Check repo access (SSH or token).");
47
+ process.exit(1);
48
+ }
49
+ } else {
50
+ try {
51
+ gitPull(cloneFolderPath, branch);
52
+ } catch {
53
+ // Continue with cached clone when fetch/pull fails.
54
+ }
55
+ }
56
+
57
+ return { config, cloneFolderPath };
58
+ }
59
+
60
+ function readCache(cwd) {
61
+ const { config, cloneFolderPath } = writeCache(cwd);
62
+
63
+ const folder = config.folder || "";
64
+ const skillsFolderPath = folder ? path.join(cloneFolderPath, folder) : cloneFolderPath;
65
+ if (!fs.existsSync(skillsFolderPath) || !fs.statSync(skillsFolderPath).isDirectory()) {
66
+ console.error(`[Error] Folder not found in repo: ${folder || "(root)"}`);
67
+ process.exit(1);
68
+ }
69
+
70
+ const skills = readSkillFiles(skillsFolderPath);
71
+
72
+ return { config, skillsFolderPath, skills };
73
+ }
74
+
75
+ module.exports = {
76
+ readCache,
77
+ };