From 2c4bd8e98da206979f39e2b4b02542b63bf66271 Mon Sep 17 00:00:00 2001 From: miguel belmonte granados Date: Tue, 26 May 2026 21:14:41 +0200 Subject: [PATCH] feat: add --tech / -t CLI flag to force specific technologies Add --tech / -t CLI flag to bypass auto-detection and manually specify the technologies to install skills for. Supports multiple flags (--tech react --tech nextjs) and comma-separated values (--tech react,nextjs). Unknown technology IDs emit a warning and are skipped. When --tech is provided, the CLI builds the detected technology list directly from SKILLS_MAP instead of running the full project scan, while still evaluating combos and frontend inference from the forced IDs. Includes tests for the new flag and updates both README files with usage documentation. --- README.md | 1 + packages/autoskills/README.md | 10 +++++ packages/autoskills/main.ts | 61 ++++++++++++++++++++++++--- packages/autoskills/tests/cli.test.ts | 55 +++++++++++++++++++++++- 4 files changed, 120 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a2b1085e..670fb66b 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ This keeps the package small while avoiding live downloads from third-party skil ``` -y, --yes Skip confirmation prompt --dry-run Show what would be installed without installing +-t, --tech Force specific technologies (skip auto-detect) -h, --help Show help message ``` diff --git a/packages/autoskills/README.md b/packages/autoskills/README.md index 0cff108d..8d9d980e 100644 --- a/packages/autoskills/README.md +++ b/packages/autoskills/README.md @@ -36,6 +36,15 @@ npx autoskills -y npx autoskills --dry-run ``` +### Force specific technologies + +Skip auto-detection and install skills for the technologies you specify: + +```bash +npx autoskills --tech react --tech nextjs +npx autoskills --tech react,nextjs,tailwind +``` + ### Claude Code summary If `claude-code` is auto-detected or passed with `-a`, `autoskills` writes a `CLAUDE.md` file in your project root summarizing the markdown files installed under `.claude/skills`. @@ -47,6 +56,7 @@ If `claude-code` is auto-detected or passed with `-a`, `autoskills` writes a `CL | `-y`, `--yes` | Skip confirmation prompt, install all detected skills | | `--dry-run` | Show detected skills without installing anything | | `-v`, `--verbose` | Show install trace and error details | +| `-t`, `--tech` | Force specific technologies (skip auto-detect) | | `-h`, `--help` | Show help message | ## Supported Technologies diff --git a/packages/autoskills/main.ts b/packages/autoskills/main.ts index 90306b9e..522b74f7 100644 --- a/packages/autoskills/main.ts +++ b/packages/autoskills/main.ts @@ -2,7 +2,15 @@ import { resolve, dirname, join } from "node:path"; import { existsSync, readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; -import { detectTechnologies, collectSkills, detectAgents, getInstalledSkillNames } from "./lib.ts"; +import { + detectTechnologies, + collectSkills, + detectAgents, + getInstalledSkillNames, + SKILLS_MAP, + FRONTEND_PACKAGES, + detectCombos, +} from "./lib.ts"; import type { SkillEntry, Technology, ComboSkill } from "./lib.ts"; import { log, @@ -57,6 +65,7 @@ interface CliArgs { help: boolean; clearCache: boolean; agents: string[]; + technologies: string[]; } function parseArgs(): CliArgs { @@ -69,6 +78,21 @@ function parseArgs(): CliArgs { agents.push(args[i]); } } + + const technologies: string[] = []; + for (let i = 0; i < args.length; i++) { + if (args[i] === "-t" || args[i] === "--tech") { + const val = args[i + 1]; + if (val && !val.startsWith("-")) { + for (const v of val.split(",")) { + const trimmed = v.trim(); + if (trimmed) technologies.push(trimmed); + } + i++; + } + } + } + return { autoYes: args.includes("-y") || args.includes("--yes"), dryRun: args.includes("--dry-run"), @@ -76,6 +100,7 @@ function parseArgs(): CliArgs { help: args.includes("--help") || args.includes("-h"), clearCache: args.includes("--clear-cache"), agents, + technologies, }; } @@ -89,6 +114,7 @@ function showHelp(): void { npx autoskills ${dim("--dry-run")} Show what would be installed npx autoskills ${dim("--clear-cache")} Clear downloaded skills cache npx autoskills ${dim("-a cursor claude-code")} Install for specific IDEs only + npx autoskills ${dim("-t react nextjs")} Force specific technologies ${bold("Options:")} -y, --yes Skip confirmation prompt @@ -96,6 +122,7 @@ function showHelp(): void { --clear-cache Clear downloaded skills cache -v, --verbose Show install trace and error details -a, --agent Install for specific IDEs only (e.g. cursor, claude-code) + -t, --tech Force specific technologies (skip auto-detect) -h, --help Show this help message `); } @@ -500,7 +527,7 @@ async function selectSkills(skills: SkillEntry[], autoYes: boolean): Promise { - const { autoYes, dryRun, verbose, help, clearCache, agents } = parseArgs(); + const { autoYes, dryRun, verbose, help, clearCache, agents, technologies } = parseArgs(); if (help) { showHelp(); @@ -522,9 +549,33 @@ async function main(): Promise { const projectDir = resolve("."); - write(dim(" Scanning project...\r")); - const { detected, isFrontend, combos } = detectTechnologies(projectDir); - write("\x1b[K"); + let detected: Technology[]; + let isFrontend: boolean; + let combos: ComboSkill[]; + + if (technologies.length > 0) { + const validIds = new Set(SKILLS_MAP.map((t) => t.id)); + const forcedIds = new Set(); + for (const id of technologies) { + if (validIds.has(id)) { + forcedIds.add(id); + } else { + log(yellow(` ⚠ Unknown technology "${id}" — skipping.`)); + } + } + + detected = SKILLS_MAP.filter((t) => forcedIds.has(t.id)); + isFrontend = detected.some((t) => FRONTEND_PACKAGES.has(t.id)); + const detectedIds = detected.map((t) => t.id); + combos = detectCombos(detectedIds); + } else { + write(dim(" Scanning project...\r")); + const result = detectTechnologies(projectDir); + write("\x1b[K"); + detected = result.detected; + isFrontend = result.isFrontend; + combos = result.combos; + } if (detected.length === 0 && !isFrontend) { log(yellow(" ⚠ No supported technologies detected.")); diff --git a/packages/autoskills/tests/cli.test.ts b/packages/autoskills/tests/cli.test.ts index 3c787cda..ad72e720 100644 --- a/packages/autoskills/tests/cli.test.ts +++ b/packages/autoskills/tests/cli.test.ts @@ -2,10 +2,23 @@ import { describe, it } from "node:test"; import { ok } from "node:assert/strict"; import { execFileSync } from "node:child_process"; import { existsSync } from "node:fs"; -import { join, resolve } from "node:path"; +import { join, resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; import { useTmpDir, writePackageJson, writeFile, writeJson, addWorkspace } from "./helpers.ts"; -const CLI_PATH = resolve(import.meta.dirname!, "..", "index.mjs"); +const __dirname = dirname(fileURLToPath(import.meta.url)); + +function findCliPath(): string { + let dir = __dirname; + for (let i = 0; i < 3; i++) { + const candidate = resolve(dir, "index.mjs"); + if (existsSync(candidate)) return candidate; + dir = dirname(dir); + } + throw new Error("index.mjs not found"); +} + +const CLI_PATH = findCliPath(); function run(args: string[] = [], cwd: string = process.cwd()): string { return execFileSync(process.execPath, [CLI_PATH, ...args], { @@ -476,4 +489,42 @@ describe("CLI", () => { ok(!output.includes("universal")); }); }); + + describe("--tech", () => { + const tmp = useTmpDir(); + + it("forces specific technology and skips auto-detection", () => { + writePackageJson(tmp.path); + const output = run(["--dry-run", "--tech", "react"], tmp.path); + ok(output.includes("React")); + ok(!output.includes("No supported technologies")); + }); + + it("supports multiple --tech flags", () => { + writePackageJson(tmp.path); + const output = run(["--dry-run", "--tech", "react", "--tech", "nextjs"], tmp.path); + ok(output.includes("React")); + ok(output.includes("Next.js")); + }); + + it("supports comma-separated technologies", () => { + writePackageJson(tmp.path); + const output = run(["--dry-run", "--tech", "react,nextjs"], tmp.path); + ok(output.includes("React")); + ok(output.includes("Next.js")); + }); + + it("warns about unknown technologies", () => { + writePackageJson(tmp.path); + const output = run(["--dry-run", "--tech", "unknown-tech"], tmp.path); + ok(output.includes("Unknown technology")); + }); + + it("combines --tech with -y", () => { + writePackageJson(tmp.path); + const output = run(["--dry-run", "--tech", "react", "-y"], tmp.path); + ok(output.includes("React")); + ok(output.includes("Skills to install")); + }); + }); });