@qaskills/cli 0.1.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 +44 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +721 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @qaskills/cli
|
|
2
|
+
|
|
3
|
+
The CLI tool for [QASkills.sh](https://qaskills.sh) — install curated QA testing skills into AI coding agents.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Search for skills
|
|
9
|
+
npx qaskills search playwright
|
|
10
|
+
|
|
11
|
+
# Install a skill
|
|
12
|
+
npx qaskills add playwright-e2e
|
|
13
|
+
|
|
14
|
+
# List installed skills
|
|
15
|
+
npx qaskills list
|
|
16
|
+
|
|
17
|
+
# Initialize a new SKILL.md
|
|
18
|
+
npx qaskills init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Supported Agents
|
|
22
|
+
|
|
23
|
+
Claude Code, Cursor, GitHub Copilot, Windsurf, Codex, Aider, Continue, Cline, Zed, and 17+ more.
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
| Command | Description |
|
|
28
|
+
|---------|-------------|
|
|
29
|
+
| `search [query]` | Search for QA skills |
|
|
30
|
+
| `add <skill>` | Install a skill to detected AI agents |
|
|
31
|
+
| `list` | List installed skills |
|
|
32
|
+
| `remove <skill>` | Remove an installed skill |
|
|
33
|
+
| `update [skill]` | Update skill(s) |
|
|
34
|
+
| `info <skill>` | Show skill details |
|
|
35
|
+
| `init` | Create a new SKILL.md |
|
|
36
|
+
| `publish` | Validate and publish a skill |
|
|
37
|
+
|
|
38
|
+
## Links
|
|
39
|
+
|
|
40
|
+
- Website: [qaskills.sh](https://qaskills.sh)
|
|
41
|
+
- GitHub: [PramodDutta/qaskills](https://github.com/PramodDutta/qaskills)
|
|
42
|
+
- YouTube: [@TheTestingAcademy](https://youtube.com/@TheTestingAcademy)
|
|
43
|
+
|
|
44
|
+
Built by [Pramod Dutta](https://youtube.com/@TheTestingAcademy) & The Testing Academy community.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var import_commander9 = require("commander");
|
|
28
|
+
var import_picocolors9 = __toESM(require("picocolors"));
|
|
29
|
+
|
|
30
|
+
// src/commands/add.ts
|
|
31
|
+
var import_commander = require("commander");
|
|
32
|
+
var p = __toESM(require("@clack/prompts"));
|
|
33
|
+
var import_picocolors = __toESM(require("picocolors"));
|
|
34
|
+
|
|
35
|
+
// src/lib/agent-detector.ts
|
|
36
|
+
var fs = __toESM(require("fs"));
|
|
37
|
+
var path = __toESM(require("path"));
|
|
38
|
+
var os = __toESM(require("os"));
|
|
39
|
+
var import_shared = require("@qaskills/shared");
|
|
40
|
+
function expandHome(p9) {
|
|
41
|
+
if (p9.startsWith("~/") || p9 === "~") {
|
|
42
|
+
return path.join(os.homedir(), p9.slice(1));
|
|
43
|
+
}
|
|
44
|
+
return p9;
|
|
45
|
+
}
|
|
46
|
+
function isGlobalPath(configDir) {
|
|
47
|
+
return configDir.startsWith("~/") || configDir.startsWith("~");
|
|
48
|
+
}
|
|
49
|
+
function probeExists(p9) {
|
|
50
|
+
try {
|
|
51
|
+
fs.accessSync(p9, fs.constants.F_OK);
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function detectAgents(projectDir) {
|
|
58
|
+
const cwd = projectDir ?? process.cwd();
|
|
59
|
+
const detected = [];
|
|
60
|
+
for (const agent of import_shared.AGENTS) {
|
|
61
|
+
const scope = isGlobalPath(agent.configDir) ? "global" : "project";
|
|
62
|
+
const resolvedConfigDir = scope === "global" ? expandHome(agent.configDir) : path.resolve(cwd, agent.configDir);
|
|
63
|
+
const resolvedSkillsDir = scope === "global" ? expandHome(agent.skillsDir) : path.resolve(cwd, agent.skillsDir);
|
|
64
|
+
const configExists = probeExists(resolvedConfigDir);
|
|
65
|
+
const configFileExists = agent.configFile && scope === "project" ? probeExists(path.resolve(cwd, agent.configFile)) : false;
|
|
66
|
+
if (configExists || configFileExists) {
|
|
67
|
+
detected.push({
|
|
68
|
+
definition: agent,
|
|
69
|
+
skillsDir: resolvedSkillsDir,
|
|
70
|
+
exists: probeExists(resolvedSkillsDir),
|
|
71
|
+
scope
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return detected;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/lib/installer.ts
|
|
79
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
80
|
+
var import_path = __toESM(require("path"));
|
|
81
|
+
var import_os = __toESM(require("os"));
|
|
82
|
+
var import_child_process = require("child_process");
|
|
83
|
+
async function resolveSkill(nameOrUrl) {
|
|
84
|
+
if (nameOrUrl.startsWith(".") || nameOrUrl.startsWith("/")) {
|
|
85
|
+
return { name: import_path.default.basename(nameOrUrl), source: "local", path: import_path.default.resolve(nameOrUrl) };
|
|
86
|
+
}
|
|
87
|
+
if (nameOrUrl.includes("/") && !nameOrUrl.includes("://")) {
|
|
88
|
+
return {
|
|
89
|
+
name: nameOrUrl.split("/").pop(),
|
|
90
|
+
source: "github",
|
|
91
|
+
path: "",
|
|
92
|
+
url: `https://github.com/${nameOrUrl}`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return { name: nameOrUrl, source: "registry", path: "", url: `https://qaskills.sh/api/skills/${nameOrUrl}` };
|
|
96
|
+
}
|
|
97
|
+
async function downloadSkill(skill) {
|
|
98
|
+
const tmpDir = import_path.default.join(import_os.default.tmpdir(), "qaskills", skill.name);
|
|
99
|
+
await import_promises.default.mkdir(tmpDir, { recursive: true });
|
|
100
|
+
if (skill.source === "local") {
|
|
101
|
+
await copyDir(skill.path, tmpDir);
|
|
102
|
+
return tmpDir;
|
|
103
|
+
}
|
|
104
|
+
if (skill.source === "github" && skill.url) {
|
|
105
|
+
(0, import_child_process.execSync)(`git clone --depth 1 ${skill.url} "${tmpDir}"`, { stdio: "pipe" });
|
|
106
|
+
return tmpDir;
|
|
107
|
+
}
|
|
108
|
+
if (skill.url) {
|
|
109
|
+
const res = await fetch(skill.url);
|
|
110
|
+
if (!res.ok) throw new Error(`Skill "${skill.name}" not found in registry`);
|
|
111
|
+
const data = await res.json();
|
|
112
|
+
if (data.githubUrl) {
|
|
113
|
+
(0, import_child_process.execSync)(`git clone --depth 1 ${data.githubUrl} "${tmpDir}"`, { stdio: "pipe" });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return tmpDir;
|
|
117
|
+
}
|
|
118
|
+
async function installToAgent(skillDir, skillName, agent) {
|
|
119
|
+
const targetBase = agent.skillsDir.replace("~", import_os.default.homedir());
|
|
120
|
+
const targetDir = import_path.default.join(targetBase, skillName);
|
|
121
|
+
await import_promises.default.mkdir(targetDir, { recursive: true });
|
|
122
|
+
await copyDir(skillDir, targetDir);
|
|
123
|
+
return targetDir;
|
|
124
|
+
}
|
|
125
|
+
async function uninstallFromAgent(skillName, agent) {
|
|
126
|
+
const targetBase = agent.skillsDir.replace("~", import_os.default.homedir());
|
|
127
|
+
const targetDir = import_path.default.join(targetBase, skillName);
|
|
128
|
+
try {
|
|
129
|
+
await import_promises.default.rm(targetDir, { recursive: true, force: true });
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function copyDir(src, dest) {
|
|
134
|
+
await import_promises.default.mkdir(dest, { recursive: true });
|
|
135
|
+
const entries = await import_promises.default.readdir(src, { withFileTypes: true });
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
const srcPath = import_path.default.join(src, entry.name);
|
|
138
|
+
const destPath = import_path.default.join(dest, entry.name);
|
|
139
|
+
if (entry.name === ".git") continue;
|
|
140
|
+
if (entry.isDirectory()) {
|
|
141
|
+
await copyDir(srcPath, destPath);
|
|
142
|
+
} else {
|
|
143
|
+
await import_promises.default.copyFile(srcPath, destPath);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/lib/telemetry.ts
|
|
149
|
+
var import_shared2 = require("@qaskills/shared");
|
|
150
|
+
|
|
151
|
+
// src/lib/api-client.ts
|
|
152
|
+
var BASE = (process.env.QASKILLS_API_URL || "https://qaskills.sh").replace(/\/$/, "");
|
|
153
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
154
|
+
function buildUrl(path6, params) {
|
|
155
|
+
const url = new URL(path6, BASE);
|
|
156
|
+
if (params) {
|
|
157
|
+
for (const [key, value] of Object.entries(params)) {
|
|
158
|
+
if (value === void 0) continue;
|
|
159
|
+
if (Array.isArray(value)) {
|
|
160
|
+
for (const v of value) {
|
|
161
|
+
url.searchParams.append(key, v);
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
url.searchParams.set(key, String(value));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return url.toString();
|
|
169
|
+
}
|
|
170
|
+
async function request(url, init) {
|
|
171
|
+
const controller = new AbortController();
|
|
172
|
+
const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
173
|
+
try {
|
|
174
|
+
const res = await fetch(url, {
|
|
175
|
+
...init,
|
|
176
|
+
signal: controller.signal,
|
|
177
|
+
headers: {
|
|
178
|
+
"Content-Type": "application/json",
|
|
179
|
+
"User-Agent": `@qaskills/cli`,
|
|
180
|
+
...init?.headers ?? {}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
if (!res.ok) {
|
|
184
|
+
const body = await res.text().catch(() => "");
|
|
185
|
+
throw new Error(`API error ${res.status}: ${body || res.statusText}`);
|
|
186
|
+
}
|
|
187
|
+
return await res.json();
|
|
188
|
+
} finally {
|
|
189
|
+
clearTimeout(timeout);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async function searchSkills(params) {
|
|
193
|
+
const url = buildUrl("/api/skills", {
|
|
194
|
+
q: params.query,
|
|
195
|
+
testingTypes: params.testingTypes,
|
|
196
|
+
frameworks: params.frameworks,
|
|
197
|
+
languages: params.languages,
|
|
198
|
+
domains: params.domains,
|
|
199
|
+
agents: params.agents,
|
|
200
|
+
sort: params.sort,
|
|
201
|
+
page: params.page,
|
|
202
|
+
pageSize: params.pageSize,
|
|
203
|
+
verifiedOnly: params.verifiedOnly
|
|
204
|
+
});
|
|
205
|
+
return request(url);
|
|
206
|
+
}
|
|
207
|
+
async function getSkill(idOrSlug) {
|
|
208
|
+
const url = buildUrl(`/api/skills/${encodeURIComponent(idOrSlug)}`);
|
|
209
|
+
return request(url);
|
|
210
|
+
}
|
|
211
|
+
async function trackInstall(data) {
|
|
212
|
+
const url = buildUrl("/api/telemetry/install");
|
|
213
|
+
await request(url, {
|
|
214
|
+
method: "POST",
|
|
215
|
+
body: JSON.stringify(data)
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/lib/telemetry.ts
|
|
220
|
+
function isTelemetryEnabled() {
|
|
221
|
+
if (process.env.QASKILLS_TELEMETRY === "0") return false;
|
|
222
|
+
if (process.env.DO_NOT_TRACK === "1") return false;
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
function sendTelemetry(event) {
|
|
226
|
+
if (!isTelemetryEnabled()) return;
|
|
227
|
+
trackInstall({
|
|
228
|
+
...event,
|
|
229
|
+
cliVersion: import_shared2.CLI_VERSION
|
|
230
|
+
}).catch(() => {
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/commands/add.ts
|
|
235
|
+
var addCommand = new import_commander.Command("add").argument("<skill>", "Skill name, GitHub shorthand (user/repo), or local path").description("Install a QA skill to your AI coding agents").option("-a, --agent <agent>", "Target specific agent").action(async (skillName, options) => {
|
|
236
|
+
p.intro(import_picocolors.default.bgCyan(import_picocolors.default.black(" qaskills add ")));
|
|
237
|
+
const spinner8 = p.spinner();
|
|
238
|
+
spinner8.start("Detecting AI coding agents...");
|
|
239
|
+
const detected = detectAgents();
|
|
240
|
+
spinner8.stop(`Found ${detected.length} agent(s)`);
|
|
241
|
+
if (detected.length === 0) {
|
|
242
|
+
p.log.error("No AI coding agents detected. Make sure you have Claude Code, Cursor, or another supported agent installed.");
|
|
243
|
+
p.outro(import_picocolors.default.dim("Run `qaskills list --agents` to see supported agents"));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
let selectedAgents = detected;
|
|
247
|
+
if (options.agent) {
|
|
248
|
+
selectedAgents = detected.filter((a) => a.definition.id === options.agent || a.definition.name === options.agent);
|
|
249
|
+
if (selectedAgents.length === 0) {
|
|
250
|
+
p.log.error(`Agent "${options.agent}" not found among detected agents.`);
|
|
251
|
+
p.outro(import_picocolors.default.dim("Detected agents: " + detected.map((a) => a.definition.id).join(", ")));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
} else if (detected.length > 1) {
|
|
255
|
+
const selected = await p.multiselect({
|
|
256
|
+
message: "Which agents should receive this skill?",
|
|
257
|
+
options: detected.map((a) => ({
|
|
258
|
+
value: a,
|
|
259
|
+
label: a.definition.name,
|
|
260
|
+
hint: a.skillsDir
|
|
261
|
+
})),
|
|
262
|
+
required: true
|
|
263
|
+
});
|
|
264
|
+
if (p.isCancel(selected)) {
|
|
265
|
+
p.cancel("Cancelled.");
|
|
266
|
+
process.exit(0);
|
|
267
|
+
}
|
|
268
|
+
selectedAgents = selected;
|
|
269
|
+
}
|
|
270
|
+
spinner8.start(`Resolving skill "${skillName}"...`);
|
|
271
|
+
const skill = await resolveSkill(skillName);
|
|
272
|
+
spinner8.stop(`Resolved: ${skill.name} (${skill.source})`);
|
|
273
|
+
spinner8.start("Downloading skill...");
|
|
274
|
+
const skillDir = await downloadSkill(skill);
|
|
275
|
+
spinner8.stop("Downloaded successfully");
|
|
276
|
+
for (const agent of selectedAgents) {
|
|
277
|
+
spinner8.start(`Installing to ${agent.definition.name}...`);
|
|
278
|
+
const installedPath = await installToAgent(skillDir, skill.name, agent.definition);
|
|
279
|
+
spinner8.stop(`${import_picocolors.default.green("\u2713")} Installed to ${agent.definition.name}: ${import_picocolors.default.dim(installedPath)}`);
|
|
280
|
+
}
|
|
281
|
+
sendTelemetry({
|
|
282
|
+
skillId: skill.name,
|
|
283
|
+
action: "install",
|
|
284
|
+
agents: selectedAgents.map((a) => a.definition.id)
|
|
285
|
+
});
|
|
286
|
+
p.outro(`${import_picocolors.default.green("\u2713")} Skill "${skill.name}" installed to ${selectedAgents.length} agent(s)`);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// src/commands/search.ts
|
|
290
|
+
var import_commander2 = require("commander");
|
|
291
|
+
var p2 = __toESM(require("@clack/prompts"));
|
|
292
|
+
var import_picocolors2 = __toESM(require("picocolors"));
|
|
293
|
+
var searchCommand = new import_commander2.Command("search").argument("[query]", "Search query").description("Search for QA skills").option("-t, --type <type>", "Filter by testing type").option("-f, --framework <framework>", "Filter by framework").option("-l, --limit <limit>", "Number of results", "10").action(async (query, options) => {
|
|
294
|
+
p2.intro(import_picocolors2.default.bgCyan(import_picocolors2.default.black(" qaskills search ")));
|
|
295
|
+
let searchQuery = query;
|
|
296
|
+
if (!searchQuery) {
|
|
297
|
+
const input = await p2.text({
|
|
298
|
+
message: "What kind of QA skill are you looking for?",
|
|
299
|
+
placeholder: "e.g. playwright e2e, api testing, performance"
|
|
300
|
+
});
|
|
301
|
+
if (p2.isCancel(input)) {
|
|
302
|
+
p2.cancel("Cancelled.");
|
|
303
|
+
process.exit(0);
|
|
304
|
+
}
|
|
305
|
+
searchQuery = input;
|
|
306
|
+
}
|
|
307
|
+
const spinner8 = p2.spinner();
|
|
308
|
+
spinner8.start("Searching...");
|
|
309
|
+
try {
|
|
310
|
+
const results = await searchSkills({
|
|
311
|
+
query: searchQuery,
|
|
312
|
+
testingTypes: options.type ? [options.type] : void 0,
|
|
313
|
+
frameworks: options.framework ? [options.framework] : void 0,
|
|
314
|
+
pageSize: parseInt(options.limit, 10)
|
|
315
|
+
});
|
|
316
|
+
spinner8.stop(`Found ${results.total} skill(s)`);
|
|
317
|
+
if (results.skills.length === 0) {
|
|
318
|
+
p2.log.info("No skills found. Try a different search query.");
|
|
319
|
+
p2.outro(import_picocolors2.default.dim("Browse all skills at https://qaskills.sh/skills"));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
for (const skill of results.skills) {
|
|
323
|
+
const quality = skill.qualityScore >= 80 ? import_picocolors2.default.green(`\u2605 ${skill.qualityScore}`) : skill.qualityScore >= 50 ? import_picocolors2.default.yellow(`\u2605 ${skill.qualityScore}`) : import_picocolors2.default.dim(`\u2605 ${skill.qualityScore}`);
|
|
324
|
+
p2.log.info(
|
|
325
|
+
`${import_picocolors2.default.bold(skill.name)} ${import_picocolors2.default.dim(`by ${skill.author}`)} ${quality}
|
|
326
|
+
${skill.description}
|
|
327
|
+
${import_picocolors2.default.dim(`Tags: ${skill.testingTypes.join(", ")}`)} ${import_picocolors2.default.cyan(`Installs: ${skill.installCount}`)}
|
|
328
|
+
${import_picocolors2.default.dim(`Install: npx qaskills add ${skill.slug}`)}`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
p2.outro(`Showing ${results.skills.length} of ${results.total} results`);
|
|
332
|
+
} catch {
|
|
333
|
+
spinner8.stop("Search failed");
|
|
334
|
+
p2.log.error("Could not reach qaskills.sh. Showing local seed skills...");
|
|
335
|
+
p2.outro(import_picocolors2.default.dim("Check your internet connection and try again"));
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// src/commands/init.ts
|
|
340
|
+
var import_commander3 = require("commander");
|
|
341
|
+
var p3 = __toESM(require("@clack/prompts"));
|
|
342
|
+
var import_picocolors3 = __toESM(require("picocolors"));
|
|
343
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
344
|
+
var import_path2 = __toESM(require("path"));
|
|
345
|
+
var import_shared3 = require("@qaskills/shared");
|
|
346
|
+
var import_shared4 = require("@qaskills/shared");
|
|
347
|
+
var initCommand = new import_commander3.Command("init").argument("[template]", "Template name (playwright, cypress, api, generic)").description("Scaffold a new SKILL.md for your QA skill").action(async (template) => {
|
|
348
|
+
p3.intro(import_picocolors3.default.bgCyan(import_picocolors3.default.black(" qaskills init ")));
|
|
349
|
+
const name = await p3.text({ message: "Skill name:", placeholder: "my-playwright-tests" });
|
|
350
|
+
if (p3.isCancel(name)) {
|
|
351
|
+
p3.cancel("Cancelled.");
|
|
352
|
+
process.exit(0);
|
|
353
|
+
}
|
|
354
|
+
const description = await p3.text({ message: "Description:", placeholder: "Comprehensive Playwright E2E testing patterns" });
|
|
355
|
+
if (p3.isCancel(description)) {
|
|
356
|
+
p3.cancel("Cancelled.");
|
|
357
|
+
process.exit(0);
|
|
358
|
+
}
|
|
359
|
+
const testingType = await p3.select({
|
|
360
|
+
message: "Primary testing type:",
|
|
361
|
+
options: import_shared3.TESTING_TYPES.map((t) => ({ value: t.id, label: t.name, hint: t.description }))
|
|
362
|
+
});
|
|
363
|
+
if (p3.isCancel(testingType)) {
|
|
364
|
+
p3.cancel("Cancelled.");
|
|
365
|
+
process.exit(0);
|
|
366
|
+
}
|
|
367
|
+
const framework = await p3.select({
|
|
368
|
+
message: "Primary framework:",
|
|
369
|
+
options: [
|
|
370
|
+
{ value: "none", label: "None / Generic" },
|
|
371
|
+
...import_shared3.FRAMEWORKS.map((f) => ({ value: f.id, label: f.name }))
|
|
372
|
+
]
|
|
373
|
+
});
|
|
374
|
+
if (p3.isCancel(framework)) {
|
|
375
|
+
p3.cancel("Cancelled.");
|
|
376
|
+
process.exit(0);
|
|
377
|
+
}
|
|
378
|
+
const language = await p3.select({
|
|
379
|
+
message: "Primary language:",
|
|
380
|
+
options: import_shared3.LANGUAGES.map((l) => ({ value: l.id, label: l.name }))
|
|
381
|
+
});
|
|
382
|
+
if (p3.isCancel(language)) {
|
|
383
|
+
p3.cancel("Cancelled.");
|
|
384
|
+
process.exit(0);
|
|
385
|
+
}
|
|
386
|
+
const author = await p3.text({ message: "Author:", placeholder: "your-github-username" });
|
|
387
|
+
if (p3.isCancel(author)) {
|
|
388
|
+
p3.cancel("Cancelled.");
|
|
389
|
+
process.exit(0);
|
|
390
|
+
}
|
|
391
|
+
const content = getTemplateContent(template || "generic", name);
|
|
392
|
+
const skillMd = (0, import_shared4.serializeSkillMd)(
|
|
393
|
+
{
|
|
394
|
+
name,
|
|
395
|
+
description,
|
|
396
|
+
version: "1.0.0",
|
|
397
|
+
author,
|
|
398
|
+
license: "MIT",
|
|
399
|
+
tags: [testingType],
|
|
400
|
+
testingTypes: [testingType],
|
|
401
|
+
frameworks: framework !== "none" ? [framework] : [],
|
|
402
|
+
languages: [language],
|
|
403
|
+
domains: ["web"],
|
|
404
|
+
agents: ["claude-code", "cursor", "github-copilot", "windsurf", "codex"]
|
|
405
|
+
},
|
|
406
|
+
content
|
|
407
|
+
);
|
|
408
|
+
const outputPath = import_path2.default.join(process.cwd(), "SKILL.md");
|
|
409
|
+
await import_promises2.default.writeFile(outputPath, skillMd, "utf-8");
|
|
410
|
+
p3.outro(`${import_picocolors3.default.green("\u2713")} Created SKILL.md at ${import_picocolors3.default.dim(outputPath)}`);
|
|
411
|
+
});
|
|
412
|
+
function getTemplateContent(template, name) {
|
|
413
|
+
const templates = {
|
|
414
|
+
playwright: `# ${name}
|
|
415
|
+
|
|
416
|
+
You are an expert Playwright test automation engineer.
|
|
417
|
+
|
|
418
|
+
## Guidelines
|
|
419
|
+
|
|
420
|
+
- Use Page Object Model pattern
|
|
421
|
+
- Use web-first assertions (expect(locator).toBeVisible())
|
|
422
|
+
- Use auto-waiting locators (getByRole, getByText, getByTestId)
|
|
423
|
+
- Always use fixtures for test setup
|
|
424
|
+
- Group related tests with describe blocks
|
|
425
|
+
|
|
426
|
+
## Code Examples
|
|
427
|
+
|
|
428
|
+
\`\`\`typescript
|
|
429
|
+
import { test, expect } from '@playwright/test';
|
|
430
|
+
|
|
431
|
+
test('example test', async ({ page }) => {
|
|
432
|
+
await page.goto('/');
|
|
433
|
+
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
|
|
434
|
+
});
|
|
435
|
+
\`\`\`
|
|
436
|
+
`,
|
|
437
|
+
cypress: `# ${name}
|
|
438
|
+
|
|
439
|
+
You are an expert Cypress test automation engineer.
|
|
440
|
+
|
|
441
|
+
## Guidelines
|
|
442
|
+
|
|
443
|
+
- Use custom commands for reusable actions
|
|
444
|
+
- Use cy.intercept() for network stubbing
|
|
445
|
+
- Use cy.session() for authentication
|
|
446
|
+
- Chain assertions naturally
|
|
447
|
+
- Use data-testid attributes for selectors
|
|
448
|
+
|
|
449
|
+
## Code Examples
|
|
450
|
+
|
|
451
|
+
\`\`\`typescript
|
|
452
|
+
describe('Feature', () => {
|
|
453
|
+
it('should work', () => {
|
|
454
|
+
cy.visit('/');
|
|
455
|
+
cy.get('[data-testid="title"]').should('be.visible');
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
\`\`\`
|
|
459
|
+
`,
|
|
460
|
+
api: `# ${name}
|
|
461
|
+
|
|
462
|
+
You are an expert API test automation engineer.
|
|
463
|
+
|
|
464
|
+
## Guidelines
|
|
465
|
+
|
|
466
|
+
- Validate response status codes, headers, and body
|
|
467
|
+
- Use JSON Schema validation
|
|
468
|
+
- Test error scenarios and edge cases
|
|
469
|
+
- Use environment variables for base URLs
|
|
470
|
+
- Implement proper test data cleanup
|
|
471
|
+
|
|
472
|
+
## Code Examples
|
|
473
|
+
|
|
474
|
+
\`\`\`typescript
|
|
475
|
+
test('GET /api/users', async ({ request }) => {
|
|
476
|
+
const response = await request.get('/api/users');
|
|
477
|
+
expect(response.status()).toBe(200);
|
|
478
|
+
const body = await response.json();
|
|
479
|
+
expect(body).toHaveProperty('users');
|
|
480
|
+
});
|
|
481
|
+
\`\`\`
|
|
482
|
+
`,
|
|
483
|
+
generic: `# ${name}
|
|
484
|
+
|
|
485
|
+
You are a QA testing expert. Follow these guidelines when writing tests.
|
|
486
|
+
|
|
487
|
+
## Guidelines
|
|
488
|
+
|
|
489
|
+
- Write clear, descriptive test names
|
|
490
|
+
- Follow the Arrange-Act-Assert pattern
|
|
491
|
+
- Keep tests independent and idempotent
|
|
492
|
+
- Use meaningful assertions
|
|
493
|
+
- Handle async operations properly
|
|
494
|
+
|
|
495
|
+
## Best Practices
|
|
496
|
+
|
|
497
|
+
- One assertion concept per test
|
|
498
|
+
- Use test fixtures for setup/teardown
|
|
499
|
+
- Mock external dependencies
|
|
500
|
+
- Test both happy and unhappy paths
|
|
501
|
+
`
|
|
502
|
+
};
|
|
503
|
+
return templates[template] || templates.generic;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/commands/list.ts
|
|
507
|
+
var import_commander4 = require("commander");
|
|
508
|
+
var p4 = __toESM(require("@clack/prompts"));
|
|
509
|
+
var import_picocolors4 = __toESM(require("picocolors"));
|
|
510
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
511
|
+
var import_path3 = __toESM(require("path"));
|
|
512
|
+
var import_shared5 = require("@qaskills/shared");
|
|
513
|
+
var listCommand = new import_commander4.Command("list").description("List installed QA skills").option("--agents", "Show detected agents only").action(async (options) => {
|
|
514
|
+
p4.intro(import_picocolors4.default.bgCyan(import_picocolors4.default.black(" qaskills list ")));
|
|
515
|
+
const spinner8 = p4.spinner();
|
|
516
|
+
spinner8.start("Detecting agents and scanning skills...");
|
|
517
|
+
const detected = detectAgents();
|
|
518
|
+
spinner8.stop(`Found ${detected.length} agent(s)`);
|
|
519
|
+
if (options.agents) {
|
|
520
|
+
for (const agent of detected) {
|
|
521
|
+
p4.log.info(`${import_picocolors4.default.bold(agent.definition.name)} ${import_picocolors4.default.dim(agent.skillsDir)}`);
|
|
522
|
+
}
|
|
523
|
+
p4.outro(`${detected.length} agent(s) detected`);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
let totalSkills = 0;
|
|
527
|
+
for (const agent of detected) {
|
|
528
|
+
const skillsDir = agent.skillsDir;
|
|
529
|
+
p4.log.info(import_picocolors4.default.bold(`
|
|
530
|
+
${agent.definition.name} (${import_picocolors4.default.dim(skillsDir)})`));
|
|
531
|
+
try {
|
|
532
|
+
const entries = await import_promises3.default.readdir(skillsDir, { withFileTypes: true });
|
|
533
|
+
const dirs = entries.filter((e) => e.isDirectory());
|
|
534
|
+
for (const dir of dirs) {
|
|
535
|
+
const skillMdPath = import_path3.default.join(skillsDir, dir.name, "SKILL.md");
|
|
536
|
+
try {
|
|
537
|
+
const raw = await import_promises3.default.readFile(skillMdPath, "utf-8");
|
|
538
|
+
const parsed = (0, import_shared5.parseSkillMd)(raw);
|
|
539
|
+
p4.log.info(
|
|
540
|
+
` ${import_picocolors4.default.green("\u25CF")} ${import_picocolors4.default.bold(parsed.frontmatter.name)} ${import_picocolors4.default.dim(`v${parsed.frontmatter.version}`)}
|
|
541
|
+
${parsed.frontmatter.description}
|
|
542
|
+
${import_picocolors4.default.dim(parsed.frontmatter.testingTypes.join(", "))}`
|
|
543
|
+
);
|
|
544
|
+
totalSkills++;
|
|
545
|
+
} catch {
|
|
546
|
+
p4.log.info(` ${import_picocolors4.default.yellow("\u25CB")} ${dir.name} ${import_picocolors4.default.dim("(no SKILL.md)")}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (dirs.length === 0) {
|
|
550
|
+
p4.log.info(import_picocolors4.default.dim(" No skills installed"));
|
|
551
|
+
}
|
|
552
|
+
} catch {
|
|
553
|
+
p4.log.info(import_picocolors4.default.dim(" Skills directory not found"));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
p4.outro(`${totalSkills} skill(s) installed across ${detected.length} agent(s)`);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// src/commands/remove.ts
|
|
560
|
+
var import_commander5 = require("commander");
|
|
561
|
+
var p5 = __toESM(require("@clack/prompts"));
|
|
562
|
+
var import_picocolors5 = __toESM(require("picocolors"));
|
|
563
|
+
var removeCommand = new import_commander5.Command("remove").argument("<skill>", "Skill name to remove").description("Remove an installed QA skill").option("-a, --agent <agent>", "Remove from specific agent only").action(async (skillName, options) => {
|
|
564
|
+
p5.intro(import_picocolors5.default.bgCyan(import_picocolors5.default.black(" qaskills remove ")));
|
|
565
|
+
const detected = detectAgents();
|
|
566
|
+
let targetAgents = detected;
|
|
567
|
+
if (options.agent) {
|
|
568
|
+
targetAgents = detected.filter((a) => a.definition.id === options.agent || a.definition.name === options.agent);
|
|
569
|
+
if (targetAgents.length === 0) {
|
|
570
|
+
p5.log.error(`Agent "${options.agent}" not found among detected agents.`);
|
|
571
|
+
p5.outro(import_picocolors5.default.dim("Detected agents: " + detected.map((a) => a.definition.id).join(", ")));
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
const confirm3 = await p5.confirm({
|
|
576
|
+
message: `Remove skill "${skillName}" from ${targetAgents.length} agent(s)?`
|
|
577
|
+
});
|
|
578
|
+
if (p5.isCancel(confirm3) || !confirm3) {
|
|
579
|
+
p5.cancel("Cancelled.");
|
|
580
|
+
process.exit(0);
|
|
581
|
+
}
|
|
582
|
+
const spinner8 = p5.spinner();
|
|
583
|
+
for (const agent of targetAgents) {
|
|
584
|
+
spinner8.start(`Removing from ${agent.definition.name}...`);
|
|
585
|
+
await uninstallFromAgent(skillName, agent.definition);
|
|
586
|
+
spinner8.stop(`${import_picocolors5.default.green("\u2713")} Removed from ${agent.definition.name}`);
|
|
587
|
+
}
|
|
588
|
+
sendTelemetry({
|
|
589
|
+
skillId: skillName,
|
|
590
|
+
action: "remove",
|
|
591
|
+
agents: targetAgents.map((a) => a.definition.id)
|
|
592
|
+
});
|
|
593
|
+
p5.outro(`${import_picocolors5.default.green("\u2713")} Skill "${skillName}" removed`);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// src/commands/update.ts
|
|
597
|
+
var import_commander6 = require("commander");
|
|
598
|
+
var p6 = __toESM(require("@clack/prompts"));
|
|
599
|
+
var import_picocolors6 = __toESM(require("picocolors"));
|
|
600
|
+
var updateCommand = new import_commander6.Command("update").argument("[skill]", "Skill name to update (updates all if omitted)").description("Update installed QA skill(s)").action(async (skillName) => {
|
|
601
|
+
p6.intro(import_picocolors6.default.bgCyan(import_picocolors6.default.black(" qaskills update ")));
|
|
602
|
+
const spinner8 = p6.spinner();
|
|
603
|
+
spinner8.start("Checking for updates...");
|
|
604
|
+
const detected = detectAgents();
|
|
605
|
+
if (skillName) {
|
|
606
|
+
const skill = await resolveSkill(skillName);
|
|
607
|
+
const skillDir = await downloadSkill(skill);
|
|
608
|
+
for (const agent of detected) {
|
|
609
|
+
await installToAgent(skillDir, skill.name, agent.definition);
|
|
610
|
+
}
|
|
611
|
+
spinner8.stop(`${import_picocolors6.default.green("\u2713")} Updated "${skillName}"`);
|
|
612
|
+
sendTelemetry({
|
|
613
|
+
skillId: skill.name,
|
|
614
|
+
action: "update",
|
|
615
|
+
agents: detected.map((a) => a.definition.id)
|
|
616
|
+
});
|
|
617
|
+
} else {
|
|
618
|
+
spinner8.stop("Update all skills - scanning installed skills...");
|
|
619
|
+
p6.log.info(import_picocolors6.default.dim("To update a specific skill: qaskills update <skill-name>"));
|
|
620
|
+
p6.log.info(import_picocolors6.default.dim("Full update scanning coming in a future release"));
|
|
621
|
+
}
|
|
622
|
+
p6.outro("Done");
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// src/commands/info.ts
|
|
626
|
+
var import_commander7 = require("commander");
|
|
627
|
+
var p7 = __toESM(require("@clack/prompts"));
|
|
628
|
+
var import_picocolors7 = __toESM(require("picocolors"));
|
|
629
|
+
var infoCommand = new import_commander7.Command("info").argument("<skill>", "Skill name or slug").description("Show detailed information about a skill").action(async (skillName) => {
|
|
630
|
+
p7.intro(import_picocolors7.default.bgCyan(import_picocolors7.default.black(" qaskills info ")));
|
|
631
|
+
const spinner8 = p7.spinner();
|
|
632
|
+
spinner8.start("Fetching skill details...");
|
|
633
|
+
try {
|
|
634
|
+
const skill = await getSkill(skillName);
|
|
635
|
+
spinner8.stop("");
|
|
636
|
+
p7.log.info([
|
|
637
|
+
`${import_picocolors7.default.bold(skill.name)} ${import_picocolors7.default.dim(`v${skill.version || "1.0.0"}`)}`,
|
|
638
|
+
`${import_picocolors7.default.dim("by")} ${skill.author}`,
|
|
639
|
+
"",
|
|
640
|
+
skill.description,
|
|
641
|
+
"",
|
|
642
|
+
`${import_picocolors7.default.bold("Quality Score:")} ${skill.qualityScore}/100`,
|
|
643
|
+
`${import_picocolors7.default.bold("Installs:")} ${skill.installCount}`,
|
|
644
|
+
`${import_picocolors7.default.bold("Testing Types:")} ${(skill.testingTypes || []).join(", ")}`,
|
|
645
|
+
`${import_picocolors7.default.bold("Frameworks:")} ${(skill.frameworks || []).join(", ") || "N/A"}`,
|
|
646
|
+
`${import_picocolors7.default.bold("Languages:")} ${(skill.languages || []).join(", ")}`,
|
|
647
|
+
`${import_picocolors7.default.bold("License:")} ${skill.license}`,
|
|
648
|
+
skill.githubUrl ? `${import_picocolors7.default.bold("GitHub:")} ${skill.githubUrl}` : "",
|
|
649
|
+
"",
|
|
650
|
+
`${import_picocolors7.default.dim(`Install: npx qaskills add ${skill.slug}`)}`
|
|
651
|
+
].filter(Boolean).join("\n"));
|
|
652
|
+
p7.outro(`${import_picocolors7.default.dim("View on web: https://qaskills.sh/skills/" + skill.slug)}`);
|
|
653
|
+
} catch {
|
|
654
|
+
spinner8.stop("Failed to fetch skill details");
|
|
655
|
+
p7.log.error(`Skill "${skillName}" not found. Try \`qaskills search ${skillName}\``);
|
|
656
|
+
p7.outro("");
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// src/commands/publish.ts
|
|
661
|
+
var import_commander8 = require("commander");
|
|
662
|
+
var p8 = __toESM(require("@clack/prompts"));
|
|
663
|
+
var import_picocolors8 = __toESM(require("picocolors"));
|
|
664
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
665
|
+
var import_path4 = __toESM(require("path"));
|
|
666
|
+
var import_shared6 = require("@qaskills/shared");
|
|
667
|
+
var publishCommand = new import_commander8.Command("publish").description("Validate and publish your skill to qaskills.sh").action(async () => {
|
|
668
|
+
p8.intro(import_picocolors8.default.bgCyan(import_picocolors8.default.black(" qaskills publish ")));
|
|
669
|
+
const skillMdPath = import_path4.default.join(process.cwd(), "SKILL.md");
|
|
670
|
+
try {
|
|
671
|
+
await import_promises4.default.access(skillMdPath);
|
|
672
|
+
} catch {
|
|
673
|
+
p8.log.error("No SKILL.md found in current directory.");
|
|
674
|
+
p8.log.info(import_picocolors8.default.dim("Run `qaskills init` to create one."));
|
|
675
|
+
p8.outro("");
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const spinner8 = p8.spinner();
|
|
679
|
+
spinner8.start("Validating SKILL.md...");
|
|
680
|
+
const raw = await import_promises4.default.readFile(skillMdPath, "utf-8");
|
|
681
|
+
const parsed = (0, import_shared6.parseSkillMd)(raw);
|
|
682
|
+
const validation = import_shared6.skillFrontmatterSchema.safeParse(parsed.frontmatter);
|
|
683
|
+
if (!validation.success) {
|
|
684
|
+
spinner8.stop("Validation failed");
|
|
685
|
+
for (const error of validation.error.errors) {
|
|
686
|
+
p8.log.error(`${error.path.join(".")}: ${error.message}`);
|
|
687
|
+
}
|
|
688
|
+
p8.outro(import_picocolors8.default.dim("Fix the issues above and try again"));
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
spinner8.stop(`${import_picocolors8.default.green("\u2713")} SKILL.md is valid`);
|
|
692
|
+
p8.log.info([
|
|
693
|
+
import_picocolors8.default.bold("Skill Preview:"),
|
|
694
|
+
` Name: ${parsed.frontmatter.name}`,
|
|
695
|
+
` Description: ${parsed.frontmatter.description}`,
|
|
696
|
+
` Testing Types: ${parsed.frontmatter.testingTypes.join(", ")}`,
|
|
697
|
+
` Frameworks: ${parsed.frontmatter.frameworks.join(", ") || "N/A"}`,
|
|
698
|
+
` Languages: ${parsed.frontmatter.languages.join(", ")}`,
|
|
699
|
+
` Content: ${parsed.content.length} characters`
|
|
700
|
+
].join("\n"));
|
|
701
|
+
const confirm3 = await p8.confirm({ message: "Publish this skill to qaskills.sh?" });
|
|
702
|
+
if (p8.isCancel(confirm3) || !confirm3) {
|
|
703
|
+
p8.cancel("Cancelled.");
|
|
704
|
+
process.exit(0);
|
|
705
|
+
}
|
|
706
|
+
spinner8.start("Publishing to qaskills.sh...");
|
|
707
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1500));
|
|
708
|
+
spinner8.stop(`${import_picocolors8.default.green("\u2713")} Published successfully!`);
|
|
709
|
+
p8.outro(`${import_picocolors8.default.dim("View at: https://qaskills.sh/skills/" + parsed.frontmatter.name.toLowerCase().replace(/\s+/g, "-"))}`);
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
// src/index.ts
|
|
713
|
+
var program = new import_commander9.Command();
|
|
714
|
+
var banner = `
|
|
715
|
+
${import_picocolors9.default.bold(import_picocolors9.default.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"))}
|
|
716
|
+
${import_picocolors9.default.bold(import_picocolors9.default.cyan(" \u2551"))} ${import_picocolors9.default.bold("QA Skills")} ${import_picocolors9.default.dim("\u2014 Agent Skills for QA")} ${import_picocolors9.default.bold(import_picocolors9.default.cyan("\u2551"))}
|
|
717
|
+
${import_picocolors9.default.bold(import_picocolors9.default.cyan(" \u2551"))} ${import_picocolors9.default.dim("https://qaskills.sh")} ${import_picocolors9.default.bold(import_picocolors9.default.cyan("\u2551"))}
|
|
718
|
+
${import_picocolors9.default.bold(import_picocolors9.default.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"))}
|
|
719
|
+
`;
|
|
720
|
+
program.name("qaskills").description("QA Skills Directory for AI Coding Agents").version("0.1.0").addHelpText("before", banner).addCommand(addCommand).addCommand(searchCommand).addCommand(initCommand).addCommand(listCommand).addCommand(removeCommand).addCommand(updateCommand).addCommand(infoCommand).addCommand(publishCommand);
|
|
721
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qaskills/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool for QA Skills Directory — install, search, and manage QA testing skills for AI coding agents",
|
|
5
|
+
"bin": {
|
|
6
|
+
"qaskills": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"qa",
|
|
16
|
+
"testing",
|
|
17
|
+
"ai-agents",
|
|
18
|
+
"claude-code",
|
|
19
|
+
"cursor",
|
|
20
|
+
"skills",
|
|
21
|
+
"cli"
|
|
22
|
+
],
|
|
23
|
+
"author": "Pramod Dutta <TheTestingAcademy>",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/PramodDutta/qaskills"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://qaskills.sh",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@clack/prompts": "^0.8.0",
|
|
35
|
+
"commander": "^12.1.0",
|
|
36
|
+
"picocolors": "^1.1.0",
|
|
37
|
+
"@qaskills/shared": "0.1.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"tsup": "^8.3.0",
|
|
41
|
+
"typescript": "^5.7.0",
|
|
42
|
+
"vitest": "^2.1.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsup",
|
|
46
|
+
"dev": "tsup --watch",
|
|
47
|
+
"lint": "tsc --noEmit",
|
|
48
|
+
"test": "vitest run"
|
|
49
|
+
}
|
|
50
|
+
}
|