ai-llm-pentest-skill 0.1.0 → 0.2.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/README.md CHANGED
@@ -20,10 +20,10 @@ A Claude Code plugin that drives an **authorized** AI/LLM security assessment en
20
20
  ## ⚡ Quick start
21
21
 
22
22
  ```bash
23
- npx ai-llm-pentest-skill install # auto-detects your assistants (Claude Code, Cursor, Codex, ...)
23
+ npx ai-llm-pentest-skill install # interactive picker — choose your assistants (auto-detected)
24
24
  ```
25
25
 
26
- The engine runs on demand via `uvx` (needs [uv](https://docs.astral.sh/uv/)) — no clone required. Then open your assistant and prompt:
26
+ Run bare, it shows a multi-select of assistants (Claude Code, Cursor, Windsurf, Copilot, Codex/AGENTS.md, Gemini) with "(not detected)" hints; pass `--assistant`, `--all`, or `--project` to skip the prompts (and for CI). The engine runs on demand via `uvx` (needs [uv](https://docs.astral.sh/uv/)) — no clone required. Then open your assistant and prompt:
27
27
 
28
28
  > **Run an AI/LLM pentest** (or `/llm-pentest` in Claude Code)
29
29
 
@@ -227,15 +227,17 @@ Development is test-driven; see [`docs/DESIGN.md`](docs/DESIGN.md) for architect
227
227
 
228
228
  ## 🏷️ Releasing (maintainers)
229
229
 
230
- Releases are fully automated from [Conventional Commits](https://www.conventionalcommits.org/):
230
+ Publishing is triggered by a **version tag** — no pull requests, no tokens:
231
231
 
232
- 1. Merge normal `feat:` / `fix:` / `chore:` commits to `main`.
233
- 2. **release-please** opens and maintains a **Release PR** that bumps the version
234
- and updates `CHANGELOG.md`.
235
- 3. **Merge the Release PR.** That creates the GitHub Release + tag, and the same
236
- `release.yml` run then publishes to npm — tested in a job without credentials,
237
- published in a minimal job via **Trusted Publishing (OIDC)**, with provenance.
238
- No `NPM_TOKEN` is ever stored.
232
+ ```bash
233
+ npm version minor # bumps package.json + CHANGELOG date, creates tag vX.Y.Z (use patch/major as needed)
234
+ git push --follow-tags # pushing the tag triggers .github/workflows/release.yml
235
+ ```
236
+
237
+ `release.yml` (on `v*` tags) tests in a credential-free job, then publishes to
238
+ npm via **Trusted Publishing (OIDC)** with provenance in a minimal job that runs
239
+ no tests and `--ignore-scripts` — so no `NPM_TOKEN` is stored and a dependency
240
+ lifecycle script can't reach the OIDC credential.
239
241
 
240
242
  **One-time setup** on [npmjs.com](https://www.npmjs.com): open the
241
243
  `ai-llm-pentest-skill` package → **Settings → Trusted Publisher → Add**:
@@ -243,15 +245,6 @@ Releases are fully automated from [Conventional Commits](https://www.conventiona
243
245
  - Provider: **GitHub Actions**, Repository: `mstfknn/ai-llm-pentest-skill`,
244
246
  Workflow: `release.yml`
245
247
 
246
- > Hardening: the publish job holds `id-token: write` but runs no tests and
247
- > `--ignore-scripts`, so a dependency lifecycle script can't reach the OIDC
248
- > credential. For an extra gate, attach the publish job to a GitHub
249
- > **Environment** with required reviewers.
250
- >
251
- > First `0.1.0`: publish once manually (`npm publish --otp=<code>`) to create the
252
- > package, then configure the Trusted Publisher — every release after that is
253
- > hands-off.
254
-
255
248
  ## 📄 License
256
249
 
257
250
  [MIT](LICENSE), with an explicit authorized-use notice.
package/bin/cli.mjs CHANGED
@@ -1,4 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { run } from "../installer/commands.mjs";
2
+ import os from "node:os";
3
3
 
4
- process.exit(run(process.argv.slice(2)));
4
+ import { main } from "../installer/commands.mjs";
5
+ import { interactiveInstall } from "../installer/interactive.mjs";
6
+
7
+ process.exit(
8
+ await main(process.argv.slice(2), {
9
+ isTTY: process.stdin.isTTY,
10
+ ctx: { home: os.homedir(), cwd: process.cwd() },
11
+ interactive: interactiveInstall,
12
+ }),
13
+ );
@@ -0,0 +1,9 @@
1
+ █████╗ ██╗ ██╗ ██╗ ███╗ ███╗
2
+ ██╔══██╗██║ ██║ ██║ ████╗ ████║
3
+ ███████║██║█████╗██║ ██║ ██╔████╔██║
4
+ ██╔══██║██║╚════╝██║ ██║ ██║╚██╔╝██║
5
+ ██║ ██║██║ ███████╗███████╗██║ ╚═╝ ██║
6
+ ╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝
7
+ authorized AI/LLM pentest orchestration · v__VERSION__
8
+ black · gray · white-box → OWASP · ATLAS · NIST
9
+ github.com/mstfknn/ai-llm-pentest-skill
@@ -136,6 +136,24 @@ function selectAssistants(rest, ctx) {
136
136
  return detected.length > 0 ? detected : ["claude-code"];
137
137
  }
138
138
 
139
+ /** True when `install` was invoked bare on a TTY (→ launch the guided flow). */
140
+ export function shouldPrompt(argv, isTTY) {
141
+ return (
142
+ argv[0] === "install" &&
143
+ !argv.includes("--assistant") &&
144
+ !argv.includes("--all") &&
145
+ Boolean(isTTY)
146
+ );
147
+ }
148
+
149
+ /** Top-level entry: launch the guided flow when appropriate, else run a command. */
150
+ export async function main(argv, opts = {}) {
151
+ if (shouldPrompt(argv, opts.isTTY)) {
152
+ return opts.interactive(opts.ctx);
153
+ }
154
+ return run(argv, opts);
155
+ }
156
+
139
157
  /** Parse argv and run a subcommand. Returns a process exit code. */
140
158
  export function run(argv, opts = {}) {
141
159
  const ctx = opts.ctx ?? { home: os.homedir(), cwd: process.cwd() };
@@ -0,0 +1,63 @@
1
+ // Interactive (@clack/prompts) install flow. Thin I/O layer over tested helpers.
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import * as p from "@clack/prompts";
7
+
8
+ import { installAssistant } from "./commands.mjs";
9
+ import { PKG_ROOT } from "./config.mjs";
10
+ import { assistantChoices, expandSelection, renderBanner } from "./ui.mjs";
11
+
12
+ function packageVersion() {
13
+ const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"));
14
+ return pkg.version;
15
+ }
16
+
17
+ /** Run the guided installer. Returns a process exit code. */
18
+ export async function interactiveInstall(ctx) {
19
+ // eslint-disable-next-line no-console
20
+ console.log(renderBanner(packageVersion()));
21
+ p.intro("ai-llm-pentest-skill installer");
22
+
23
+ const choices = assistantChoices(ctx);
24
+ const selected = await p.multiselect({
25
+ message: "Which assistants should this install for?",
26
+ options: [
27
+ ...choices.map((c) => ({
28
+ value: c.value,
29
+ label: c.detected ? c.label : `${c.label} (not detected)`,
30
+ })),
31
+ { value: "__all__", label: "✨ All of the above" },
32
+ ],
33
+ required: true,
34
+ });
35
+ if (p.isCancel(selected)) {
36
+ p.cancel("Cancelled.");
37
+ return 1;
38
+ }
39
+
40
+ const scope = await p.select({
41
+ message: "Install where?",
42
+ options: [
43
+ { value: "global", label: "Home directory (all projects)" },
44
+ { value: "project", label: "This project only" },
45
+ ],
46
+ initialValue: "global",
47
+ });
48
+ if (p.isCancel(scope)) {
49
+ p.cancel("Cancelled.");
50
+ return 1;
51
+ }
52
+
53
+ const keys = expandSelection(selected, choices.map((c) => c.value));
54
+ const spin = p.spinner();
55
+ for (const key of keys) {
56
+ spin.start(`Installing for ${key}`);
57
+ installAssistant(key, scope, ctx);
58
+ spin.stop(`Installed for ${key}`);
59
+ }
60
+
61
+ p.outro('Done. Prompt your assistant: "Run an AI/LLM pentest" (or /llm-pentest).');
62
+ return 0;
63
+ }
@@ -0,0 +1,28 @@
1
+ // Presentation helpers for the interactive installer.
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { ASSISTANTS, detectInstalledAssistants } from "./assistants.mjs";
7
+ import { PKG_ROOT } from "./config.mjs";
8
+
9
+ /** The ASCII banner with the version filled in. */
10
+ export function renderBanner(version) {
11
+ const banner = fs.readFileSync(path.join(PKG_ROOT, "installer", "banner.txt"), "utf8");
12
+ return banner.split("__VERSION__").join(version);
13
+ }
14
+
15
+ /** Build multiselect choices for every assistant, flagged by on-disk detection. */
16
+ export function assistantChoices(ctx) {
17
+ const detected = new Set(detectInstalledAssistants(ctx));
18
+ return Object.entries(ASSISTANTS).map(([value, assistant]) => ({
19
+ value,
20
+ label: assistant.label,
21
+ detected: detected.has(value),
22
+ }));
23
+ }
24
+
25
+ /** Resolve a multiselect result: the __all__ sentinel expands to every key. */
26
+ export function expandSelection(values, allKeys) {
27
+ return values.includes("__all__") ? [...allKeys] : values;
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-llm-pentest-skill",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Installer for the ai-llm-pentest agent skill — drive an authorized AI/LLM penetration test (OWASP/ATLAS/NIST-mapped) from Claude Code, Codex, Cursor, Gemini, Copilot, and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,5 +45,8 @@
45
45
  "devDependencies": {
46
46
  "tdd-guard-vitest": "^0.2.0",
47
47
  "vitest": "^5.0.0"
48
+ },
49
+ "dependencies": {
50
+ "@clack/prompts": "^1.8.1"
48
51
  }
49
52
  }