@buiducnhat/agent-skills 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +195 -454
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ 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";
|
|
10
|
+
import { execSync, spawn } from "node:child_process";
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
12
|
|
|
13
13
|
//#region \0rolldown/runtime.js
|
|
@@ -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
|
-
}
|
|
1466
|
-
}
|
|
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;
|
|
1306
|
+
//#region src/rules.ts
|
|
1307
|
+
function escapeRegex(str) {
|
|
1308
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1480
1309
|
}
|
|
1481
|
-
|
|
1482
|
-
const
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
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,163 +1456,40 @@ function copyDirectory(src, dest) {
|
|
|
1589
1456
|
else fs.copyFileSync(srcPath, destPath);
|
|
1590
1457
|
}
|
|
1591
1458
|
}
|
|
1592
|
-
function
|
|
1593
|
-
|
|
1594
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
1595
|
-
for (const entry of entries) {
|
|
1596
|
-
if (excludeNames.has(entry.name)) continue;
|
|
1597
|
-
const srcPath = path.join(src, entry.name);
|
|
1598
|
-
const destPath = path.join(dest, entry.name);
|
|
1599
|
-
if (entry.isDirectory()) copyDirectory(srcPath, destPath);
|
|
1600
|
-
else fs.copyFileSync(srcPath, destPath);
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
async function copyTemplates(tempDir, projectDir, action) {
|
|
1604
|
-
const s = Y();
|
|
1605
|
-
s.start("Copying templates to your project...");
|
|
1606
|
-
const templatesDir = path.join(tempDir, "templates");
|
|
1607
|
-
const srcRuler = path.join(templatesDir, ".ruler");
|
|
1608
|
-
const srcClaude = path.join(templatesDir, ".claude");
|
|
1609
|
-
const destRuler = path.join(projectDir, ".ruler");
|
|
1459
|
+
function copyClaudeTemplate(tempDir, projectDir) {
|
|
1460
|
+
const srcClaude = path.join(tempDir, "templates", ".claude");
|
|
1610
1461
|
const destClaude = path.join(projectDir, ".claude");
|
|
1611
|
-
|
|
1612
|
-
const existingManifest = readManifest(projectDir);
|
|
1613
|
-
const manifestSkills = existingManifest?.skills ?? [];
|
|
1614
|
-
const installedSkills = getInstalledSkillNames(projectDir);
|
|
1615
|
-
const deprecatedSkills = computeDeprecatedSkills(existingManifest, templateSkills);
|
|
1616
|
-
const customSkills = computeCustomSkills(installedSkills, manifestSkills, templateSkills);
|
|
1617
|
-
if (deprecatedSkills.length > 0) {
|
|
1618
|
-
for (const skill of deprecatedSkills) {
|
|
1619
|
-
const skillPath = path.join(destRuler, "skills", skill);
|
|
1620
|
-
if (fs.existsSync(skillPath)) fs.rmSync(skillPath, {
|
|
1621
|
-
recursive: true,
|
|
1622
|
-
force: true
|
|
1623
|
-
});
|
|
1624
|
-
}
|
|
1625
|
-
M.info(`Removing ${deprecatedSkills.length} deprecated library skill(s): ${deprecatedSkills.join(", ")}`);
|
|
1626
|
-
}
|
|
1627
|
-
if (action === "fresh") {
|
|
1628
|
-
let rulerBackup;
|
|
1629
|
-
let claudeBackup;
|
|
1630
|
-
if (fs.existsSync(destRuler)) {
|
|
1631
|
-
rulerBackup = `${destRuler}.backup-${Date.now()}`;
|
|
1632
|
-
fs.renameSync(destRuler, rulerBackup);
|
|
1633
|
-
M.info(`Backed up existing .ruler/ to ${path.basename(rulerBackup)}`);
|
|
1634
|
-
}
|
|
1635
|
-
if (fs.existsSync(destClaude)) {
|
|
1636
|
-
claudeBackup = `${destClaude}.backup-${Date.now()}`;
|
|
1637
|
-
fs.renameSync(destClaude, claudeBackup);
|
|
1638
|
-
M.info(`Backed up existing .claude/ to ${path.basename(claudeBackup)}`);
|
|
1639
|
-
}
|
|
1640
|
-
if (fs.existsSync(srcRuler)) copyDirectory(srcRuler, destRuler);
|
|
1641
|
-
if (rulerBackup && customSkills.length > 0) {
|
|
1642
|
-
for (const skill of customSkills) {
|
|
1643
|
-
const srcSkill = path.join(rulerBackup, "skills", skill);
|
|
1644
|
-
const destSkill = path.join(destRuler, "skills", skill);
|
|
1645
|
-
if (fs.existsSync(srcSkill)) copyDirectory(srcSkill, destSkill);
|
|
1646
|
-
}
|
|
1647
|
-
M.info(`Preserving ${customSkills.length} custom skill(s): ${customSkills.join(", ")}`);
|
|
1648
|
-
}
|
|
1649
|
-
if (fs.existsSync(srcClaude)) copyDirectory(srcClaude, destClaude);
|
|
1650
|
-
} else {
|
|
1651
|
-
if (!fs.existsSync(destRuler)) {
|
|
1652
|
-
if (fs.existsSync(srcRuler)) copyDirectory(srcRuler, destRuler);
|
|
1653
|
-
if (fs.existsSync(srcClaude)) copyDirectory(srcClaude, destClaude);
|
|
1654
|
-
if (templateSkills.length > 0) writeManifest(projectDir, templateSkills);
|
|
1655
|
-
makeScriptsExecutable(path.join(destRuler, "scripts"));
|
|
1656
|
-
s.stop("Copied templates to project");
|
|
1657
|
-
return;
|
|
1658
|
-
}
|
|
1659
|
-
if (fs.existsSync(srcRuler)) {
|
|
1660
|
-
copyDirectoryExcluding(srcRuler, destRuler, new Set(["skills"]));
|
|
1661
|
-
const srcSkillsDir = path.join(srcRuler, "skills");
|
|
1662
|
-
const destSkillsDir = path.join(destRuler, "skills");
|
|
1663
|
-
if (fs.existsSync(srcSkillsDir)) {
|
|
1664
|
-
fs.mkdirSync(destSkillsDir, { recursive: true });
|
|
1665
|
-
for (const skill of templateSkills) {
|
|
1666
|
-
const srcSkill = path.join(srcSkillsDir, skill);
|
|
1667
|
-
const destSkill = path.join(destSkillsDir, skill);
|
|
1668
|
-
if (fs.existsSync(srcSkill)) copyDirectory(srcSkill, destSkill);
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
if (customSkills.length > 0) M.info(`Preserving ${customSkills.length} custom skill(s): ${customSkills.join(", ")}`);
|
|
1673
|
-
if (fs.existsSync(srcClaude)) copyDirectory(srcClaude, destClaude);
|
|
1674
|
-
}
|
|
1675
|
-
if (templateSkills.length > 0) writeManifest(projectDir, templateSkills);
|
|
1676
|
-
makeScriptsExecutable(path.join(destRuler, "scripts"));
|
|
1677
|
-
s.stop("Copied templates to project");
|
|
1678
|
-
}
|
|
1679
|
-
function ensureRulerScripts(tempDir, projectDir) {
|
|
1680
|
-
const srcScripts = path.join(tempDir, "templates", ".ruler", "scripts");
|
|
1681
|
-
const destScripts = path.join(projectDir, ".ruler", "scripts");
|
|
1682
|
-
if (!fs.existsSync(srcScripts)) return;
|
|
1683
|
-
copyDirectory(srcScripts, destScripts);
|
|
1684
|
-
makeScriptsExecutable(destScripts);
|
|
1685
|
-
}
|
|
1686
|
-
function makeScriptsExecutable(scriptsDir) {
|
|
1687
|
-
if (!fs.existsSync(scriptsDir)) return;
|
|
1688
|
-
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);
|
|
1689
1463
|
}
|
|
1690
1464
|
function printHelp() {
|
|
1691
1465
|
console.log(`
|
|
1692
|
-
@buiducnhat/agent-skills - Install AI agent skills for coding assistants
|
|
1466
|
+
@buiducnhat/agent-skills - Install AI agent workflow skills for coding assistants
|
|
1693
1467
|
|
|
1694
1468
|
Usage: npx @buiducnhat/agent-skills [options]
|
|
1695
1469
|
|
|
1696
1470
|
Options:
|
|
1697
|
-
--
|
|
1698
|
-
--non-interactive Skip all prompts, use defaults
|
|
1471
|
+
--non-interactive Skip interactive prompts (installs all skills to all agents)
|
|
1699
1472
|
-h, --help Show this help message
|
|
1700
1473
|
-v, --version Show version
|
|
1701
1474
|
|
|
1702
1475
|
Examples:
|
|
1703
1476
|
npx @buiducnhat/agent-skills
|
|
1704
|
-
npx @buiducnhat/agent-skills --
|
|
1705
|
-
npx @buiducnhat/agent-skills --agents claude --non-interactive
|
|
1706
|
-
|
|
1707
|
-
Supported agents:
|
|
1708
|
-
claude, copilot, cursor, windsurf, codex, gemini-cli, amp, cline, roo,
|
|
1709
|
-
aider, antigravity, pi, jules, kiro, kilocode, crush, amazonqcli,
|
|
1710
|
-
firebase, openhands, junie, jetbrains-ai, augmentcode, opencode,
|
|
1711
|
-
goose, qwen, zed, trae, warp, firebender, factory, mistral
|
|
1477
|
+
npx @buiducnhat/agent-skills --non-interactive
|
|
1712
1478
|
`);
|
|
1713
1479
|
}
|
|
1714
|
-
function printSummary(agents,
|
|
1480
|
+
function printSummary(agents, results) {
|
|
1715
1481
|
M.success("Installation complete!");
|
|
1716
1482
|
M.message("");
|
|
1717
1483
|
M.message("What was set up:");
|
|
1718
|
-
M.message(
|
|
1719
|
-
M.message(` .
|
|
1720
|
-
M.message(` .ruler/ruler.toml - Ruler config (agents: ${agents.join(", ")})`);
|
|
1721
|
-
const counts = countSkillsByType(projectDir);
|
|
1722
|
-
if (counts.custom > 0) M.message(` .ruler/skills/ - ${counts.library} library + ${counts.custom} custom skill(s)`);
|
|
1723
|
-
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}`);
|
|
1724
1486
|
M.message("");
|
|
1725
|
-
M.message("Agent configurations
|
|
1487
|
+
M.message("Agent configurations updated for:");
|
|
1726
1488
|
for (const agent of agents) M.message(` - ${agent}`);
|
|
1727
1489
|
M.message("");
|
|
1728
1490
|
M.message("Next steps:");
|
|
1729
|
-
M.message(" 1. Review
|
|
1730
|
-
M.message(" 2.
|
|
1731
|
-
M.message(" 3. Commit the generated files to your repository");
|
|
1732
|
-
}
|
|
1733
|
-
function countSkillsByType(projectDir) {
|
|
1734
|
-
const skillsDir = path.join(projectDir, ".ruler", "skills");
|
|
1735
|
-
if (!fs.existsSync(skillsDir)) return {
|
|
1736
|
-
library: 0,
|
|
1737
|
-
custom: 0,
|
|
1738
|
-
total: 0
|
|
1739
|
-
};
|
|
1740
|
-
const manifest = readManifest(projectDir);
|
|
1741
|
-
const manifestSkills = new Set(manifest?.skills ?? []);
|
|
1742
|
-
const allSkills = fs.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1743
|
-
const library = allSkills.filter((s) => manifestSkills.has(s)).length;
|
|
1744
|
-
return {
|
|
1745
|
-
library,
|
|
1746
|
-
custom: allSkills.length - library,
|
|
1747
|
-
total: allSkills.length
|
|
1748
|
-
};
|
|
1491
|
+
M.message(" 1. Review the updated agent rules files");
|
|
1492
|
+
M.message(" 2. Commit the generated files to your repository");
|
|
1749
1493
|
}
|
|
1750
1494
|
|
|
1751
1495
|
//#endregion
|
|
@@ -1770,33 +1514,30 @@ async function main() {
|
|
|
1770
1514
|
}
|
|
1771
1515
|
Ie(import_picocolors.default.bold(import_picocolors.default.cyan(" Agent Skills Installer ")));
|
|
1772
1516
|
const cwd = process.cwd();
|
|
1773
|
-
|
|
1774
|
-
const
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
action = "update";
|
|
1778
|
-
M.info("Existing .ruler/ found, updating...");
|
|
1779
|
-
} else action = await promptExistingAction();
|
|
1780
|
-
let selectedAgents;
|
|
1781
|
-
if (args.agents) {
|
|
1782
|
-
selectedAgents = args.agents.split(",").map((a) => a.trim());
|
|
1783
|
-
M.info(`Using agents: ${selectedAgents.join(", ")}`);
|
|
1784
|
-
} else if (args.nonInteractive) {
|
|
1785
|
-
selectedAgents = ["claude"];
|
|
1786
|
-
M.info("Using default agent: claude");
|
|
1787
|
-
} else selectedAgents = await promptAgentSelection();
|
|
1788
|
-
if (selectedAgents.length === 0) {
|
|
1789
|
-
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 *"));
|
|
1790
1521
|
process.exit(1);
|
|
1791
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(", ")}`);
|
|
1792
1534
|
let tempDir;
|
|
1793
1535
|
try {
|
|
1794
1536
|
tempDir = await fetchTemplates();
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
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);
|
|
1800
1541
|
Se(import_picocolors.default.green("Done! Your AI agent skills are ready."));
|
|
1801
1542
|
} catch (err) {
|
|
1802
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"
|