@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.
- package/dist/index.js +196 -524
- 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
|
|
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
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
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
|
-
|
|
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/
|
|
1461
|
-
function
|
|
1462
|
-
|
|
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
|
-
|
|
1468
|
-
const
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
const
|
|
1487
|
-
|
|
1488
|
-
if (
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
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/
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
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
|
|
1509
|
-
const
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
const
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
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
|
|
1524
|
-
const
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
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
|
|
1593
|
-
|
|
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
|
-
|
|
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
|
-
--
|
|
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 --
|
|
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,
|
|
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(
|
|
1786
|
-
M.message(` .
|
|
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
|
|
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
|
|
1797
|
-
M.message(" 2.
|
|
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
|
-
|
|
1841
|
-
const
|
|
1842
|
-
|
|
1843
|
-
|
|
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
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
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.
|
|
4
|
-
"description": "Install AI agent skills
|
|
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
|
-
"
|
|
44
|
+
"workflow",
|
|
45
45
|
"claude",
|
|
46
46
|
"copilot",
|
|
47
47
|
"cursor"
|