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:
2026-03-19 08:07:08 +07:00
parent b591776839
commit f411f52515
3 changed files with 367 additions and 7 deletions

View File

@@ -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

View File

@@ -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;
}

View 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;
}