aicoding-rules 0.2.0 → 0.3.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.
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.0",
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
  }