aicoding-rules 0.2.0 → 0.3.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.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # aicoding-rules
2
2
 
3
- A CLI tool to download and install AI coding rules for various AI assistants.
3
+ A CLI tool to download and install AI coding rules and skills for various AI assistants.
4
4
 
5
5
  ## Supported Tools
6
6
 
@@ -58,6 +58,20 @@ Install all rules at once:
58
58
  npx aicoding-rules all
59
59
  ```
60
60
 
61
+ ### Skills
62
+
63
+ Install AI coding skills with interactive selection:
64
+
65
+ ```bash
66
+ npx aicoding-rules skills
67
+ ```
68
+
69
+ Use `--replace` flag to remove and reinstall existing skill folders:
70
+
71
+ ```bash
72
+ npx aicoding-rules skills --replace
73
+ ```
74
+
61
75
  ## Authentication
62
76
 
63
77
  The tool uses Plus4U OIDC authentication. On first run, a browser window will open for you to log in. The authentication token is cached for the session.
@@ -93,3 +107,25 @@ Rules are extracted to the current working directory. Run the command from the r
93
107
 
94
108
  - Node.js >= 18.0.0
95
109
  - Plus4U account with access to the rules repository
110
+
111
+ ## Changelog
112
+
113
+ ### 0.3.0 (2026-01-29)
114
+
115
+ - Added `--replace` option for skills command to completely remove and reinstall existing skill folders before extracting
116
+ - Improved extraction logic to detect top-level folders in zip archives
117
+
118
+ ### 0.2.0 (2026-01-27)
119
+
120
+ - Added skills support with interactive selection
121
+ - New `skills` command for downloading AI coding skills
122
+ - Skills can be installed to custom target directories
123
+
124
+ ### 0.1.0 (2026-01-09)
125
+
126
+ - Initial release
127
+ - Support for Cursor, Claude, Antigravity, and Copilot rules
128
+ - Interactive mode with checkbox selection
129
+ - Direct commands for individual rule installation
130
+ - Plus4U OIDC authentication
131
+ - Custom configuration via `~/.aicoding-rules.json`
package/bin/cli.js CHANGED
@@ -8,6 +8,17 @@ import { selectRules, selectSkills } from "../src/interactive.js";
8
8
  const config = loadConfig();
9
9
  const targetDir = process.cwd();
10
10
 
11
+ function wrapAction(fn) {
12
+ return async (...args) => {
13
+ try {
14
+ await fn(...args);
15
+ } catch (err) {
16
+ console.error(`Error: ${err.message}`);
17
+ process.exit(1);
18
+ }
19
+ };
20
+ }
21
+
11
22
  const program = new Command();
12
23
 
13
24
  program
@@ -18,53 +29,54 @@ program
18
29
  program
19
30
  .command("cursor")
20
31
  .description("Install Cursor rules")
21
- .action(async () => {
32
+ .action(wrapAction(async () => {
22
33
  await downloadAndExtract(config, "cursor", targetDir);
23
- });
34
+ }));
24
35
 
25
36
  program
26
37
  .command("claude")
27
38
  .description("Install Claude rules")
28
- .action(async () => {
39
+ .action(wrapAction(async () => {
29
40
  await downloadAndExtract(config, "claude", targetDir);
30
- });
41
+ }));
31
42
 
32
43
  program
33
44
  .command("antigravity")
34
45
  .description("Install Antigravity rules")
35
- .action(async () => {
46
+ .action(wrapAction(async () => {
36
47
  await downloadAndExtract(config, "antigravity", targetDir);
37
- });
48
+ }));
38
49
 
39
50
  program
40
51
  .command("copilot")
41
52
  .description("Install Copilot prompts")
42
- .action(async () => {
53
+ .action(wrapAction(async () => {
43
54
  await downloadAndExtract(config, "copilot", targetDir);
44
- });
55
+ }));
45
56
 
46
57
  program
47
58
  .command("all")
48
59
  .description("Install all rules")
49
- .action(async () => {
60
+ .action(wrapAction(async () => {
50
61
  const allKeys = getRuleKeys(config);
51
62
  await downloadMultiple(config, allKeys, targetDir);
52
- });
63
+ }));
53
64
 
54
65
  program
55
66
  .command("skills")
56
67
  .description("Install AI coding skills (interactive selection)")
57
- .action(async () => {
68
+ .option("--replace", "Remove existing folders that match the zip contents before extracting (only those folders; other skills stay intact)")
69
+ .action(wrapAction(async (opts) => {
58
70
  const { selectedSkills, targetDir: skillsDir } = await selectSkills(config);
59
71
  if (selectedSkills.length > 0) {
60
- await downloadMultipleSkills(config, selectedSkills, skillsDir);
72
+ await downloadMultipleSkills(config, selectedSkills, skillsDir, { replaceExtracted: opts.replace });
61
73
  }
62
- });
74
+ }));
63
75
 
64
76
  program
65
- .action(async () => {
77
+ .action(wrapAction(async () => {
66
78
  const selectedKeys = await selectRules(config);
67
79
  await downloadMultiple(config, selectedKeys, targetDir);
68
- });
80
+ }));
69
81
 
70
82
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicoding-rules",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Download and install AI coding rules for Cursor, Claude, Antigravity, and Copilot",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/download.js CHANGED
@@ -1,10 +1,45 @@
1
1
  import AdmZip from "adm-zip";
2
- import { mkdirSync } from "fs";
2
+ import { mkdirSync, rmSync, existsSync } from "fs";
3
+ import { join } from "path";
3
4
  import { getAuthenticatedClient } from "./auth.js";
4
5
 
6
+ const OVERWRITE_FILES = true;
7
+
8
+ function getZipTopLevelNames(zip) {
9
+ const entries = zip.getEntries();
10
+ const topLevel = new Set();
11
+ for (const entry of entries) {
12
+ const name = entry.entryName.replace(/^\/+/, "");
13
+ if (!name) continue;
14
+
15
+ const firstSegment = name.split("/")[0];
16
+ if (firstSegment && firstSegment !== ".") {
17
+ topLevel.add(firstSegment);
18
+ }
19
+ }
20
+ return [...topLevel];
21
+ }
22
+
23
+ function removePathsInDir(targetDir, pathNames) {
24
+ for (const name of pathNames) {
25
+ const fullPath = join(targetDir, name);
26
+ if (existsSync(fullPath)) {
27
+ try {
28
+ rmSync(fullPath, { recursive: true, force: true });
29
+ } catch (err) {
30
+ throw new Error(`Failed to remove ${fullPath}: ${err.message}`);
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ function extractZip(zip, outputPath) {
37
+ zip.extractAllTo(outputPath, OVERWRITE_FILES);
38
+ }
39
+
5
40
  function extractZipBuffer(buffer, outputPath) {
6
41
  const zip = new AdmZip(buffer);
7
- zip.extractAllTo(outputPath, true);
42
+ zip.extractAllTo(outputPath, OVERWRITE_FILES);
8
43
  }
9
44
 
10
45
  function ensureDir(dirPath) {
@@ -44,7 +79,8 @@ export async function downloadMultiple(config, ruleKeys, targetDir) {
44
79
  }
45
80
  }
46
81
 
47
- export async function downloadSkill(config, skillKey, targetDir) {
82
+ export async function downloadSkill(config, skillKey, targetDir, options = {}) {
83
+ const { replaceExtracted = false } = options;
48
84
  const skill = config.skills?.[skillKey];
49
85
  if (!skill) {
50
86
  throw new Error(`Unknown skill: ${skillKey}`);
@@ -71,15 +107,21 @@ export async function downloadSkill(config, skillKey, targetDir) {
71
107
 
72
108
  ensureDir(targetDir);
73
109
 
74
- const attachmentBuffer = response.body;
75
- extractZipBuffer(attachmentBuffer, targetDir);
110
+ const zip = new AdmZip(response.body);
111
+ if (replaceExtracted) {
112
+ const topLevelNames = getZipTopLevelNames(zip);
113
+ if (topLevelNames.length > 0) {
114
+ console.log(` Replacing: ${topLevelNames.join(", ")}`);
115
+ }
116
+ removePathsInDir(targetDir, topLevelNames);
117
+ }
118
+ extractZip(zip, targetDir);
76
119
 
77
120
  console.log(`${skill.name} installed to ${targetDir}`);
78
121
  }
79
122
 
80
- export async function downloadMultipleSkills(config, skillKeys, targetDir) {
81
- ensureDir(targetDir);
123
+ export async function downloadMultipleSkills(config, skillKeys, targetDir, options = {}) {
82
124
  for (const key of skillKeys) {
83
- await downloadSkill(config, key, targetDir);
125
+ await downloadSkill(config, key, targetDir, options);
84
126
  }
85
127
  }
@@ -71,8 +71,10 @@ export async function selectSkillsLocation() {
71
71
  choices: [
72
72
  { name: "~/.claude/skills (Claude global skills)", value: "claude-global" },
73
73
  { name: "~/.cursor/skills (Cursor global skills)", value: "cursor-global" },
74
+ { name: "~/.codex/skills (Codex global skills)", value: "codex-global" },
74
75
  { name: "Current directory - .claude/skills", value: "claude-local" },
75
- { name: "Current directory - .cursor/skills", value: "cursor-local" }
76
+ { name: "Current directory - .cursor/skills", value: "cursor-local" },
77
+ { name: "Current directory - .codex/skills", value: "codex-local" }
76
78
  ]
77
79
  }
78
80
  ]);
@@ -89,10 +91,14 @@ function resolveSkillsPath(location) {
89
91
  return join(home, ".claude", "skills");
90
92
  case "cursor-global":
91
93
  return join(home, ".cursor", "skills");
94
+ case "codex-global":
95
+ return join(home, ".codex", "skills");
92
96
  case "claude-local":
93
97
  return join(cwd, ".claude", "skills");
94
98
  case "cursor-local":
95
99
  return join(cwd, ".cursor", "skills");
100
+ case "codex-local":
101
+ return join(cwd, ".codex", "skills");
96
102
  default:
97
103
  return cwd;
98
104
  }