aicoding-rules 0.1.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 +37 -15
- package/package.json +1 -1
- package/src/config.js +11 -0
- package/src/download.js +88 -1
- package/src/interactive.js +72 -1
package/bin/cli.js
CHANGED
|
@@ -2,59 +2,81 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { loadConfig, getRuleKeys } from "../src/config.js";
|
|
5
|
-
import { downloadAndExtract, downloadMultiple } from "../src/download.js";
|
|
6
|
-
import { selectRules } from "../src/interactive.js";
|
|
5
|
+
import { downloadAndExtract, downloadMultiple, downloadMultipleSkills } from "../src/download.js";
|
|
6
|
+
import { selectRules, selectSkills } from "../src/interactive.js";
|
|
7
7
|
|
|
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
|
|
14
25
|
.name("aicoding-rules")
|
|
15
|
-
.description("Download and install AI coding rules for Cursor, Claude, Antigravity, and Copilot")
|
|
26
|
+
.description("Download and install AI coding rules and skills for Cursor, Claude, Antigravity, and Copilot")
|
|
16
27
|
.version("1.0.0");
|
|
17
28
|
|
|
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
|
+
}));
|
|
64
|
+
|
|
65
|
+
program
|
|
66
|
+
.command("skills")
|
|
67
|
+
.description("Install AI coding skills (interactive selection)")
|
|
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) => {
|
|
70
|
+
const { selectedSkills, targetDir: skillsDir } = await selectSkills(config);
|
|
71
|
+
if (selectedSkills.length > 0) {
|
|
72
|
+
await downloadMultipleSkills(config, selectedSkills, skillsDir, { replaceExtracted: opts.replace });
|
|
73
|
+
}
|
|
74
|
+
}));
|
|
53
75
|
|
|
54
76
|
program
|
|
55
|
-
.action(async () => {
|
|
77
|
+
.action(wrapAction(async () => {
|
|
56
78
|
const selectedKeys = await selectRules(config);
|
|
57
79
|
await downloadMultiple(config, selectedKeys, targetDir);
|
|
58
|
-
});
|
|
80
|
+
}));
|
|
59
81
|
|
|
60
82
|
program.parse();
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -12,6 +12,9 @@ const DEFAULT_CONFIG = {
|
|
|
12
12
|
claude: { fileOid: "68872b563e3eb464f2214263", name: "Claude rules" },
|
|
13
13
|
antigravity: { fileOid: "69202dcfacf31067955cfc1c", name: "Antigravity rules" },
|
|
14
14
|
copilot: { fileOid: "67eab73e899a1711f6cc106f", name: "Copilot prompts" }
|
|
15
|
+
},
|
|
16
|
+
skills: {
|
|
17
|
+
uu_skills: { fileOid: "69786bfc45c7ce292846341e", name: "UU Skills" }
|
|
15
18
|
}
|
|
16
19
|
};
|
|
17
20
|
|
|
@@ -50,3 +53,11 @@ export function getRuleKeys(config) {
|
|
|
50
53
|
export function getRule(config, key) {
|
|
51
54
|
return config.rules[key];
|
|
52
55
|
}
|
|
56
|
+
|
|
57
|
+
export function getSkillKeys(config) {
|
|
58
|
+
return Object.keys(config.skills || {});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getSkill(config, key) {
|
|
62
|
+
return config.skills?.[key];
|
|
63
|
+
}
|
package/src/download.js
CHANGED
|
@@ -1,9 +1,49 @@
|
|
|
1
1
|
import AdmZip from "adm-zip";
|
|
2
|
+
import { mkdirSync, rmSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
2
4
|
import { getAuthenticatedClient } from "./auth.js";
|
|
3
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
|
+
|
|
4
40
|
function extractZipBuffer(buffer, outputPath) {
|
|
5
41
|
const zip = new AdmZip(buffer);
|
|
6
|
-
zip.extractAllTo(outputPath,
|
|
42
|
+
zip.extractAllTo(outputPath, OVERWRITE_FILES);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ensureDir(dirPath) {
|
|
46
|
+
mkdirSync(dirPath, { recursive: true });
|
|
7
47
|
}
|
|
8
48
|
|
|
9
49
|
export async function downloadAndExtract(config, ruleKey, targetDir) {
|
|
@@ -38,3 +78,50 @@ export async function downloadMultiple(config, ruleKeys, targetDir) {
|
|
|
38
78
|
await downloadAndExtract(config, key, targetDir);
|
|
39
79
|
}
|
|
40
80
|
}
|
|
81
|
+
|
|
82
|
+
export async function downloadSkill(config, skillKey, targetDir, options = {}) {
|
|
83
|
+
const { replaceExtracted = false } = options;
|
|
84
|
+
const skill = config.skills?.[skillKey];
|
|
85
|
+
if (!skill) {
|
|
86
|
+
throw new Error(`Unknown skill: ${skillKey}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!skill.fileOid) {
|
|
90
|
+
throw new Error(`Skill ${skillKey} has no fileOid configured. Please update your config.`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const appClient = await getAuthenticatedClient();
|
|
94
|
+
|
|
95
|
+
const url = `${config.baseUrl}/document/ebc/file/getDataByOid?oid=${config.documentOid}&uuEbcData.fileOid=${skill.fileOid}`;
|
|
96
|
+
|
|
97
|
+
console.log(`Downloading ${skill.name}...`);
|
|
98
|
+
|
|
99
|
+
const response = await appClient.exchange(
|
|
100
|
+
url,
|
|
101
|
+
"get",
|
|
102
|
+
{},
|
|
103
|
+
{},
|
|
104
|
+
0,
|
|
105
|
+
{ responseType: "buffer" }
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
ensureDir(targetDir);
|
|
109
|
+
|
|
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);
|
|
119
|
+
|
|
120
|
+
console.log(`${skill.name} installed to ${targetDir}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function downloadMultipleSkills(config, skillKeys, targetDir, options = {}) {
|
|
124
|
+
for (const key of skillKeys) {
|
|
125
|
+
await downloadSkill(config, key, targetDir, options);
|
|
126
|
+
}
|
|
127
|
+
}
|
package/src/interactive.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
|
-
import {
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { getRuleKeys, getRule, getSkillKeys, getSkill } from "./config.js";
|
|
3
5
|
|
|
4
6
|
export async function selectRules(config) {
|
|
5
7
|
const ruleKeys = getRuleKeys(config);
|
|
@@ -26,3 +28,72 @@ export async function selectRules(config) {
|
|
|
26
28
|
|
|
27
29
|
return selectedRules;
|
|
28
30
|
}
|
|
31
|
+
|
|
32
|
+
export async function selectSkills(config) {
|
|
33
|
+
const skillKeys = getSkillKeys(config);
|
|
34
|
+
|
|
35
|
+
if (skillKeys.length === 0) {
|
|
36
|
+
console.log("No skills available in configuration.");
|
|
37
|
+
return { selectedSkills: [], targetDir: null };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const choices = skillKeys.map(key => ({
|
|
41
|
+
name: getSkill(config, key).name,
|
|
42
|
+
value: key
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
const { selectedSkills } = await inquirer.prompt([
|
|
46
|
+
{
|
|
47
|
+
type: "checkbox",
|
|
48
|
+
name: "selectedSkills",
|
|
49
|
+
message: "Select skills to install:",
|
|
50
|
+
choices,
|
|
51
|
+
validate: (answer) => {
|
|
52
|
+
if (answer.length === 0) {
|
|
53
|
+
return "You must select at least one skill.";
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const targetDir = await selectSkillsLocation();
|
|
61
|
+
|
|
62
|
+
return { selectedSkills, targetDir };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function selectSkillsLocation() {
|
|
66
|
+
const { location } = await inquirer.prompt([
|
|
67
|
+
{
|
|
68
|
+
type: "list",
|
|
69
|
+
name: "location",
|
|
70
|
+
message: "Where do you want to extract the skills?",
|
|
71
|
+
choices: [
|
|
72
|
+
{ name: "~/.claude/skills (Claude global skills)", value: "claude-global" },
|
|
73
|
+
{ name: "~/.cursor/skills (Cursor global skills)", value: "cursor-global" },
|
|
74
|
+
{ name: "Current directory - .claude/skills", value: "claude-local" },
|
|
75
|
+
{ name: "Current directory - .cursor/skills", value: "cursor-local" }
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
return resolveSkillsPath(location);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function resolveSkillsPath(location) {
|
|
84
|
+
const cwd = process.cwd();
|
|
85
|
+
const home = homedir();
|
|
86
|
+
|
|
87
|
+
switch (location) {
|
|
88
|
+
case "claude-global":
|
|
89
|
+
return join(home, ".claude", "skills");
|
|
90
|
+
case "cursor-global":
|
|
91
|
+
return join(home, ".cursor", "skills");
|
|
92
|
+
case "claude-local":
|
|
93
|
+
return join(cwd, ".claude", "skills");
|
|
94
|
+
case "cursor-local":
|
|
95
|
+
return join(cwd, ".cursor", "skills");
|
|
96
|
+
default:
|
|
97
|
+
return cwd;
|
|
98
|
+
}
|
|
99
|
+
}
|