@buiducnhat/agent-skills 0.2.1 → 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.
Files changed (2) hide show
  1. package/dist/index.js +196 -524
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -7,8 +7,8 @@ import y, { stdin, stdout } from "node:process";
7
7
  import * as g from "node:readline";
8
8
  import O from "node:readline";
9
9
  import { Writable } from "node:stream";
10
- import { execSync } from "node:child_process";
11
- import os, { tmpdir } from "node:os";
10
+ import { execSync, spawn } from "node:child_process";
11
+ import { tmpdir } from "node:os";
12
12
 
13
13
  //#region \0rolldown/runtime.js
14
14
  var __create = Object.create;
@@ -1223,215 +1223,59 @@ ${J}${i.trimStart()}`), r = 3 + stripVTControlCharacters(i.trimStart()).length);
1223
1223
  }
1224
1224
  };
1225
1225
 
1226
- //#endregion
1227
- //#region src/apply.ts
1228
- async function runRulerApply(projectDir, agents) {
1229
- const s = Y();
1230
- s.start("Running ruler to generate agent configurations...");
1231
- const agentFlag = agents.join(",");
1232
- try {
1233
- execSync(`npx --yes @intellectronica/ruler apply --agents ${agentFlag}`, {
1234
- cwd: projectDir,
1235
- stdio: "pipe",
1236
- timeout: 12e4
1237
- });
1238
- s.stop("Generated agent configurations");
1239
- } catch {
1240
- s.stop("ruler apply encountered issues");
1241
- M.warn(`ruler apply had warnings or errors. You can run it manually:
1242
- npx @intellectronica/ruler apply --agents ${agentFlag}`);
1243
- }
1244
- }
1245
-
1246
- //#endregion
1247
- //#region src/configure.ts
1248
- function configureRulerToml(projectDir, agents) {
1249
- const tomlPath = path.join(projectDir, ".ruler", "ruler.toml");
1250
- if (!fs.existsSync(tomlPath)) {
1251
- M.warn("ruler.toml not found, skipping configuration");
1252
- return;
1253
- }
1254
- let content = fs.readFileSync(tomlPath, "utf-8");
1255
- const newLine = `default_agents = [${agents.map((a) => `"${a}"`).join(", ")}]`;
1256
- if (/^#?\s*default_agents\s*=/m.test(content)) content = content.replace(/^#?\s*default_agents\s*=.*$/m, newLine);
1257
- else {
1258
- const insertPoint = content.indexOf("\n\n");
1259
- if (insertPoint !== -1) content = content.slice(0, insertPoint) + "\n" + newLine + content.slice(insertPoint);
1260
- else content += `\n${newLine}\n`;
1261
- }
1262
- fs.writeFileSync(tomlPath, content, "utf-8");
1263
- M.info(`Configured ruler.toml with agents: ${agents.join(", ")}`);
1264
- }
1265
-
1266
1226
  //#endregion
1267
1227
  //#region src/constants.ts
1268
1228
  const REPO_URL = "https://github.com/buiducnhat/agent-skills.git";
1269
1229
  const REPO_BRANCH = "main";
1270
- const RULER_AGENTS = [
1271
- {
1272
- value: "claude",
1273
- label: "Claude Code",
1274
- hint: "Anthropic"
1275
- },
1276
- {
1277
- value: "copilot",
1278
- label: "GitHub Copilot",
1279
- hint: "GitHub"
1280
- },
1281
- {
1282
- value: "cursor",
1283
- label: "Cursor",
1284
- hint: "Cursor"
1285
- },
1286
- {
1287
- value: "windsurf",
1288
- label: "Windsurf",
1289
- hint: "Codeium"
1290
- },
1291
- {
1292
- value: "codex",
1293
- label: "OpenAI Codex CLI",
1294
- hint: "OpenAI"
1295
- },
1296
- {
1297
- value: "gemini-cli",
1298
- label: "Gemini CLI",
1299
- hint: "Google"
1300
- },
1301
- {
1302
- value: "amp",
1303
- label: "Amp",
1304
- hint: "Sourcegraph"
1305
- },
1306
- {
1307
- value: "cline",
1308
- label: "Cline",
1309
- hint: "VS Code"
1310
- },
1311
- {
1312
- value: "roo",
1313
- label: "Roo Code",
1314
- hint: "VS Code"
1315
- },
1316
- {
1317
- value: "aider",
1318
- label: "Aider",
1319
- hint: "Terminal"
1320
- },
1321
- {
1322
- value: "antigravity",
1323
- label: "Antigravity",
1324
- hint: ""
1325
- },
1326
- {
1327
- value: "pi",
1328
- label: "Pi Coding Agent",
1329
- hint: ""
1330
- },
1331
- {
1332
- value: "jules",
1333
- label: "Jules",
1334
- hint: "Google"
1335
- },
1336
- {
1337
- value: "kiro",
1338
- label: "Kiro",
1339
- hint: "AWS"
1340
- },
1341
- {
1342
- value: "kilocode",
1343
- label: "Kilo Code",
1344
- hint: "VS Code"
1345
- },
1346
- {
1347
- value: "crush",
1348
- label: "Crush",
1349
- hint: ""
1350
- },
1351
- {
1352
- value: "amazonqcli",
1353
- label: "Amazon Q CLI",
1354
- hint: "AWS"
1355
- },
1356
- {
1357
- value: "firebase",
1358
- label: "Firebase Studio",
1359
- hint: "Google"
1360
- },
1361
- {
1362
- value: "openhands",
1363
- label: "Open Hands",
1364
- hint: ""
1365
- },
1366
- {
1367
- value: "junie",
1368
- label: "Junie",
1369
- hint: "JetBrains"
1370
- },
1371
- {
1372
- value: "jetbrains-ai",
1373
- label: "JetBrains AI Assistant",
1374
- hint: "JetBrains"
1375
- },
1376
- {
1377
- value: "augmentcode",
1378
- label: "AugmentCode",
1379
- hint: ""
1380
- },
1381
- {
1382
- value: "opencode",
1383
- label: "OpenCode",
1384
- hint: ""
1385
- },
1386
- {
1387
- value: "goose",
1388
- label: "Goose",
1389
- hint: "Block"
1390
- },
1391
- {
1392
- value: "qwen",
1393
- label: "Qwen Code",
1394
- hint: "Alibaba"
1395
- },
1396
- {
1397
- value: "zed",
1398
- label: "Zed",
1399
- hint: ""
1400
- },
1401
- {
1402
- value: "trae",
1403
- label: "Trae AI",
1404
- hint: "ByteDance"
1405
- },
1406
- {
1407
- value: "warp",
1408
- label: "Warp",
1409
- hint: ""
1410
- },
1411
- {
1412
- value: "firebender",
1413
- label: "Firebender",
1414
- hint: ""
1415
- },
1416
- {
1417
- value: "factory",
1418
- label: "Factory Droid",
1419
- hint: ""
1420
- },
1421
- {
1422
- value: "mistral",
1423
- label: "Mistral Vibe",
1424
- hint: "Mistral"
1425
- }
1426
- ];
1427
- const POPULAR_AGENTS = [
1428
- "claude",
1429
- "copilot",
1430
- "cursor",
1431
- "windsurf",
1432
- "codex",
1433
- "gemini-cli"
1434
- ];
1230
+ const AGENT_SKILLS_DIRS = {
1231
+ ".claude": "claude-code",
1232
+ ".cursor": "cursor",
1233
+ ".codex": "codex",
1234
+ ".pi": "pi",
1235
+ ".gemini": "gemini-cli",
1236
+ ".agents": "amp",
1237
+ ".agent": "antigravity",
1238
+ ".roo": "roo-code",
1239
+ ".opencode": "opencode",
1240
+ ".factory": "factory-droid",
1241
+ ".vibe": "mistral-vibe",
1242
+ ".cline": "cline",
1243
+ ".goose": "goose"
1244
+ };
1245
+ const AGENT_RULES_MAP = {
1246
+ "github-copilot": "AGENTS.md",
1247
+ codex: "AGENTS.md",
1248
+ pi: "AGENTS.md",
1249
+ jules: "AGENTS.md",
1250
+ cursor: "AGENTS.md",
1251
+ amp: "AGENTS.md",
1252
+ "gemini-cli": "AGENTS.md",
1253
+ "kilo-code": "AGENTS.md",
1254
+ opencode: "AGENTS.md",
1255
+ "qwen-code": "AGENTS.md",
1256
+ "roo-code": "AGENTS.md",
1257
+ zed: "AGENTS.md",
1258
+ "factory-droid": "AGENTS.md",
1259
+ "mistral-vibe": "AGENTS.md",
1260
+ aider: "AGENTS.md",
1261
+ windsurf: "AGENTS.md",
1262
+ "claude-code": "CLAUDE.md",
1263
+ cline: ".clinerules",
1264
+ crush: "CRUSH.md",
1265
+ warp: "WARP.md",
1266
+ antigravity: ".agent/rules/ruler.md",
1267
+ "amazon-q": ".amazonq/rules/ruler_q_rules.md",
1268
+ "firebase-studio": ".idx/airules.md",
1269
+ "open-hands": ".openhands/microagents/repo.md",
1270
+ junie: ".junie/guidelines.md",
1271
+ "augment-code": ".augment/rules/ruler_augment_instructions.md",
1272
+ "trae-ai": ".trae/rules/project_rules.md",
1273
+ kiro: ".kiro/steering/ruler_kiro_instructions.md",
1274
+ "jetbrains-ai": ".aiassistant/rules/AGENTS.md",
1275
+ goose: ".goosehints"
1276
+ };
1277
+ const RULES_MARKER_START = "<!-- BEGIN agent-skills rules -->";
1278
+ const RULES_MARKER_END = "<!-- END agent-skills rules -->";
1435
1279
 
1436
1280
  //#endregion
1437
1281
  //#region src/fetch.ts
@@ -1441,7 +1285,9 @@ async function fetchTemplates() {
1441
1285
  const tempDir = mkdtempSync(path.join(tmpdir(), "agent-skills-"));
1442
1286
  try {
1443
1287
  execSync(`git clone --depth 1 --branch ${REPO_BRANCH} "${REPO_URL}" "${tempDir}"`, { stdio: "pipe" });
1444
- if (!existsSync(path.join(tempDir, "templates"))) throw new Error("templates/ directory not found in the repository");
1288
+ const templatesDir = path.join(tempDir, "templates");
1289
+ if (!existsSync(templatesDir)) throw new Error("templates/ directory not found in the repository");
1290
+ if (!existsSync(path.join(templatesDir, "AGENTS.md"))) throw new Error("templates/AGENTS.md not found in the repository");
1445
1291
  s.stop("Downloaded agent skills");
1446
1292
  return tempDir;
1447
1293
  } catch (err) {
@@ -1457,99 +1303,124 @@ function cleanupTemp(tempDir) {
1457
1303
  }
1458
1304
 
1459
1305
  //#endregion
1460
- //#region src/prompts.ts
1461
- function handleCancel(value) {
1462
- if (pD(value)) {
1463
- xe("Operation cancelled.");
1464
- process.exit(0);
1465
- }
1306
+ //#region src/rules.ts
1307
+ function escapeRegex(str) {
1308
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1466
1309
  }
1467
- async function promptExistingAction() {
1468
- const action = await ve({
1469
- message: "Existing .ruler/ directory found. What would you like to do?",
1470
- options: [{
1471
- value: "update",
1472
- label: "Update existing configuration"
1473
- }, {
1474
- value: "fresh",
1475
- label: "Fresh install (backs up and overwrites current)"
1476
- }]
1477
- });
1478
- handleCancel(action);
1479
- return action;
1480
- }
1481
- async function promptAgentSelection() {
1482
- const selected = await fe({
1483
- message: "Which AI agents do you use? (space to toggle, enter to confirm)",
1484
- options: [...RULER_AGENTS].sort((a, b) => {
1485
- const aPopular = POPULAR_AGENTS.includes(a.value);
1486
- const bPopular = POPULAR_AGENTS.includes(b.value);
1487
- if (aPopular && !bPopular) return -1;
1488
- if (!aPopular && bPopular) return 1;
1489
- return a.label.localeCompare(b.label);
1490
- }).map((a) => ({
1491
- value: a.value,
1492
- label: a.label,
1493
- hint: a.hint || void 0
1494
- })),
1495
- initialValues: ["claude"],
1496
- required: true
1497
- });
1498
- handleCancel(selected);
1499
- return selected;
1310
+ function injectRules(projectDir, agents, agentsContent) {
1311
+ const rulesFilePaths = /* @__PURE__ */ new Map();
1312
+ for (const agent of agents) {
1313
+ const rulesFile = AGENT_RULES_MAP[agent];
1314
+ if (rulesFile && !rulesFilePaths.has(rulesFile)) rulesFilePaths.set(rulesFile, agent);
1315
+ }
1316
+ const results = [];
1317
+ for (const rulesFilePath of rulesFilePaths.keys()) {
1318
+ const fullPath = path.join(projectDir, rulesFilePath);
1319
+ if (rulesFilePath.endsWith(".json")) {
1320
+ results.push({
1321
+ rulesFile: rulesFilePath,
1322
+ action: "skipped"
1323
+ });
1324
+ continue;
1325
+ }
1326
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
1327
+ const markedBlock = `\n${RULES_MARKER_START}\n${agentsContent}\n${RULES_MARKER_END}\n`;
1328
+ if (fs.existsSync(fullPath)) {
1329
+ const existing = fs.readFileSync(fullPath, "utf-8");
1330
+ const markerRegex = new RegExp(`${escapeRegex(RULES_MARKER_START)}[\\s\\S]*?${escapeRegex(RULES_MARKER_END)}`);
1331
+ if (markerRegex.test(existing)) {
1332
+ const updated = existing.replace(markerRegex, `${RULES_MARKER_START}\n${agentsContent}\n${RULES_MARKER_END}`);
1333
+ fs.writeFileSync(fullPath, updated, "utf-8");
1334
+ } else fs.writeFileSync(fullPath, existing + markedBlock, "utf-8");
1335
+ results.push({
1336
+ rulesFile: rulesFilePath,
1337
+ action: "updated"
1338
+ });
1339
+ } else {
1340
+ fs.writeFileSync(fullPath, markedBlock, "utf-8");
1341
+ results.push({
1342
+ rulesFile: rulesFilePath,
1343
+ action: "created"
1344
+ });
1345
+ }
1346
+ }
1347
+ return results;
1500
1348
  }
1501
1349
 
1502
1350
  //#endregion
1503
- //#region src/manifest.ts
1504
- const MANIFEST_FILENAME = ".library-manifest.json";
1505
- function getManifestPath(projectDir) {
1506
- return path.join(projectDir, ".ruler", "skills", MANIFEST_FILENAME);
1351
+ //#region src/skills.ts
1352
+ async function runSkillsAdd(projectDir, nonInteractive) {
1353
+ const args = nonInteractive ? [
1354
+ "skills",
1355
+ "add",
1356
+ "buiducnhat/agent-skills",
1357
+ "--skill",
1358
+ "*",
1359
+ "--all",
1360
+ "-y"
1361
+ ] : [
1362
+ "skills",
1363
+ "add",
1364
+ "buiducnhat/agent-skills",
1365
+ "--skill",
1366
+ "*"
1367
+ ];
1368
+ return new Promise((resolve) => {
1369
+ const chunks = [];
1370
+ const child = spawn("npx", args, {
1371
+ cwd: projectDir,
1372
+ stdio: [
1373
+ "inherit",
1374
+ "pipe",
1375
+ "inherit"
1376
+ ]
1377
+ });
1378
+ child.stdout.on("data", (chunk) => {
1379
+ process.stdout.write(chunk);
1380
+ chunks.push(chunk);
1381
+ });
1382
+ child.on("close", (code) => {
1383
+ const rawOutput = Buffer.concat(chunks).toString("utf-8");
1384
+ const success = code === 0;
1385
+ resolve({
1386
+ success,
1387
+ detectedAgents: success ? detectAgentsFromOutput(rawOutput) : [],
1388
+ rawOutput
1389
+ });
1390
+ });
1391
+ child.on("error", (err) => {
1392
+ resolve({
1393
+ success: false,
1394
+ detectedAgents: [],
1395
+ rawOutput: err.message
1396
+ });
1397
+ });
1398
+ });
1507
1399
  }
1508
- function readManifest(projectDir) {
1509
- const manifestPath = getManifestPath(projectDir);
1510
- if (!fs.existsSync(manifestPath)) return null;
1511
- try {
1512
- const raw = fs.readFileSync(manifestPath, "utf8");
1513
- const data = JSON.parse(raw);
1514
- if (typeof data.version !== "number" || !Array.isArray(data.skills) || !data.skills.every((s) => typeof s === "string")) return null;
1515
- return {
1516
- version: data.version,
1517
- skills: data.skills
1518
- };
1519
- } catch {
1520
- return null;
1400
+ function detectAgentsFromOutput(output) {
1401
+ const detected = /* @__PURE__ */ new Set();
1402
+ for (const [dirPrefix, agentId] of Object.entries(AGENT_SKILLS_DIRS)) {
1403
+ const dirName = dirPrefix.slice(1);
1404
+ const patterns = [
1405
+ new RegExp(`\\.${dirName}/skills/`, "i"),
1406
+ new RegExp(`Installing.*\\.${dirName}`, "i"),
1407
+ new RegExp(`Added.*\\.${dirName}`, "i"),
1408
+ new RegExp(`\\b${dirName}\\b`, "i")
1409
+ ];
1410
+ for (const pattern of patterns) if (pattern.test(output)) {
1411
+ detected.add(agentId);
1412
+ break;
1413
+ }
1521
1414
  }
1415
+ return Array.from(detected);
1522
1416
  }
1523
- function writeManifest(projectDir, skills) {
1524
- const manifestPath = getManifestPath(projectDir);
1525
- const manifestDir = path.dirname(manifestPath);
1526
- fs.mkdirSync(manifestDir, { recursive: true });
1527
- const manifest = {
1528
- version: 1,
1529
- skills: [...new Set(skills)].sort()
1530
- };
1531
- fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
1532
- }
1533
- function getTemplateSkillNames(tempDir) {
1534
- const skillsDir = path.join(tempDir, "templates", ".ruler", "skills");
1535
- if (!fs.existsSync(skillsDir)) return [];
1536
- return fs.readdirSync(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
1537
- }
1538
- function getInstalledSkillNames(projectDir) {
1539
- const skillsDir = path.join(projectDir, ".ruler", "skills");
1540
- if (!fs.existsSync(skillsDir)) return [];
1541
- return fs.readdirSync(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== MANIFEST_FILENAME).map((entry) => entry.name);
1542
- }
1543
- function computeDeprecatedSkills(oldManifest, newTemplateSkills) {
1544
- if (!oldManifest) return [];
1545
- const templateSet = new Set(newTemplateSkills);
1546
- return oldManifest.skills.filter((skill) => !templateSet.has(skill));
1547
- }
1548
- function computeCustomSkills(installedSkills, manifestSkills, templateSkills) {
1549
- const templateSet = new Set(templateSkills);
1550
- if (manifestSkills.length === 0) return installedSkills.filter((skill) => !templateSet.has(skill));
1551
- const manifestSet = new Set(manifestSkills);
1552
- return installedSkills.filter((skill) => !manifestSet.has(skill) && !templateSet.has(skill));
1417
+ function detectAgentsFromFilesystem(projectDir) {
1418
+ const detected = [];
1419
+ for (const [dirPrefix, agentId] of Object.entries(AGENT_SKILLS_DIRS)) {
1420
+ const skillsDir = path.join(projectDir, dirPrefix, "skills");
1421
+ if (fs.existsSync(skillsDir)) detected.push(agentId);
1422
+ }
1423
+ return detected;
1553
1424
  }
1554
1425
 
1555
1426
  //#endregion
@@ -1561,9 +1432,6 @@ function parseArgs(argv) {
1561
1432
  version: false
1562
1433
  };
1563
1434
  for (let i = 0; i < argv.length; i++) switch (argv[i]) {
1564
- case "--agents":
1565
- args.agents = argv[++i];
1566
- break;
1567
1435
  case "--non-interactive":
1568
1436
  args.nonInteractive = true;
1569
1437
  break;
@@ -1576,7 +1444,6 @@ function parseArgs(argv) {
1576
1444
  args.version = true;
1577
1445
  break;
1578
1446
  }
1579
- if (args.agents) args.nonInteractive = true;
1580
1447
  return args;
1581
1448
  }
1582
1449
  function copyDirectory(src, dest) {
@@ -1589,230 +1456,40 @@ function copyDirectory(src, dest) {
1589
1456
  else fs.copyFileSync(srcPath, destPath);
1590
1457
  }
1591
1458
  }
1592
- function getAgentOutputSkillsDir(projectDir, agent) {
1593
- return path.join(projectDir, `.${agent}`, "skills");
1594
- }
1595
- function preserveCustomSkills(tempDir, projectDir, agents) {
1596
- const templateSkills = new Set(getTemplateSkillNames(tempDir));
1597
- const manifestSkills = new Set(readManifest(projectDir)?.skills ?? []);
1598
- const backupDir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-skills-custom-skills-"));
1599
- const entries = [];
1600
- for (const agent of agents) {
1601
- const skillsDir = getAgentOutputSkillsDir(projectDir, agent);
1602
- if (!fs.existsSync(skillsDir)) continue;
1603
- const customSkills = fs.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).filter((skill) => !templateSkills.has(skill) && !manifestSkills.has(skill));
1604
- for (const skill of customSkills) {
1605
- const srcSkillDir = path.join(skillsDir, skill);
1606
- const destSkillDir = path.join(backupDir, agent, skill);
1607
- try {
1608
- copyDirectory(srcSkillDir, destSkillDir);
1609
- entries.push({
1610
- agent,
1611
- skill
1612
- });
1613
- } catch (err) {
1614
- const message = err instanceof Error ? err.message : String(err);
1615
- M.warn(`Failed to back up custom skill "${skill}" for agent "${agent}": ${message}`);
1616
- }
1617
- }
1618
- }
1619
- if (entries.length === 0) {
1620
- fs.rmSync(backupDir, {
1621
- recursive: true,
1622
- force: true
1623
- });
1624
- return null;
1625
- }
1626
- M.info(`Backed up ${entries.length} custom skill(s) from agent output directories`);
1627
- return {
1628
- backupDir,
1629
- entries
1630
- };
1631
- }
1632
- function restoreCustomSkills(preserved, projectDir) {
1633
- if (!preserved) return;
1634
- let restored = 0;
1635
- for (const entry of preserved.entries) {
1636
- const destSkillsDir = getAgentOutputSkillsDir(projectDir, entry.agent);
1637
- const srcSkillDir = path.join(preserved.backupDir, entry.agent, entry.skill);
1638
- const destSkillDir = path.join(destSkillsDir, entry.skill);
1639
- try {
1640
- if (!fs.existsSync(srcSkillDir)) continue;
1641
- fs.mkdirSync(destSkillsDir, { recursive: true });
1642
- fs.rmSync(destSkillDir, {
1643
- recursive: true,
1644
- force: true
1645
- });
1646
- copyDirectory(srcSkillDir, destSkillDir);
1647
- restored++;
1648
- } catch (err) {
1649
- const message = err instanceof Error ? err.message : String(err);
1650
- M.warn(`Failed to restore custom skill "${entry.skill}" for agent "${entry.agent}": ${message}`);
1651
- }
1652
- }
1653
- fs.rmSync(preserved.backupDir, {
1654
- recursive: true,
1655
- force: true
1656
- });
1657
- M.info(`Restored ${restored}/${preserved.entries.length} custom skill(s) to agent output directories`);
1658
- }
1659
- function copyDirectoryExcluding(src, dest, excludeNames) {
1660
- fs.mkdirSync(dest, { recursive: true });
1661
- const entries = fs.readdirSync(src, { withFileTypes: true });
1662
- for (const entry of entries) {
1663
- if (excludeNames.has(entry.name)) continue;
1664
- const srcPath = path.join(src, entry.name);
1665
- const destPath = path.join(dest, entry.name);
1666
- if (entry.isDirectory()) copyDirectory(srcPath, destPath);
1667
- else fs.copyFileSync(srcPath, destPath);
1668
- }
1669
- }
1670
- async function copyTemplates(tempDir, projectDir, action) {
1671
- const s = Y();
1672
- s.start("Copying templates to your project...");
1673
- const templatesDir = path.join(tempDir, "templates");
1674
- const srcRuler = path.join(templatesDir, ".ruler");
1675
- const srcClaude = path.join(templatesDir, ".claude");
1676
- const destRuler = path.join(projectDir, ".ruler");
1459
+ function copyClaudeTemplate(tempDir, projectDir) {
1460
+ const srcClaude = path.join(tempDir, "templates", ".claude");
1677
1461
  const destClaude = path.join(projectDir, ".claude");
1678
- const templateSkills = getTemplateSkillNames(tempDir);
1679
- const existingManifest = readManifest(projectDir);
1680
- const manifestSkills = existingManifest?.skills ?? [];
1681
- const installedSkills = getInstalledSkillNames(projectDir);
1682
- const deprecatedSkills = computeDeprecatedSkills(existingManifest, templateSkills);
1683
- const customSkills = computeCustomSkills(installedSkills, manifestSkills, templateSkills);
1684
- if (deprecatedSkills.length > 0) {
1685
- for (const skill of deprecatedSkills) {
1686
- const skillPath = path.join(destRuler, "skills", skill);
1687
- if (fs.existsSync(skillPath)) fs.rmSync(skillPath, {
1688
- recursive: true,
1689
- force: true
1690
- });
1691
- }
1692
- M.info(`Removing ${deprecatedSkills.length} deprecated library skill(s): ${deprecatedSkills.join(", ")}`);
1693
- }
1694
- if (action === "fresh") {
1695
- let rulerBackup;
1696
- let claudeBackup;
1697
- if (fs.existsSync(destRuler)) {
1698
- rulerBackup = `${destRuler}.backup-${Date.now()}`;
1699
- fs.renameSync(destRuler, rulerBackup);
1700
- M.info(`Backed up existing .ruler/ to ${path.basename(rulerBackup)}`);
1701
- }
1702
- if (fs.existsSync(destClaude)) {
1703
- claudeBackup = `${destClaude}.backup-${Date.now()}`;
1704
- fs.renameSync(destClaude, claudeBackup);
1705
- M.info(`Backed up existing .claude/ to ${path.basename(claudeBackup)}`);
1706
- }
1707
- if (fs.existsSync(srcRuler)) copyDirectory(srcRuler, destRuler);
1708
- if (rulerBackup && customSkills.length > 0) {
1709
- for (const skill of customSkills) {
1710
- const srcSkill = path.join(rulerBackup, "skills", skill);
1711
- const destSkill = path.join(destRuler, "skills", skill);
1712
- if (fs.existsSync(srcSkill)) copyDirectory(srcSkill, destSkill);
1713
- }
1714
- M.info(`Preserving ${customSkills.length} custom skill(s): ${customSkills.join(", ")}`);
1715
- }
1716
- if (fs.existsSync(srcClaude)) copyDirectory(srcClaude, destClaude);
1717
- } else {
1718
- if (!fs.existsSync(destRuler)) {
1719
- if (fs.existsSync(srcRuler)) copyDirectory(srcRuler, destRuler);
1720
- if (fs.existsSync(srcClaude)) copyDirectory(srcClaude, destClaude);
1721
- if (templateSkills.length > 0) writeManifest(projectDir, templateSkills);
1722
- makeScriptsExecutable(path.join(destRuler, "scripts"));
1723
- s.stop("Copied templates to project");
1724
- return;
1725
- }
1726
- if (fs.existsSync(srcRuler)) {
1727
- copyDirectoryExcluding(srcRuler, destRuler, new Set(["skills"]));
1728
- const srcSkillsDir = path.join(srcRuler, "skills");
1729
- const destSkillsDir = path.join(destRuler, "skills");
1730
- if (fs.existsSync(srcSkillsDir)) {
1731
- fs.mkdirSync(destSkillsDir, { recursive: true });
1732
- for (const skill of templateSkills) {
1733
- const srcSkill = path.join(srcSkillsDir, skill);
1734
- const destSkill = path.join(destSkillsDir, skill);
1735
- if (fs.existsSync(srcSkill)) copyDirectory(srcSkill, destSkill);
1736
- }
1737
- }
1738
- }
1739
- if (customSkills.length > 0) M.info(`Preserving ${customSkills.length} custom skill(s): ${customSkills.join(", ")}`);
1740
- if (fs.existsSync(srcClaude)) copyDirectory(srcClaude, destClaude);
1741
- }
1742
- if (templateSkills.length > 0) writeManifest(projectDir, templateSkills);
1743
- makeScriptsExecutable(path.join(destRuler, "scripts"));
1744
- s.stop("Copied templates to project");
1745
- }
1746
- function ensureRulerScripts(tempDir, projectDir) {
1747
- const srcScripts = path.join(tempDir, "templates", ".ruler", "scripts");
1748
- const destScripts = path.join(projectDir, ".ruler", "scripts");
1749
- if (!fs.existsSync(srcScripts)) return;
1750
- copyDirectory(srcScripts, destScripts);
1751
- makeScriptsExecutable(destScripts);
1752
- }
1753
- function makeScriptsExecutable(scriptsDir) {
1754
- if (!fs.existsSync(scriptsDir)) return;
1755
- for (const script of fs.readdirSync(scriptsDir).filter((fileName) => fileName.endsWith(".sh"))) fs.chmodSync(path.join(scriptsDir, script), 493);
1462
+ if (fs.existsSync(srcClaude)) copyDirectory(srcClaude, destClaude);
1756
1463
  }
1757
1464
  function printHelp() {
1758
1465
  console.log(`
1759
- @buiducnhat/agent-skills - Install AI agent skills for coding assistants
1466
+ @buiducnhat/agent-skills - Install AI agent workflow skills for coding assistants
1760
1467
 
1761
1468
  Usage: npx @buiducnhat/agent-skills [options]
1762
1469
 
1763
1470
  Options:
1764
- --agents <list> Comma-separated agent IDs (e.g., claude,cursor,copilot)
1765
- --non-interactive Skip all prompts, use defaults
1471
+ --non-interactive Skip interactive prompts (installs all skills to all agents)
1766
1472
  -h, --help Show this help message
1767
1473
  -v, --version Show version
1768
1474
 
1769
1475
  Examples:
1770
1476
  npx @buiducnhat/agent-skills
1771
- npx @buiducnhat/agent-skills --agents claude,cursor
1772
- npx @buiducnhat/agent-skills --agents claude --non-interactive
1773
-
1774
- Supported agents:
1775
- claude, copilot, cursor, windsurf, codex, gemini-cli, amp, cline, roo,
1776
- aider, antigravity, pi, jules, kiro, kilocode, crush, amazonqcli,
1777
- firebase, openhands, junie, jetbrains-ai, augmentcode, opencode,
1778
- goose, qwen, zed, trae, warp, firebender, factory, mistral
1477
+ npx @buiducnhat/agent-skills --non-interactive
1779
1478
  `);
1780
1479
  }
1781
- function printSummary(agents, projectDir) {
1480
+ function printSummary(agents, results) {
1782
1481
  M.success("Installation complete!");
1783
1482
  M.message("");
1784
1483
  M.message("What was set up:");
1785
- M.message(` .claude/ - Claude vibe coding settings`);
1786
- M.message(` .ruler/AGENTS.md - Agent instructions`);
1787
- M.message(` .ruler/ruler.toml - Ruler config (agents: ${agents.join(", ")})`);
1788
- const counts = countSkillsByType(projectDir);
1789
- if (counts.custom > 0) M.message(` .ruler/skills/ - ${counts.library} library + ${counts.custom} custom skill(s)`);
1790
- else M.message(` .ruler/skills/ - ${counts.library} workflow skills`);
1484
+ M.message(" .claude/ - Claude Code settings");
1485
+ for (const result of results) if (result.action !== "skipped") M.message(` ${result.rulesFile} - ${result.action}`);
1791
1486
  M.message("");
1792
- M.message("Agent configurations generated for:");
1487
+ M.message("Agent configurations updated for:");
1793
1488
  for (const agent of agents) M.message(` - ${agent}`);
1794
1489
  M.message("");
1795
1490
  M.message("Next steps:");
1796
- M.message(" 1. Review .ruler/AGENTS.md for agent instructions");
1797
- M.message(" 2. Customize .ruler/ruler.toml if needed");
1798
- M.message(" 3. Commit the generated files to your repository");
1799
- }
1800
- function countSkillsByType(projectDir) {
1801
- const skillsDir = path.join(projectDir, ".ruler", "skills");
1802
- if (!fs.existsSync(skillsDir)) return {
1803
- library: 0,
1804
- custom: 0,
1805
- total: 0
1806
- };
1807
- const manifest = readManifest(projectDir);
1808
- const manifestSkills = new Set(manifest?.skills ?? []);
1809
- const allSkills = fs.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
1810
- const library = allSkills.filter((s) => manifestSkills.has(s)).length;
1811
- return {
1812
- library,
1813
- custom: allSkills.length - library,
1814
- total: allSkills.length
1815
- };
1491
+ M.message(" 1. Review the updated agent rules files");
1492
+ M.message(" 2. Commit the generated files to your repository");
1816
1493
  }
1817
1494
 
1818
1495
  //#endregion
@@ -1837,35 +1514,30 @@ async function main() {
1837
1514
  }
1838
1515
  Ie(import_picocolors.default.bold(import_picocolors.default.cyan(" Agent Skills Installer ")));
1839
1516
  const cwd = process.cwd();
1840
- const rulerDir = path.join(cwd, ".ruler");
1841
- const existingInstall = fs.existsSync(rulerDir);
1842
- let action = "fresh";
1843
- if (existingInstall) if (args.nonInteractive) {
1844
- action = "update";
1845
- M.info("Existing .ruler/ found, updating...");
1846
- } else action = await promptExistingAction();
1847
- let selectedAgents;
1848
- if (args.agents) {
1849
- selectedAgents = args.agents.split(",").map((a) => a.trim());
1850
- M.info(`Using agents: ${selectedAgents.join(", ")}`);
1851
- } else if (args.nonInteractive) {
1852
- selectedAgents = ["claude"];
1853
- M.info("Using default agent: claude");
1854
- } else selectedAgents = await promptAgentSelection();
1855
- if (selectedAgents.length === 0) {
1856
- xe("No agents selected.");
1517
+ M.step("Installing skills via skills CLI...");
1518
+ const skillsResult = await runSkillsAdd(cwd, args.nonInteractive);
1519
+ if (!skillsResult.success) {
1520
+ xe(import_picocolors.default.red("Skills CLI failed. See errors above.\nYou can try running manually: npx skills add buiducnhat/agent-skills --skill *"));
1857
1521
  process.exit(1);
1858
1522
  }
1523
+ let agents = detectAgentsFromOutput(skillsResult.rawOutput);
1524
+ if (agents.length === 0) {
1525
+ M.warn("Could not detect agents from skills CLI output. Scanning filesystem...");
1526
+ agents = detectAgentsFromFilesystem(cwd);
1527
+ }
1528
+ if (agents.length === 0) {
1529
+ M.warn("No agents detected. Skills may have been installed but rules injection was skipped.");
1530
+ Se(import_picocolors.default.yellow("Done. No agent rules files were updated."));
1531
+ process.exit(0);
1532
+ }
1533
+ M.info(`Detected agents: ${agents.join(", ")}`);
1859
1534
  let tempDir;
1860
1535
  try {
1861
1536
  tempDir = await fetchTemplates();
1862
- await copyTemplates(tempDir, cwd, action);
1863
- configureRulerToml(cwd, selectedAgents);
1864
- const preserved = preserveCustomSkills(tempDir, cwd, selectedAgents);
1865
- await runRulerApply(cwd, selectedAgents);
1866
- restoreCustomSkills(preserved, cwd);
1867
- ensureRulerScripts(tempDir, cwd);
1868
- printSummary(selectedAgents, cwd);
1537
+ const agentsContent = fs.readFileSync(path.join(tempDir, "templates", "AGENTS.md"), "utf-8");
1538
+ const results = injectRules(cwd, agents, agentsContent);
1539
+ copyClaudeTemplate(tempDir, cwd);
1540
+ printSummary(agents, results);
1869
1541
  Se(import_picocolors.default.green("Done! Your AI agent skills are ready."));
1870
1542
  } catch (err) {
1871
1543
  const message = err instanceof Error ? err.message : String(err);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@buiducnhat/agent-skills",
3
- "version": "0.2.1",
4
- "description": "Install AI agent skills and ruler configuration for coding assistants",
3
+ "version": "0.3.0",
4
+ "description": "Install AI agent workflow skills for coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "agent-skills": "./dist/index.js"
@@ -41,7 +41,7 @@
41
41
  "ai",
42
42
  "agent",
43
43
  "skills",
44
- "ruler",
44
+ "workflow",
45
45
  "claude",
46
46
  "copilot",
47
47
  "cursor"