Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

developer-stack-skills

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

developer-stack-skills - npm Package Compare versions

Comparing version
1.0.1
to
1.1.0
+19
bin/developer-stack-skills.js
#!/usr/bin/env node
const { parseArgs, printHelp, runInstall } = require("../lib/installer");
async function main() {
const args = parseArgs(process.argv.slice(2));
if (args.command === "install") {
await runInstall(args);
return;
}
printHelp();
}
main().catch((error) => {
console.error(`[developer-stack-skills] install failed: ${error.message}`);
process.exitCode = 1;
});
# Changelog
## 1.1.0 - 2026-05-15
Added:
- `developer-stack-skills install` CLI
- interactive install flow for agent selection, install mode, and target directory
- automatic OS detection
- `copy` and `symlink` install modes
- install logs for package version, OS, source directory, install directory, installed skills, and updated config files
- project-level agent config generation for `Claude`, `Cursor`, `Cline`, `Roocode`, and `GitHub Copilot`
- basic Node test coverage for installer argument parsing and config helpers
Changed:
- README now documents interactive and non-interactive installer usage
- published package files now include `bin/`, `lib/`, and `CHANGELOG.md`
- package description updated to mention installer CLI
Notes:
- installer is manual by design; it does not auto-run during `npm install`
const fsp = require("fs/promises");
const path = require("path");
const readline = require("readline");
const PACKAGE_NAME = "developer-stack-skills";
const MANAGED_START = "developer-stack-skills:start";
const MANAGED_END = "developer-stack-skills:end";
const AGENTS = ["all", "claude", "cursor", "cline", "roocode", "copilot"];
const MODES = ["copy", "symlink"];
const SKILLS = [
"java-spring",
"python-backend",
"frontend",
"testing",
"project-conventions",
];
function detectPlatform() {
switch (process.platform) {
case "win32":
return "windows";
case "darwin":
return "macos";
default:
return "linux";
}
}
function parseArgs(argv) {
const args = {
command: argv[0] || "help",
agent: null,
mode: null,
projectDir: null,
yes: false,
};
for (let index = 1; index < argv.length; index += 1) {
const token = argv[index];
if (token === "--yes" || token === "-y") {
args.yes = true;
continue;
}
if (token.startsWith("--agent=")) {
args.agent = token.slice("--agent=".length);
continue;
}
if (token === "--agent") {
args.agent = argv[index + 1] || null;
index += 1;
continue;
}
if (token.startsWith("--mode=")) {
args.mode = token.slice("--mode=".length);
continue;
}
if (token === "--mode") {
args.mode = argv[index + 1] || null;
index += 1;
continue;
}
if (token.startsWith("--dir=")) {
args.projectDir = token.slice("--dir=".length);
continue;
}
if (token === "--dir") {
args.projectDir = argv[index + 1] || null;
index += 1;
}
}
return args;
}
function normalizeAgent(agent) {
if (!agent) {
return null;
}
const normalized = agent.trim().toLowerCase();
if (normalized === "roo") {
return "roocode";
}
if (normalized === "github-copilot") {
return "copilot";
}
return normalized;
}
function normalizeMode(mode) {
return mode ? mode.trim().toLowerCase() : null;
}
function printHelp() {
console.log(`${PACKAGE_NAME} installer`);
console.log("");
console.log("Usage:");
console.log(" developer-stack-skills install");
console.log(" developer-stack-skills install --agent all --mode symlink --dir .");
console.log(" npx developer-stack-skills install --agent cline --mode copy");
console.log("");
console.log("Options:");
console.log(" --agent <all|claude|cursor|cline|roocode|copilot>");
console.log(" --mode <copy|symlink>");
console.log(" --dir <project-directory>");
console.log(" --yes");
}
function createPrompt() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return {
ask(question) {
return new Promise((resolve) => {
rl.question(question, (answer) => resolve(answer.trim()));
});
},
close() {
rl.close();
},
};
}
async function chooseValue(prompt, question, options, fallback) {
const answer = await prompt.ask(question);
const normalized = answer ? answer.trim().toLowerCase() : fallback;
if (!normalized) {
throw new Error(`Missing required value. Allowed: ${options.join(", ")}`);
}
if (!options.includes(normalized)) {
throw new Error(`Invalid value "${answer}". Allowed: ${options.join(", ")}`);
}
return normalized;
}
function getPackageRoot() {
return path.resolve(__dirname, "..");
}
function getVersion() {
const packageJson = require(path.join(getPackageRoot(), "package.json"));
return packageJson.version;
}
function detectPackageInstallType(packageRoot, projectDir) {
const normalizedPackageRoot = path.resolve(packageRoot);
const localNodeModulesRoot = path.resolve(projectDir, "node_modules", PACKAGE_NAME);
return normalizedPackageRoot === localNodeModulesRoot ? "local" : "global";
}
function getInstallRoot(projectDir) {
return path.join(projectDir, ".ai-skills", PACKAGE_NAME);
}
function getSkillSourcePath(packageRoot, skillName) {
return path.join(packageRoot, skillName);
}
function getSkillDestPath(installRoot, skillName) {
return path.join(installRoot, skillName);
}
async function ensureDir(dirPath) {
await fsp.mkdir(dirPath, { recursive: true });
}
async function removePath(targetPath) {
await fsp.rm(targetPath, { recursive: true, force: true });
}
async function installSkill({ packageRoot, installRoot, skillName, mode, platform }) {
const sourcePath = getSkillSourcePath(packageRoot, skillName);
const destPath = getSkillDestPath(installRoot, skillName);
await removePath(destPath);
if (mode === "copy") {
await fsp.cp(sourcePath, destPath, { recursive: true });
} else {
const symlinkType = platform === "windows" ? "junction" : "dir";
await fsp.symlink(sourcePath, destPath, symlinkType);
}
return {
skillName,
sourcePath,
destPath,
};
}
function buildSkillPaths(installRoot) {
return SKILLS.map((skill) => path.join(installRoot, skill, "SKILL.md"));
}
function quoteYamlString(value) {
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
}
function replaceManagedBlock(content, block, commentStyle) {
const startMarker = commentStyle === "html"
? `<!-- ${MANAGED_START} -->`
: `# ${MANAGED_START}`;
const endMarker = commentStyle === "html"
? `<!-- ${MANAGED_END} -->`
: `# ${MANAGED_END}`;
const managedBlock = `${startMarker}\n${block}\n${endMarker}`;
const escapedStart = escapeRegExp(startMarker);
const escapedEnd = escapeRegExp(endMarker);
const pattern = new RegExp(`${escapedStart}[\\s\\S]*?${escapedEnd}`, "m");
if (pattern.test(content)) {
return content.replace(pattern, managedBlock);
}
if (!content.trim()) {
return `${managedBlock}\n`;
}
return `${content.replace(/\s*$/, "")}\n\n${managedBlock}\n`;
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function upsertSkillsSection(content, items, itemRenderer) {
const lines = content ? content.split(/\r?\n/) : [];
const sectionLines = ["skills:", ...items.map(itemRenderer)];
const sectionStart = lines.findIndex((line) => /^skills:\s*$/.test(line.trim()));
if (sectionStart === -1) {
return `${lines.filter(Boolean).join("\n")}${lines.filter(Boolean).length ? "\n\n" : ""}${sectionLines.join("\n")}\n`;
}
let sectionEnd = sectionStart + 1;
while (sectionEnd < lines.length) {
const line = lines[sectionEnd];
if (!line.trim()) {
sectionEnd += 1;
continue;
}
if (/^\s*-/.test(line) || /^\s*#/.test(line)) {
sectionEnd += 1;
continue;
}
break;
}
const merged = [
...lines.slice(0, sectionStart),
...sectionLines,
...lines.slice(sectionEnd),
];
return `${merged.join("\n").replace(/\s*$/, "")}\n`;
}
async function writeFileWithDirs(filePath, content) {
await ensureDir(path.dirname(filePath));
await fsp.writeFile(filePath, content, "utf8");
}
async function readIfExists(filePath) {
try {
return await fsp.readFile(filePath, "utf8");
} catch (error) {
if (error.code === "ENOENT") {
return "";
}
throw error;
}
}
async function configureClaude(projectDir, skillPaths) {
const filePath = path.join(projectDir, "CLAUDE.md");
const current = await readIfExists(filePath);
const body = [
"Load these skill files before starting work:",
"",
...skillPaths.map((skillPath) => `- ${skillPath}`),
"",
"After loading, create concise implementation plan, state assumptions, then implement requested changes.",
].join("\n");
const next = replaceManagedBlock(current, body, "html");
await writeFileWithDirs(filePath, next);
return filePath;
}
async function configureCursor(projectDir, skillPaths) {
const filePath = path.join(projectDir, ".cursor", "rules", "developer-stack-skills.mdc");
const body = [
"---",
"description: Load installed developer-stack-skills files before coding",
"globs: []",
"alwaysApply: false",
"---",
"",
"Read and follow these skill files before starting work:",
"",
...skillPaths.map((skillPath) => `- ${skillPath}`),
].join("\n");
await writeFileWithDirs(filePath, body);
return filePath;
}
async function configureCline(projectDir, skillPaths) {
const filePath = path.join(projectDir, ".clinerules");
const current = await readIfExists(filePath);
const next = upsertSkillsSection(current, skillPaths, (skillPath) => ` - ${quoteYamlString(skillPath)}`);
await writeFileWithDirs(filePath, next);
return filePath;
}
async function configureRoocode(projectDir, skillPaths) {
const filePath = path.join(projectDir, ".roo", "config.yml");
const current = await readIfExists(filePath);
const next = upsertSkillsSection(current, skillPaths, (skillPath) => ` - path: ${quoteYamlString(skillPath)}`);
await writeFileWithDirs(filePath, next);
return filePath;
}
async function configureCopilot(projectDir, skillPaths) {
const filePath = path.join(projectDir, ".github", "copilot-instructions.md");
const current = await readIfExists(filePath);
const body = [
"Follow these skill files before producing code or process guidance:",
"",
...skillPaths.map((skillPath) => `- ${skillPath}`),
].join("\n");
const next = replaceManagedBlock(current, body, "html");
await writeFileWithDirs(filePath, next);
return filePath;
}
function getAgentTargets(agent) {
return agent === "all" ? AGENTS.filter((item) => item !== "all") : [agent];
}
async function configureAgents(agent, projectDir, installRoot) {
const skillPaths = buildSkillPaths(installRoot);
const targets = getAgentTargets(agent);
const configured = [];
for (const target of targets) {
if (target === "claude") {
configured.push({ agent: target, filePath: await configureClaude(projectDir, skillPaths) });
continue;
}
if (target === "cursor") {
configured.push({ agent: target, filePath: await configureCursor(projectDir, skillPaths) });
continue;
}
if (target === "cline") {
configured.push({ agent: target, filePath: await configureCline(projectDir, skillPaths) });
continue;
}
if (target === "roocode") {
configured.push({ agent: target, filePath: await configureRoocode(projectDir, skillPaths) });
continue;
}
if (target === "copilot") {
configured.push({ agent: target, filePath: await configureCopilot(projectDir, skillPaths) });
}
}
return configured;
}
async function collectAnswers(args) {
const prompt = createPrompt();
try {
const agent = normalizeAgent(args.agent) || await chooseValue(
prompt,
"Agent to configure [all/claude/cursor/cline/roocode/copilot] (default: all): ",
AGENTS,
"all",
);
const mode = normalizeMode(args.mode) || await chooseValue(
prompt,
"Install mode [copy/symlink] (default: symlink): ",
MODES,
"symlink",
);
const projectDirInput = args.projectDir || await prompt.ask(`Project directory (default: ${process.cwd()}): `);
const projectDir = path.resolve(projectDirInput || process.cwd());
return {
agent,
mode,
projectDir,
};
} finally {
prompt.close();
}
}
function validateArgs(args) {
const agent = normalizeAgent(args.agent || "all");
const mode = normalizeMode(args.mode || "symlink");
if (!AGENTS.includes(agent)) {
throw new Error(`Invalid agent "${args.agent}". Allowed: ${AGENTS.join(", ")}`);
}
if (!MODES.includes(mode)) {
throw new Error(`Invalid mode "${args.mode}". Allowed: ${MODES.join(", ")}`);
}
return {
agent,
mode,
projectDir: path.resolve(args.projectDir || process.cwd()),
};
}
async function runInstall(rawArgs) {
const platform = detectPlatform();
const packageRoot = getPackageRoot();
const version = getVersion();
const selected = rawArgs.yes ? validateArgs(rawArgs) : await collectAnswers(rawArgs);
const installRoot = getInstallRoot(selected.projectDir);
const packageInstallType = detectPackageInstallType(packageRoot, selected.projectDir);
console.log(`[${PACKAGE_NAME}] installing version ${version}`);
console.log(`[${PACKAGE_NAME}] package install type: ${packageInstallType}`);
console.log(`[${PACKAGE_NAME}] os: ${platform}`);
console.log(`[${PACKAGE_NAME}] package dir: ${packageRoot}`);
console.log(`[${PACKAGE_NAME}] project dir: ${selected.projectDir}`);
console.log(`[${PACKAGE_NAME}] install dir: ${installRoot}`);
console.log(`[${PACKAGE_NAME}] agent: ${selected.agent}`);
console.log(`[${PACKAGE_NAME}] mode: ${selected.mode}`);
await ensureDir(installRoot);
const installedSkills = [];
for (const skillName of SKILLS) {
const result = await installSkill({
packageRoot,
installRoot,
skillName,
mode: selected.mode,
platform,
});
installedSkills.push(result);
console.log(`[${PACKAGE_NAME}] skill installed: ${result.skillName} -> ${result.destPath}`);
}
const configured = await configureAgents(selected.agent, selected.projectDir, installRoot);
for (const item of configured) {
console.log(`[${PACKAGE_NAME}] ${item.agent} config updated: ${item.filePath}`);
}
console.log(`[${PACKAGE_NAME}] install complete`);
return {
version,
platform,
packageRoot,
projectDir: selected.projectDir,
installRoot,
installedSkills,
configured,
};
}
module.exports = {
AGENTS,
MODES,
SKILLS,
buildSkillPaths,
configureAgents,
detectPlatform,
parseArgs,
printHelp,
replaceManagedBlock,
runInstall,
upsertSkillsSection,
validateArgs,
detectPackageInstallType,
};
+12
-3
{
"name": "developer-stack-skills",
"version": "1.0.1",
"description": "AI agent SKILL.md files for Java/Spring, Python/FastAPI, React/Angular, Testing, and Project Conventions. Compatible with Claude, Cline, Roocode, Copilot, and Cursor.",
"version": "1.1.0",
"description": "AI agent SKILL.md files plus installer CLI for Java/Spring, Python/FastAPI, React/Angular, Testing, and Project Conventions. Compatible with Claude, Cline, Roocode, Copilot, and Cursor.",
"keywords": [

@@ -29,3 +29,11 @@ "ai-agents",

"license": "MIT",
"bin": {
"developer-stack-skills": "bin/developer-stack-skills.js"
},
"scripts": {
"test": "node --test"
},
"files": [
"bin/**/*",
"lib/**/*",
"java-spring/SKILL.md",

@@ -37,3 +45,4 @@ "python-backend/SKILL.md",

"examples/**/*",
"README.md"
"README.md",
"CHANGELOG.md"
],

@@ -40,0 +49,0 @@ "skills": {

+86
-33

@@ -15,51 +15,104 @@ # developer-stack-skills

---
Global install:
## Skills Included
```bash
npm install -g developer-stack-skills
```
| Skill | File | Use When |
|---|---|---|
| `java-spring` | `java-spring/SKILL.md` | Spring Boot, JPA, REST APIs, JUnit |
| `python-backend` | `python-backend/SKILL.md` | FastAPI, SQLAlchemy, Pydantic, pytest |
| `frontend` | `frontend/SKILL.md` | React, Angular, TypeScript, TanStack Query |
| `testing` | `testing/SKILL.md` | Unit, integration, E2E across all stacks |
| `project-conventions` | `project-conventions/SKILL.md` | Git, ADRs, naming, PR standards, README |
Version in this README: `1.1.0`
---
Run installer:
## Usage by Agent
```bash
developer-stack-skills install
```
### Claude / Cursor
Point to the skill file in your system prompt or agent config:
Or run from local package without global install:
```bash
npx developer-stack-skills install
```
Read node_modules/developer-stack-skills/java-spring/SKILL.md before writing any Java code.
Installer will:
1. Detect OS automatically
2. Ask which agent to configure: `all`, `claude`, `cursor`, `cline`, `roocode`, or `copilot`
3. Ask whether to `copy` files or create `symlink`
4. Ask which project directory to install into
5. Install all skill folders into:
```text
<project>/.ai-skills/developer-stack-skills/
```
### Cline (VS Code)
Add to `.clinerules` in your project root:
6. Update agent-specific config files in that project
7. Log package version, package install type (`local` or `global`), OS, source directory, install directory, and each generated config path
Installer does not run automatically on `npm install`. Run `developer-stack-skills install` or `npx developer-stack-skills install` when you want to configure a project.
Non-interactive install:
```bash
developer-stack-skills install --agent all --mode symlink --dir . --yes
```
skills:
- node_modules/developer-stack-skills/java-spring/SKILL.md
- node_modules/developer-stack-skills/testing/SKILL.md
- node_modules/developer-stack-skills/project-conventions/SKILL.md
```
### Roocode
Add to `.roo/config.yml`:
```yaml
skills:
- path: node_modules/developer-stack-skills/python-backend/SKILL.md
- path: node_modules/developer-stack-skills/frontend/SKILL.md
Example log output:
```text
[developer-stack-skills] installing version 1.1.0
[developer-stack-skills] package install type: global
[developer-stack-skills] os: windows
[developer-stack-skills] package dir: C:\Users\<you>\AppData\Roaming\npm\node_modules\developer-stack-skills
[developer-stack-skills] project dir: D:\Projects\my-app
[developer-stack-skills] install dir: D:\Projects\my-app\.ai-skills\developer-stack-skills
[developer-stack-skills] skill installed: java-spring -> D:\Projects\my-app\.ai-skills\developer-stack-skills\java-spring
[developer-stack-skills] cline config updated: D:\Projects\my-app\.clinerules
[developer-stack-skills] install complete
```
### GitHub Copilot
Reference in `.github/copilot-instructions.md`:
```markdown
Follow the conventions in:
- node_modules/developer-stack-skills/frontend/SKILL.md
- node_modules/developer-stack-skills/project-conventions/SKILL.md
Flags:
- `--agent <all|claude|cursor|cline|roocode|copilot>`
- `--mode <copy|symlink>`
- `--dir <project-directory>`
- `--yes`
---
## Installed Files
Skill files get copied or linked here:
```text
<project>/.ai-skills/developer-stack-skills/
```
Agent configs get created or updated here:
- `Claude`: `CLAUDE.md`
- `Cursor`: `.cursor/rules/developer-stack-skills.mdc`
- `Cline`: `.clinerules`
- `Roocode`: `.roo/config.yml`
- `GitHub Copilot`: `.github/copilot-instructions.md`
Notes:
- `copy` makes project-local copies of skill folders
- `symlink` keeps installed skills linked to package source
- Running installer again refreshes installed skill folders and rewrites managed config sections
---
## Skills Included
| Skill | File | Use When |
|---|---|---|
| `java-spring` | `java-spring/SKILL.md` | Spring Boot, JPA, REST APIs, JUnit |
| `python-backend` | `python-backend/SKILL.md` | FastAPI, SQLAlchemy, Pydantic, pytest |
| `frontend` | `frontend/SKILL.md` | React, Angular, TypeScript, TanStack Query |
| `testing` | `testing/SKILL.md` | Unit, integration, E2E across all stacks |
| `project-conventions` | `project-conventions/SKILL.md` | Git, ADRs, naming, PR standards, README |
---
## Stack

@@ -66,0 +119,0 @@