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.1.0
to
1.2.0
+8
bin/postinstall.js
#!/usr/bin/env node
const { runPostInstall } = require("../lib/installer");
runPostInstall().catch((error) => {
console.error(`[developer-stack-skills] postinstall failed: ${error.message}`);
process.exitCode = 1;
});
# Release Notes
## 1.2.0 - 2026-05-15
This release streamlines package setup so installing package can immediately configure skills without extra manual steps in interactive environments.
Highlights:
- `npm install developer-stack-skills` now sets up project-level skills automatically
- `npm install -g developer-stack-skills` now sets up globally installed skills under `~/.ai-skills/developer-stack-skills/`
- added `uninstall`, `version`, and `help` commands
- added `--dry-run` for install and uninstall preview
- source checkout and CI/non-interactive installs skip auto-config safely
Behavior details:
- local package installs default to project-level skill installation and prefer `copy`
- global package installs default to global skill installation and prefer `symlink`
- agent integration still happens by updating agent config files with skill paths
- uninstall removes managed config entries and installed skill folders
Manual fallback remains available:
```bash
developer-stack-skills install
npx developer-stack-skills install
```
+23
-2
#!/usr/bin/env node
const { parseArgs, printHelp, runInstall } = require("../lib/installer");
const {
parseArgs,
printHelp,
printVersion,
runInstall,
runUninstall,
} = require("../lib/installer");

@@ -13,2 +19,17 @@ async function main() {

if (args.command === "uninstall") {
await runUninstall(args);
return;
}
if (["version", "--version", "-v"].includes(args.command)) {
printVersion();
return;
}
if (["help", "--help", "-h"].includes(args.command)) {
printHelp();
return;
}
printHelp();

@@ -18,4 +39,4 @@ }

main().catch((error) => {
console.error(`[developer-stack-skills] install failed: ${error.message}`);
console.error(`[developer-stack-skills] command failed: ${error.message}`);
process.exitCode = 1;
});
# Changelog
## 1.2.0 - 2026-05-15
Added:
- `postinstall` hook to auto-run configuration during interactive `npm install`
- `source` package install type detection to skip auto-config when working inside repo checkout
- global skill install target at `~/.ai-skills/developer-stack-skills/`
- default install mode selection by package scope: `copy` for local install, `symlink` for global install
- `uninstall` command to remove installed skills and agent config entries
- `version` command
- `help` command
- `--dry-run` flag for install and uninstall preview
- test coverage for install scope helpers, `source` detection, and uninstall config cleanup helpers
Changed:
- `npm install developer-stack-skills` now configures project-level skills without separate installer step
- `npm install -g developer-stack-skills` now configures global skill install without separate installer step
- global install flow still prompts for project directory to update agent config files
- CLI now supports full command lifecycle: install, uninstall, version, and help
- help output now documents commands and dry-run usage
- README now documents auto-config behavior, local vs global install scope, uninstall/version/help commands, and updated example logs
Notes:
- auto-config skips in non-interactive environments such as CI
- uninstall removes agent linkage by rewriting config files, not by agent symlink removal
- manual fallback remains available with `developer-stack-skills install` or `npx developer-stack-skills install`
## 1.1.0 - 2026-05-15

@@ -23,2 +52,2 @@

- installer is manual by design; it does not auto-run during `npm install`
- installer originally required manual execution after package install
const fsp = require("fs/promises");
const os = require("os");
const path = require("path");

@@ -37,2 +38,3 @@ const readline = require("readline");

projectDir: null,
dryRun: false,
yes: false,

@@ -49,2 +51,7 @@ };

if (token === "--dry-run") {
args.dryRun = true;
continue;
}
if (token.startsWith("--agent=")) {

@@ -110,5 +117,14 @@ args.agent = token.slice("--agent=".length);

console.log(" developer-stack-skills install");
console.log(" developer-stack-skills uninstall");
console.log(" developer-stack-skills version");
console.log(" developer-stack-skills help");
console.log(" developer-stack-skills install --agent all --mode symlink --dir .");
console.log(" npx developer-stack-skills install --agent cline --mode copy");
console.log(" npx developer-stack-skills install --agent cline --mode copy --dry-run");
console.log("");
console.log("Commands:");
console.log(" install install skills and update agent config");
console.log(" uninstall remove installed skills and agent config entries");
console.log(" version print package version");
console.log(" help print this help");
console.log("");
console.log("Options:");

@@ -118,5 +134,10 @@ console.log(" --agent <all|claude|cursor|cline|roocode|copilot>");

console.log(" --dir <project-directory>");
console.log(" --dry-run");
console.log(" --yes");
}
function printVersion() {
console.log(getVersion());
}
function createPrompt() {

@@ -166,11 +187,36 @@ const rl = readline.createInterface({

const normalizedPackageRoot = path.resolve(packageRoot);
const normalizedProjectDir = path.resolve(projectDir);
const localNodeModulesRoot = path.resolve(projectDir, "node_modules", PACKAGE_NAME);
if (normalizedPackageRoot === normalizedProjectDir) {
return "source";
}
return normalizedPackageRoot === localNodeModulesRoot ? "local" : "global";
}
function getInstallRoot(projectDir) {
function getInstallRoot(projectDir, packageInstallType) {
if (packageInstallType === "global") {
return path.join(os.homedir(), ".ai-skills", PACKAGE_NAME);
}
return path.join(projectDir, ".ai-skills", PACKAGE_NAME);
}
function getDefaultProjectDir(env = process.env, cwd = process.cwd()) {
return path.resolve(env.INIT_CWD || cwd);
}
function getDefaultMode(packageInstallType) {
return packageInstallType === "local" ? "copy" : "symlink";
}
function isInteractiveInstall(env = process.env) {
if (env.DEVELOPER_STACK_SKILLS_SKIP_POSTINSTALL === "1") {
return false;
}
return Boolean(process.stdin.isTTY && process.stdout.isTTY && env.CI !== "true");
}
function getSkillSourcePath(packageRoot, skillName) {

@@ -184,21 +230,31 @@ return path.join(packageRoot, skillName);

async function ensureDir(dirPath) {
async function ensureDir(dirPath, dryRun = false) {
if (dryRun) {
return;
}
await fsp.mkdir(dirPath, { recursive: true });
}
async function removePath(targetPath) {
async function removePath(targetPath, dryRun = false) {
if (dryRun) {
return;
}
await fsp.rm(targetPath, { recursive: true, force: true });
}
async function installSkill({ packageRoot, installRoot, skillName, mode, platform }) {
async function installSkill({ packageRoot, installRoot, skillName, mode, platform, dryRun = false }) {
const sourcePath = getSkillSourcePath(packageRoot, skillName);
const destPath = getSkillDestPath(installRoot, skillName);
await removePath(destPath);
await removePath(destPath, dryRun);
if (mode === "copy") {
await fsp.cp(sourcePath, destPath, { recursive: true });
if (!dryRun) {
await fsp.cp(sourcePath, destPath, { recursive: true });
}
} else {
const symlinkType = platform === "windows" ? "junction" : "dir";
await fsp.symlink(sourcePath, destPath, symlinkType);
if (!dryRun) {
await fsp.symlink(sourcePath, destPath, symlinkType);
}
}

@@ -245,2 +301,16 @@

function removeManagedBlock(content, commentStyle) {
const startMarker = commentStyle === "html"
? `<!-- ${MANAGED_START} -->`
: `# ${MANAGED_START}`;
const endMarker = commentStyle === "html"
? `<!-- ${MANAGED_END} -->`
: `# ${MANAGED_END}`;
const escapedStart = escapeRegExp(startMarker);
const escapedEnd = escapeRegExp(endMarker);
const pattern = new RegExp(`\\n?${escapedStart}[\\s\\S]*?${escapedEnd}\\n?`, "m");
return content.replace(pattern, "").replace(/\n{3,}/g, "\n\n").replace(/\s*$/, content.trim() ? "\n" : "");
}
function escapeRegExp(value) {

@@ -282,4 +352,50 @@ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");

async function writeFileWithDirs(filePath, content) {
await ensureDir(path.dirname(filePath));
function removeSkillsSectionItems(content, items, itemRenderer) {
const lines = content ? content.split(/\r?\n/) : [];
const sectionStart = lines.findIndex((line) => /^skills:\s*$/.test(line.trim()));
if (sectionStart === -1) {
return content;
}
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 removeSet = new Set(items.map(itemRenderer));
const keptLines = lines
.slice(sectionStart + 1, sectionEnd)
.filter((line) => !removeSet.has(line));
const hasSkillEntries = keptLines.some((line) => /^\s*-/.test(line));
const merged = hasSkillEntries
? [
...lines.slice(0, sectionStart),
"skills:",
...keptLines,
...lines.slice(sectionEnd),
]
: [
...lines.slice(0, sectionStart),
...lines.slice(sectionEnd),
];
return `${merged.join("\n").replace(/\s*$/, "")}${merged.some((line) => line.trim()) ? "\n" : ""}`;
}
async function writeFileWithDirs(filePath, content, dryRun = false) {
await ensureDir(path.dirname(filePath), dryRun);
if (dryRun) {
return;
}
await fsp.writeFile(filePath, content, "utf8");

@@ -299,3 +415,3 @@ }

async function configureClaude(projectDir, skillPaths) {
async function configureClaude(projectDir, skillPaths, dryRun = false) {
const filePath = path.join(projectDir, "CLAUDE.md");

@@ -312,7 +428,7 @@ const current = await readIfExists(filePath);

const next = replaceManagedBlock(current, body, "html");
await writeFileWithDirs(filePath, next);
await writeFileWithDirs(filePath, next, dryRun);
return filePath;
}
async function configureCursor(projectDir, skillPaths) {
async function configureCursor(projectDir, skillPaths, dryRun = false) {
const filePath = path.join(projectDir, ".cursor", "rules", "developer-stack-skills.mdc");

@@ -331,7 +447,7 @@ const body = [

await writeFileWithDirs(filePath, body);
await writeFileWithDirs(filePath, body, dryRun);
return filePath;
}
async function configureCline(projectDir, skillPaths) {
async function configureCline(projectDir, skillPaths, dryRun = false) {
const filePath = path.join(projectDir, ".clinerules");

@@ -341,7 +457,7 @@ const current = await readIfExists(filePath);

await writeFileWithDirs(filePath, next);
await writeFileWithDirs(filePath, next, dryRun);
return filePath;
}
async function configureRoocode(projectDir, skillPaths) {
async function configureRoocode(projectDir, skillPaths, dryRun = false) {
const filePath = path.join(projectDir, ".roo", "config.yml");

@@ -351,7 +467,7 @@ const current = await readIfExists(filePath);

await writeFileWithDirs(filePath, next);
await writeFileWithDirs(filePath, next, dryRun);
return filePath;
}
async function configureCopilot(projectDir, skillPaths) {
async function configureCopilot(projectDir, skillPaths, dryRun = false) {
const filePath = path.join(projectDir, ".github", "copilot-instructions.md");

@@ -366,3 +482,3 @@ const current = await readIfExists(filePath);

const next = replaceManagedBlock(current, body, "html");
await writeFileWithDirs(filePath, next);
await writeFileWithDirs(filePath, next, dryRun);
return filePath;

@@ -375,3 +491,3 @@ }

async function configureAgents(agent, projectDir, installRoot) {
async function configureAgents(agent, projectDir, installRoot, dryRun = false) {
const skillPaths = buildSkillPaths(installRoot);

@@ -383,3 +499,3 @@ const targets = getAgentTargets(agent);

if (target === "claude") {
configured.push({ agent: target, filePath: await configureClaude(projectDir, skillPaths) });
configured.push({ agent: target, filePath: await configureClaude(projectDir, skillPaths, dryRun) });
continue;

@@ -389,3 +505,3 @@ }

if (target === "cursor") {
configured.push({ agent: target, filePath: await configureCursor(projectDir, skillPaths) });
configured.push({ agent: target, filePath: await configureCursor(projectDir, skillPaths, dryRun) });
continue;

@@ -395,3 +511,3 @@ }

if (target === "cline") {
configured.push({ agent: target, filePath: await configureCline(projectDir, skillPaths) });
configured.push({ agent: target, filePath: await configureCline(projectDir, skillPaths, dryRun) });
continue;

@@ -401,3 +517,3 @@ }

if (target === "roocode") {
configured.push({ agent: target, filePath: await configureRoocode(projectDir, skillPaths) });
configured.push({ agent: target, filePath: await configureRoocode(projectDir, skillPaths, dryRun) });
continue;

@@ -407,3 +523,3 @@ }

if (target === "copilot") {
configured.push({ agent: target, filePath: await configureCopilot(projectDir, skillPaths) });
configured.push({ agent: target, filePath: await configureCopilot(projectDir, skillPaths, dryRun) });
}

@@ -415,20 +531,125 @@ }

async function collectAnswers(args) {
async function unconfigureClaude(projectDir, dryRun = false) {
const filePath = path.join(projectDir, "CLAUDE.md");
const current = await readIfExists(filePath);
const next = removeManagedBlock(current, "html");
if (next.trim()) {
await writeFileWithDirs(filePath, next, dryRun);
} else {
await removePath(filePath, dryRun);
}
return filePath;
}
async function unconfigureCursor(projectDir, dryRun = false) {
const filePath = path.join(projectDir, ".cursor", "rules", "developer-stack-skills.mdc");
await removePath(filePath, dryRun);
return filePath;
}
async function unconfigureCline(projectDir, skillPaths, dryRun = false) {
const filePath = path.join(projectDir, ".clinerules");
const current = await readIfExists(filePath);
const next = removeSkillsSectionItems(current, skillPaths, (skillPath) => ` - ${quoteYamlString(skillPath)}`);
if (next.trim()) {
await writeFileWithDirs(filePath, next, dryRun);
} else {
await removePath(filePath, dryRun);
}
return filePath;
}
async function unconfigureRoocode(projectDir, skillPaths, dryRun = false) {
const filePath = path.join(projectDir, ".roo", "config.yml");
const current = await readIfExists(filePath);
const next = removeSkillsSectionItems(current, skillPaths, (skillPath) => ` - path: ${quoteYamlString(skillPath)}`);
if (next.trim()) {
await writeFileWithDirs(filePath, next, dryRun);
} else {
await removePath(filePath, dryRun);
}
return filePath;
}
async function unconfigureCopilot(projectDir, dryRun = false) {
const filePath = path.join(projectDir, ".github", "copilot-instructions.md");
const current = await readIfExists(filePath);
const next = removeManagedBlock(current, "html");
if (next.trim()) {
await writeFileWithDirs(filePath, next, dryRun);
} else {
await removePath(filePath, dryRun);
}
return filePath;
}
async function unconfigureAgents(agent, projectDir, installRoot, dryRun = false) {
const skillPaths = buildSkillPaths(installRoot);
const targets = getAgentTargets(agent);
const configured = [];
for (const target of targets) {
if (target === "claude") {
configured.push({ agent: target, filePath: await unconfigureClaude(projectDir, dryRun) });
continue;
}
if (target === "cursor") {
configured.push({ agent: target, filePath: await unconfigureCursor(projectDir, dryRun) });
continue;
}
if (target === "cline") {
configured.push({ agent: target, filePath: await unconfigureCline(projectDir, skillPaths, dryRun) });
continue;
}
if (target === "roocode") {
configured.push({ agent: target, filePath: await unconfigureRoocode(projectDir, skillPaths, dryRun) });
continue;
}
if (target === "copilot") {
configured.push({ agent: target, filePath: await unconfigureCopilot(projectDir, dryRun) });
}
}
return configured;
}
async function collectAnswers(args, defaults = {}) {
const prompt = createPrompt();
try {
const defaultAgent = defaults.agent || "all";
const defaultMode = defaults.mode || "symlink";
const defaultProjectDir = path.resolve(defaults.projectDir || process.cwd());
const askMode = defaults.askMode !== false;
const agent = normalizeAgent(args.agent) || await chooseValue(
prompt,
"Agent to configure [all/claude/cursor/cline/roocode/copilot] (default: all): ",
`Agent to configure [all/claude/cursor/cline/roocode/copilot] (default: ${defaultAgent}): `,
AGENTS,
"all",
defaultAgent,
);
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());
const mode = askMode
? (normalizeMode(args.mode) || await chooseValue(
prompt,
`Install mode [copy/symlink] (default: ${defaultMode}): `,
MODES,
defaultMode,
))
: normalizeMode(args.mode || defaultMode);
let projectDir;
if (args.projectDir) {
projectDir = path.resolve(args.projectDir);
} else if (defaults.askProjectDir === false) {
projectDir = defaultProjectDir;
} else {
const projectDirInput = await prompt.ask(`Project directory (default: ${defaultProjectDir}): `);
projectDir = path.resolve(projectDirInput || defaultProjectDir);
}

@@ -445,5 +666,5 @@ return {

function validateArgs(args) {
const agent = normalizeAgent(args.agent || "all");
const mode = normalizeMode(args.mode || "symlink");
function validateArgs(args, defaults = {}) {
const agent = normalizeAgent(args.agent || defaults.agent || "all");
const mode = normalizeMode(args.mode || defaults.mode || "symlink");

@@ -461,16 +682,42 @@ if (!AGENTS.includes(agent)) {

mode,
projectDir: path.resolve(args.projectDir || process.cwd()),
projectDir: path.resolve(args.projectDir || defaults.projectDir || process.cwd()),
};
}
async function runInstall(rawArgs) {
async function resolveSelection(rawArgs, options = {}) {
const packageRoot = getPackageRoot();
const packageInstallType = options.packageInstallType || detectPackageInstallType(
packageRoot,
options.projectDir || rawArgs.projectDir || process.cwd(),
);
const defaults = {
agent: "all",
mode: getDefaultMode(packageInstallType),
projectDir: options.projectDir || getDefaultProjectDir(),
askProjectDir: options.askProjectDir,
askMode: options.askMode,
};
const selected = rawArgs.yes
? validateArgs(rawArgs, defaults)
: await collectAnswers(rawArgs, defaults);
const installRoot = getInstallRoot(selected.projectDir, packageInstallType);
const installScope = packageInstallType === "global" ? "global" : "project";
return {
packageInstallType,
selected,
installRoot,
installScope,
};
}
async function runInstall(rawArgs, options = {}) {
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);
const { packageInstallType, selected, installRoot, installScope } = await resolveSelection(rawArgs, options);
console.log(`[${PACKAGE_NAME}] installing version ${version}`);
console.log(`[${PACKAGE_NAME}] package install type: ${packageInstallType}`);
console.log(`[${PACKAGE_NAME}] skill install scope: ${installScope}`);
console.log(`[${PACKAGE_NAME}] os: ${platform}`);

@@ -482,4 +729,5 @@ console.log(`[${PACKAGE_NAME}] package dir: ${packageRoot}`);

console.log(`[${PACKAGE_NAME}] mode: ${selected.mode}`);
console.log(`[${PACKAGE_NAME}] dry run: ${rawArgs.dryRun ? "yes" : "no"}`);
await ensureDir(installRoot);
await ensureDir(installRoot, rawArgs.dryRun);

@@ -494,13 +742,14 @@ const installedSkills = [];

platform,
dryRun: rawArgs.dryRun,
});
installedSkills.push(result);
console.log(`[${PACKAGE_NAME}] skill installed: ${result.skillName} -> ${result.destPath}`);
console.log(`[${PACKAGE_NAME}] skill ${rawArgs.dryRun ? "would install" : "installed"}: ${result.skillName} -> ${result.destPath}`);
}
const configured = await configureAgents(selected.agent, selected.projectDir, installRoot);
const configured = await configureAgents(selected.agent, selected.projectDir, installRoot, rawArgs.dryRun);
for (const item of configured) {
console.log(`[${PACKAGE_NAME}] ${item.agent} config updated: ${item.filePath}`);
console.log(`[${PACKAGE_NAME}] ${item.agent} config ${rawArgs.dryRun ? "would update" : "updated"}: ${item.filePath}`);
}
console.log(`[${PACKAGE_NAME}] install complete`);
console.log(`[${PACKAGE_NAME}] ${rawArgs.dryRun ? "install dry run complete" : "install complete"}`);

@@ -513,2 +762,3 @@ return {

installRoot,
installScope,
installedSkills,

@@ -519,2 +769,68 @@ configured,

async function runUninstall(rawArgs, options = {}) {
const version = getVersion();
const { packageInstallType, selected, installRoot, installScope } = await resolveSelection(
rawArgs,
{ ...options, askMode: false },
);
console.log(`[${PACKAGE_NAME}] uninstalling version ${version}`);
console.log(`[${PACKAGE_NAME}] package install type: ${packageInstallType}`);
console.log(`[${PACKAGE_NAME}] skill install scope: ${installScope}`);
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}] dry run: ${rawArgs.dryRun ? "yes" : "no"}`);
const configured = await unconfigureAgents(selected.agent, selected.projectDir, installRoot, rawArgs.dryRun);
for (const item of configured) {
console.log(`[${PACKAGE_NAME}] ${item.agent} config ${rawArgs.dryRun ? "would remove" : "removed"}: ${item.filePath}`);
}
for (const skillName of SKILLS) {
const skillPath = getSkillDestPath(installRoot, skillName);
await removePath(skillPath, rawArgs.dryRun);
console.log(`[${PACKAGE_NAME}] skill ${rawArgs.dryRun ? "would remove" : "removed"}: ${skillPath}`);
}
console.log(`[${PACKAGE_NAME}] ${rawArgs.dryRun ? "uninstall dry run complete" : "uninstall complete"}`);
return {
version,
packageInstallType,
projectDir: selected.projectDir,
installRoot,
installScope,
configured,
};
}
async function runPostInstall(env = process.env) {
const packageRoot = getPackageRoot();
const projectDir = getDefaultProjectDir(env);
const packageInstallType = detectPackageInstallType(packageRoot, projectDir);
if (packageInstallType === "source") {
console.log(`[${PACKAGE_NAME}] postinstall skipped in source checkout`);
return { skipped: true, reason: "source" };
}
if (!isInteractiveInstall(env)) {
console.log(`[${PACKAGE_NAME}] postinstall skipped in non-interactive install`);
console.log(`[${PACKAGE_NAME}] run "npx developer-stack-skills install" to configure later`);
return { skipped: true, reason: "non-interactive" };
}
console.log(`[${PACKAGE_NAME}] postinstall detected ${packageInstallType} package install`);
return runInstall(
{ command: "install" },
{
packageInstallType,
projectDir,
askProjectDir: packageInstallType === "global",
},
);
}
module.exports = {

@@ -526,10 +842,19 @@ AGENTS,

configureAgents,
detectPackageInstallType,
detectPlatform,
getDefaultMode,
getDefaultProjectDir,
getInstallRoot,
isInteractiveInstall,
parseArgs,
printHelp,
printVersion,
removeManagedBlock,
removeSkillsSectionItems,
replaceManagedBlock,
runInstall,
runPostInstall,
runUninstall,
upsertSkillsSection,
validateArgs,
detectPackageInstallType,
};
+3
-1
{
"name": "developer-stack-skills",
"version": "1.1.0",
"version": "1.2.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.",

@@ -33,2 +33,3 @@ "keywords": [

"scripts": {
"postinstall": "node bin/postinstall.js",
"test": "node --test"

@@ -46,2 +47,3 @@ },

"README.md",
"RELEASE_NOTES.md",
"CHANGELOG.md"

@@ -48,0 +50,0 @@ ],

+61
-10

@@ -21,6 +21,16 @@ # developer-stack-skills

Version in this README: `1.1.0`
Version in this README: `1.2.0`
Run installer:
Interactive `npm install` now auto-runs configuration.
- `npm install developer-stack-skills`
Installs skills into `<project>/.ai-skills/developer-stack-skills`
Default mode prompt prefers `copy`
- `npm install -g developer-stack-skills`
Installs skills into `~/.ai-skills/developer-stack-skills`
Default mode prompt prefers `symlink`
Still asks which project directory to update for agent config files
Manual installer still available:
```bash

@@ -30,2 +40,20 @@ developer-stack-skills install

Remove installed skills and agent config:
```bash
developer-stack-skills uninstall
```
Show version:
```bash
developer-stack-skills version
```
Show help:
```bash
developer-stack-skills help
```
Or run from local package without global install:

@@ -42,5 +70,9 @@

3. Ask whether to `copy` files or create `symlink`
4. Ask which project directory to install into
5. Install all skill folders into:
4. Ask which project directory to install into when needed
5. Install all skill folders into project or global skill directory
6. Update agent-specific config files in that project
7. Log package version, package install type (`source`, `local`, or `global`), install scope, OS, source directory, install directory, and each generated config path
Project-level install dir:
```text

@@ -50,7 +82,10 @@ <project>/.ai-skills/developer-stack-skills/

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
Global install dir:
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.
```text
~/.ai-skills/developer-stack-skills/
```
`postinstall` skips auto-config in non-interactive environments and in source checkout of this repo. In those cases, run `developer-stack-skills install` or `npx developer-stack-skills install` manually.
Non-interactive install:

@@ -60,2 +95,3 @@

developer-stack-skills install --agent all --mode symlink --dir . --yes
developer-stack-skills uninstall --agent all --dir . --dry-run --yes
```

@@ -66,9 +102,10 @@

```text
[developer-stack-skills] installing version 1.1.0
[developer-stack-skills] installing version 1.2.0
[developer-stack-skills] package install type: global
[developer-stack-skills] skill install scope: 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] install dir: C:\Users\<you>\.ai-skills\developer-stack-skills
[developer-stack-skills] skill installed: java-spring -> C:\Users\<you>\.ai-skills\developer-stack-skills\java-spring
[developer-stack-skills] cline config updated: D:\Projects\my-app\.clinerules

@@ -83,4 +120,12 @@ [developer-stack-skills] install complete

- `--dir <project-directory>`
- `--dry-run`
- `--yes`
Commands:
- `install`
- `uninstall`
- `version`
- `help`
---

@@ -96,2 +141,8 @@

Or for global package installs:
```text
~/.ai-skills/developer-stack-skills/
```
Agent configs get created or updated here:

@@ -98,0 +149,0 @@