Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
10 changes: 10 additions & 0 deletions packages/autoskills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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
Expand Down
61 changes: 56 additions & 5 deletions packages/autoskills/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -57,6 +65,7 @@ interface CliArgs {
help: boolean;
clearCache: boolean;
agents: string[];
technologies: string[];
}

function parseArgs(): CliArgs {
Expand All @@ -69,13 +78,29 @@ 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"),
verbose: args.includes("--verbose") || args.includes("-v"),
help: args.includes("--help") || args.includes("-h"),
clearCache: args.includes("--clear-cache"),
agents,
technologies,
};
}

Expand All @@ -89,13 +114,15 @@ 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
--dry-run Show skills without installing
--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
`);
}
Expand Down Expand Up @@ -500,7 +527,7 @@ async function selectSkills(skills: SkillEntry[], autoYes: boolean): Promise<Ski
// ── Main ─────────────────────────────────────────────────────

async function main(): Promise<void> {
const { autoYes, dryRun, verbose, help, clearCache, agents } = parseArgs();
const { autoYes, dryRun, verbose, help, clearCache, agents, technologies } = parseArgs();

if (help) {
showHelp();
Expand All @@ -522,9 +549,33 @@ async function main(): Promise<void> {

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<string>();
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."));
Expand Down
55 changes: 53 additions & 2 deletions packages/autoskills/tests/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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], {
Expand Down Expand Up @@ -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"));
});
});
});