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 +37 -1
- package/bin/cli.js +27 -15
- package/package.json +1 -1
- package/src/download.js +50 -8
- package/src/interactive.js +7 -1
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
|
-
.
|
|
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
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,
|
|
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
|
|
75
|
-
|
|
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
|
}
|
package/src/interactive.js
CHANGED
|
@@ -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
|
}
|