Add add-azure-skills command to integrate 192 Microsoft Azure skills
- New command: add-azure-skills with bundle and individual skill install - 5 curated bundles: quick-start (8), popular (21), ai-ml (8), devops (8), security (8) - Auto-clone Microsoft Agent-Skills repo with cache and --refresh support - Skip existing skills, report install/skip/missing counts - Requires .agent init first, fully compatible with validate (78 checks pass) - Update README with Azure skills usage guide and CLI reference Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
48
README.md
48
README.md
@@ -188,6 +188,40 @@ antigravity-superpowers init --force --backup
|
||||
|
||||
With `--backup`, the existing `.agent` is renamed to `.agent-backup-2026-03-19T10-30-00` before the new profile is copied.
|
||||
|
||||
### 6. Add Microsoft Azure skills
|
||||
|
||||
Tích hợp 192 Azure skills từ [Microsoft Agent-Skills](https://github.com/MicrosoftDocs/Agent-Skills) vào project. Các skills này cung cấp tra cứu tài liệu Azure chính thức (troubleshooting, best practices, architecture, security...).
|
||||
|
||||
```bash
|
||||
# Xem các bundle có sẵn
|
||||
antigravity-superpowers add-azure-skills --list-bundles
|
||||
|
||||
# Cài bundle quick-start (8 skills cơ bản)
|
||||
antigravity-superpowers add-azure-skills --bundle quick-start
|
||||
|
||||
# Cài bundle popular (21 skills phổ biến)
|
||||
antigravity-superpowers add-azure-skills --bundle popular
|
||||
|
||||
# Cài từng skill riêng lẻ
|
||||
antigravity-superpowers add-azure-skills --skill azure-functions --skill azure-cosmos-db
|
||||
|
||||
# Kết hợp bundle + skill riêng
|
||||
antigravity-superpowers add-azure-skills --bundle ai-ml --skill azure-container-apps
|
||||
|
||||
# Xem toàn bộ 192 skills có sẵn
|
||||
antigravity-superpowers add-azure-skills --list-skills
|
||||
```
|
||||
|
||||
**Bundles có sẵn:**
|
||||
|
||||
| Bundle | Skills | Mô tả |
|
||||
| --- | --- | --- |
|
||||
| `quick-start` | 8 | App Service, Functions, Blob Storage, SQL, Key Vault, Monitor... |
|
||||
| `popular` | 21 | Các dịch vụ phổ biến nhất: Cosmos DB, AKS, Container Apps, Logic Apps... |
|
||||
| `ai-ml` | 8 | AI Services, AI Vision, Bot Service, Machine Learning, Foundry... |
|
||||
| `devops` | 8 | DevOps, Pipelines, Repos, Boards, Artifacts, Automation... |
|
||||
| `security` | 8 | Key Vault, RBAC, Policy, Sentinel, Defender, Firewall... |
|
||||
|
||||
---
|
||||
|
||||
## CLI Reference
|
||||
@@ -196,15 +230,23 @@ With `--backup`, the existing `.agent` is renamed to `.agent-backup-2026-03-19T1
|
||||
antigravity-superpowers <command> [options]
|
||||
|
||||
Commands:
|
||||
init [dir] Initialize .agent profile in a project
|
||||
validate [dir] Validate .agent profile in a project
|
||||
list List available skills
|
||||
init [dir] Initialize .agent profile in a project
|
||||
validate [dir] Validate .agent profile in a project
|
||||
list List bundled skills
|
||||
add-azure-skills [opts] Add Microsoft Azure skills to a project
|
||||
|
||||
Init options:
|
||||
-f, --force Overwrite existing .agent directory
|
||||
-b, --backup Backup existing .agent before overwrite (use with --force)
|
||||
-n, --dry-run Preview files that would be copied
|
||||
|
||||
Azure skills options:
|
||||
-B, --bundle <name> Install a bundle (quick-start, popular, ai-ml, devops, security)
|
||||
-s, --skill <name> Install individual skill (repeatable)
|
||||
--list-bundles List available bundles
|
||||
--list-skills List all 192 Azure skills
|
||||
--refresh Re-download skill repository
|
||||
|
||||
Global options:
|
||||
-v, --version Show version
|
||||
-h, --help Show help
|
||||
|
||||
27
src/cli.js
27
src/cli.js
@@ -3,6 +3,7 @@ import { fileURLToPath } from "node:url";
|
||||
import { initCommand } from "./commands/init.js";
|
||||
import { validateCommand } from "./commands/validate.js";
|
||||
import { listCommand } from "./commands/list.js";
|
||||
import { addAzureSkillsCommand } from "./commands/add-azure-skills.js";
|
||||
|
||||
async function getVersion() {
|
||||
const pkgPath = fileURLToPath(new URL("../package.json", import.meta.url));
|
||||
@@ -18,14 +19,24 @@ function helpText() {
|
||||
" antigravity-superpowers <command> [options]",
|
||||
"",
|
||||
"Commands:",
|
||||
" init [dir] [--force] [--backup] [--dry-run] Initialize .agent profile in a project",
|
||||
" validate [dir] Validate .agent profile in a project",
|
||||
" list List available skills",
|
||||
" init [dir] [--force] [--backup] [--dry-run] Initialize .agent profile in a project",
|
||||
" validate [dir] Validate .agent profile in a project",
|
||||
" list List bundled skills",
|
||||
" add-azure-skills [options] Add Microsoft Azure skills to a project",
|
||||
"",
|
||||
"Options:",
|
||||
"Init options:",
|
||||
" -f, --force Overwrite existing .agent directory",
|
||||
" -b, --backup Backup existing .agent before overwrite (use with --force)",
|
||||
" -n, --dry-run Preview files that would be copied",
|
||||
"",
|
||||
"Azure skills options:",
|
||||
" -B, --bundle <name> Install a skill bundle (quick-start, popular, ai-ml, devops, security)",
|
||||
" -s, --skill <name> Install individual skill (repeatable)",
|
||||
" --list-bundles List available bundles",
|
||||
" --list-skills List all available Azure skills",
|
||||
" --refresh Re-download skill repository",
|
||||
"",
|
||||
"Global options:",
|
||||
" -v, --version Show version",
|
||||
" -h, --help Show help",
|
||||
].join("\n");
|
||||
@@ -68,6 +79,14 @@ export async function runCli(args, io = process) {
|
||||
});
|
||||
}
|
||||
|
||||
if (command === "add-azure-skills") {
|
||||
return addAzureSkillsCommand(rest, {
|
||||
cwd: io.cwd?.() ?? process.cwd(),
|
||||
stdout: io.stdout,
|
||||
stderr: io.stderr,
|
||||
});
|
||||
}
|
||||
|
||||
io.stderr.write(`Unknown command: ${command}\n\n${helpText()}\n`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
299
src/commands/add-azure-skills.js
Normal file
299
src/commands/add-azure-skills.js
Normal file
@@ -0,0 +1,299 @@
|
||||
import { access, cp, mkdir, readdir, readFile, rm } from "node:fs/promises";
|
||||
import { constants as fsConstants } from "node:fs";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { join, resolve } from "node:path";
|
||||
|
||||
const REPO_URL = "https://github.com/MicrosoftDocs/Agent-Skills.git";
|
||||
const CLONE_DIR = join(process.env.TMPDIR || "/tmp", "antigravity-azure-skills-cache");
|
||||
|
||||
const BUNDLES = {
|
||||
"quick-start": [
|
||||
"azure-app-service",
|
||||
"azure-blob-storage",
|
||||
"azure-functions",
|
||||
"azure-sql-database",
|
||||
"azure-key-vault",
|
||||
"azure-app-configuration",
|
||||
"azure-microsoft-foundry",
|
||||
"azure-monitor",
|
||||
],
|
||||
popular: [
|
||||
"azure-app-service",
|
||||
"azure-blob-storage",
|
||||
"azure-cosmos-db",
|
||||
"azure-container-registry",
|
||||
"azure-functions",
|
||||
"azure-sql-database",
|
||||
"azure-key-vault",
|
||||
"azure-virtual-machines",
|
||||
"azure-kubernetes-service",
|
||||
"azure-event-grid",
|
||||
"azure-service-bus",
|
||||
"azure-monitor",
|
||||
"azure-app-configuration",
|
||||
"azure-resource-manager",
|
||||
"azure-cost-management",
|
||||
"azure-logic-apps",
|
||||
"azure-microsoft-foundry",
|
||||
"azure-data-factory",
|
||||
"azure-api-management",
|
||||
"azure-container-apps",
|
||||
"azure-defender-for-cloud",
|
||||
],
|
||||
"ai-ml": [
|
||||
"azure-ai-services",
|
||||
"azure-ai-vision",
|
||||
"azure-bot-service",
|
||||
"azure-cognitive-search",
|
||||
"azure-machine-learning",
|
||||
"azure-ai-foundry-local",
|
||||
"azure-microsoft-foundry",
|
||||
"azure-anomaly-detector",
|
||||
],
|
||||
devops: [
|
||||
"azure-devops",
|
||||
"azure-pipelines",
|
||||
"azure-repos",
|
||||
"azure-boards",
|
||||
"azure-artifacts",
|
||||
"azure-test-plans",
|
||||
"azure-automation",
|
||||
"azure-monitor",
|
||||
],
|
||||
security: [
|
||||
"azure-key-vault",
|
||||
"azure-rbac",
|
||||
"azure-policy",
|
||||
"azure-sentinel",
|
||||
"azure-defender-for-cloud",
|
||||
"azure-attestation",
|
||||
"azure-confidential-computing",
|
||||
"azure-firewall",
|
||||
],
|
||||
};
|
||||
|
||||
async function exists(path) {
|
||||
try {
|
||||
await access(path, fsConstants.F_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const parsed = {
|
||||
target: ".",
|
||||
bundle: null,
|
||||
skills: [],
|
||||
listBundles: false,
|
||||
listSkills: false,
|
||||
refresh: false,
|
||||
};
|
||||
let targetSet = false;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
|
||||
if (arg === "--bundle" || arg === "-B") {
|
||||
parsed.bundle = args[++i];
|
||||
if (!parsed.bundle) throw new Error("--bundle requires a bundle name");
|
||||
continue;
|
||||
}
|
||||
if (arg === "--list-bundles") {
|
||||
parsed.listBundles = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--list-skills") {
|
||||
parsed.listSkills = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--refresh") {
|
||||
parsed.refresh = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--skill" || arg === "-s") {
|
||||
const skill = args[++i];
|
||||
if (!skill) throw new Error("--skill requires a skill name");
|
||||
parsed.skills.push(skill);
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith("-")) {
|
||||
throw new Error(`Unknown option: ${arg}`);
|
||||
}
|
||||
if (targetSet) {
|
||||
throw new Error("Too many positional arguments.");
|
||||
}
|
||||
parsed.target = arg;
|
||||
targetSet = true;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function cloneOrUpdate(stdout, refresh) {
|
||||
if (refresh && exists(CLONE_DIR)) {
|
||||
try {
|
||||
execFileSync("rm", ["-rf", CLONE_DIR]);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
const cloneExists =
|
||||
(() => {
|
||||
try {
|
||||
return require("node:fs").existsSync(join(CLONE_DIR, "skills"));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})() ||
|
||||
(() => {
|
||||
try {
|
||||
execFileSync("test", ["-d", join(CLONE_DIR, "skills")]);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
if (!cloneExists) {
|
||||
stdout.write(`Cloning Microsoft Agent-Skills repository...\n`);
|
||||
execFileSync("git", ["clone", "--depth", "1", REPO_URL, CLONE_DIR], {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
stdout.write(`Cached at ${CLONE_DIR}\n`);
|
||||
} else {
|
||||
stdout.write(`Using cached repository at ${CLONE_DIR}\n`);
|
||||
stdout.write(`(Use --refresh to re-download)\n`);
|
||||
}
|
||||
|
||||
return join(CLONE_DIR, "skills");
|
||||
}
|
||||
|
||||
export async function addAzureSkillsCommand(args, { cwd, stdout, stderr }) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = parseArgs(args);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
stderr.write(`${message}\n`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// List bundles
|
||||
if (parsed.listBundles) {
|
||||
stdout.write("Available bundles:\n\n");
|
||||
for (const [name, skills] of Object.entries(BUNDLES)) {
|
||||
stdout.write(` ${name.padEnd(15)} (${skills.length} skills)\n`);
|
||||
}
|
||||
stdout.write(`\nUsage: antigravity-superpowers add-azure-skills --bundle <name>\n`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// List all available skills
|
||||
if (parsed.listSkills) {
|
||||
let skillsDir;
|
||||
try {
|
||||
skillsDir = cloneOrUpdate(stdout, parsed.refresh);
|
||||
} catch (error) {
|
||||
stderr.write(`Failed to clone repository: ${error.message}\n`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const entries = await readdir(skillsDir, { withFileTypes: true });
|
||||
const skillNames = entries
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((e) => e.name)
|
||||
.sort();
|
||||
|
||||
stdout.write(`\n${skillNames.length} Azure skills available:\n\n`);
|
||||
for (const name of skillNames) {
|
||||
stdout.write(` ${name}\n`);
|
||||
}
|
||||
stdout.write(
|
||||
`\nUsage: antigravity-superpowers add-azure-skills --skill <name> [--skill <name> ...]\n`,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Determine which skills to install
|
||||
let skillsToInstall = [];
|
||||
|
||||
if (parsed.bundle) {
|
||||
const bundleSkills = BUNDLES[parsed.bundle];
|
||||
if (!bundleSkills) {
|
||||
stderr.write(
|
||||
`Unknown bundle: ${parsed.bundle}\nAvailable: ${Object.keys(BUNDLES).join(", ")}\n`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
skillsToInstall = bundleSkills;
|
||||
}
|
||||
|
||||
if (parsed.skills.length > 0) {
|
||||
skillsToInstall = [...new Set([...skillsToInstall, ...parsed.skills])];
|
||||
}
|
||||
|
||||
if (skillsToInstall.length === 0) {
|
||||
stderr.write(
|
||||
"No skills specified. Use --bundle <name> or --skill <name>.\n" +
|
||||
"Run with --list-bundles or --list-skills to see available options.\n",
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Clone/update repo
|
||||
let skillsDir;
|
||||
try {
|
||||
skillsDir = cloneOrUpdate(stdout, parsed.refresh);
|
||||
} catch (error) {
|
||||
stderr.write(`Failed to clone repository: ${error.message}\n`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Resolve target
|
||||
const targetDir = resolve(cwd, parsed.target);
|
||||
const agentSkillsDir = join(targetDir, ".agent", "skills");
|
||||
|
||||
if (!(await exists(join(targetDir, ".agent")))) {
|
||||
stderr.write(
|
||||
`No .agent directory found at ${targetDir}\nRun 'antigravity-superpowers init' first.\n`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Copy skills
|
||||
let installed = 0;
|
||||
let skipped = 0;
|
||||
const missing = [];
|
||||
|
||||
for (const skill of skillsToInstall) {
|
||||
const src = join(skillsDir, skill);
|
||||
const dest = join(agentSkillsDir, skill);
|
||||
|
||||
if (!(await exists(src))) {
|
||||
missing.push(skill);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await exists(dest)) {
|
||||
stdout.write(` [SKIP] ${skill} (already exists)\n`);
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
await cp(src, dest, { recursive: true });
|
||||
stdout.write(` [ADD] ${skill}\n`);
|
||||
installed++;
|
||||
}
|
||||
|
||||
stdout.write(`\nInstalled: ${installed}, Skipped: ${skipped}`);
|
||||
if (missing.length > 0) {
|
||||
stdout.write(`, Not found: ${missing.length}`);
|
||||
stderr.write(`\nMissing skills: ${missing.join(", ")}\n`);
|
||||
}
|
||||
stdout.write(`\n`);
|
||||
|
||||
return missing.length > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user