@hasna/testers 0.0.23 → 0.0.24

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.
Files changed (2) hide show
  1. package/dist/cli/index.js +64 -22
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -27115,7 +27115,7 @@ import chalk6 from "chalk";
27115
27115
  // package.json
27116
27116
  var package_default = {
27117
27117
  name: "@hasna/testers",
27118
- version: "0.0.23",
27118
+ version: "0.0.24",
27119
27119
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
27120
27120
  type: "module",
27121
27121
  main: "dist/index.js",
@@ -29833,7 +29833,11 @@ projectCmd.command("list").description("List all projects").action(() => {
29833
29833
  });
29834
29834
  projectCmd.command("show <id>").description("Show project details").action((id) => {
29835
29835
  try {
29836
- const project = getProject(id);
29836
+ let project = getProject(id);
29837
+ if (!project) {
29838
+ const all = listProjects();
29839
+ project = all.find((p) => p.id.startsWith(id) || p.name === id) ?? null;
29840
+ }
29837
29841
  if (!project) {
29838
29842
  logError(chalk6.red(`Project not found: ${id}`));
29839
29843
  process.exit(1);
@@ -30120,8 +30124,8 @@ program2.command("init").description("Initialize a new testing project").option(
30120
30124
  log(chalk6.yellow(` Unknown CI provider: ${opts.ci}. Supported: github`));
30121
30125
  }
30122
30126
  log("");
30123
- const rl = createInterface({ input: process.stdin, output: process.stdout });
30124
- const ask = (q) => new Promise((resolve2) => rl.question(q, resolve2));
30127
+ const rl2 = createInterface({ input: process.stdin, output: process.stdout });
30128
+ const ask = (q) => new Promise((resolve2) => rl2.question(q, resolve2));
30125
30129
  try {
30126
30130
  const envAnswer = await ask(" Would you like to configure environments? [y/N] ");
30127
30131
  if (envAnswer.trim().toLowerCase() === "y") {
@@ -30151,7 +30155,7 @@ program2.command("init").description("Initialize a new testing project").option(
30151
30155
  log("");
30152
30156
  }
30153
30157
  } finally {
30154
- rl.close();
30158
+ rl2.close();
30155
30159
  }
30156
30160
  log(chalk6.bold(" Next steps:"));
30157
30161
  log(` 1. Start your dev server`);
@@ -31215,10 +31219,27 @@ apiCmd.command("list").description("List API checks").option("--project <id>", "
31215
31219
  process.exit(1);
31216
31220
  }
31217
31221
  });
31218
- apiCmd.command("add").description("Add a new API check interactively").option("--project <id>", "Project ID").action(async (opts) => {
31219
- const rl = createInterface({ input: process.stdin, output: process.stdout });
31220
- const ask = (q) => new Promise((res) => rl.question(q, res));
31222
+ apiCmd.command("add").description("Add a new API check (interactive if no --url given)").option("--project <id>", "Project ID").option("-n, --name <name>", "Check name (non-interactive)").option("-u, --url <url>", "URL to check, full or path (non-interactive)").option("-m, --method <method>", "HTTP method (default: GET)").option("--status <code>", "Expected HTTP status code (default: 200)").option("--contains <text>", "Body must contain this string").option("--response-time <ms>", "Max acceptable response time in ms").option("-t, --tag <tag>", "Tag (repeatable)", []).action(async (opts) => {
31221
31223
  try {
31224
+ if (opts.url) {
31225
+ const projectId2 = resolveProject(opts.project);
31226
+ const check2 = createApiCheck({
31227
+ name: opts.name?.trim() || opts.url,
31228
+ method: opts.method?.toUpperCase() ?? "GET",
31229
+ url: opts.url.trim(),
31230
+ expectedStatus: opts.status ? parseInt(opts.status, 10) : 200,
31231
+ expectedBodyContains: opts.contains || undefined,
31232
+ expectedResponseTimeMs: opts.responseTime ? parseInt(opts.responseTime, 10) : undefined,
31233
+ tags: opts.tag ?? [],
31234
+ projectId: projectId2
31235
+ });
31236
+ log("");
31237
+ log(chalk6.green(`\u2713 Created API check ${chalk6.bold(check2.name)} (${check2.shortId})`));
31238
+ log(chalk6.dim(` ${check2.method} ${check2.url} \u2192 expect ${check2.expectedStatus}`));
31239
+ return;
31240
+ }
31241
+ const rl2 = createInterface({ input: process.stdin, output: process.stdout });
31242
+ const ask = (q) => new Promise((res) => rl2.question(q, res));
31222
31243
  const name = await ask("Name: ");
31223
31244
  if (!name.trim()) {
31224
31245
  logError(chalk6.red("Name is required"));
@@ -31237,7 +31258,7 @@ apiCmd.command("add").description("Add a new API check interactively").option("-
31237
31258
  const expectedStatus = statusInput.trim() ? parseInt(statusInput.trim(), 10) : 200;
31238
31259
  const bodyContains = await ask("Body must contain (optional, press enter to skip): ");
31239
31260
  const tagsInput = await ask("Tags (comma-separated, optional): ");
31240
- rl.close();
31261
+ rl2.close();
31241
31262
  const projectId = resolveProject(opts.project);
31242
31263
  const check = createApiCheck({
31243
31264
  name: name.trim(),
@@ -31252,7 +31273,6 @@ apiCmd.command("add").description("Add a new API check interactively").option("-
31252
31273
  log(chalk6.green(`\u2713 Created API check ${chalk6.bold(check.name)} (${check.shortId})`));
31253
31274
  log(chalk6.dim(` ${check.method} ${check.url} \u2192 expect ${check.expectedStatus}`));
31254
31275
  } catch (error) {
31255
- rl.close();
31256
31276
  logError(chalk6.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
31257
31277
  process.exit(1);
31258
31278
  }
@@ -31366,9 +31386,9 @@ apiCmd.command("delete <id>").description("Delete an API check").option("-y, --y
31366
31386
  process.exit(1);
31367
31387
  }
31368
31388
  if (!opts.yes) {
31369
- const rl = createInterface({ input: process.stdin, output: process.stdout });
31370
- const answer = await new Promise((res) => rl.question(`Delete "${check.name}" (${check.shortId})? [y/N] `, res));
31371
- rl.close();
31389
+ const rl2 = createInterface({ input: process.stdin, output: process.stdout });
31390
+ const answer = await new Promise((res) => rl2.question(`Delete "${check.name}" (${check.shortId})? [y/N] `, res));
31391
+ rl2.close();
31372
31392
  if (answer.toLowerCase() !== "y") {
31373
31393
  log(chalk6.dim("Cancelled."));
31374
31394
  return;
@@ -31589,27 +31609,49 @@ personaCmd.command("list").description("List personas").option("--project <id>",
31589
31609
  process.exit(1);
31590
31610
  }
31591
31611
  });
31592
- personaCmd.command("add").description("Create a persona interactively").option("--global", "Create as a global persona (no project scope)", false).option("--project <id>", "Project ID").action(async (opts) => {
31593
- const rl = createInterface({ input: process.stdin, output: process.stdout });
31594
- const ask = (q) => new Promise((res) => rl.question(q, res));
31612
+ personaCmd.command("add").description("Create a persona (interactive if no --name/--role given)").option("--global", "Create as a global persona (no project scope)", false).option("--project <id>", "Project ID").option("-n, --name <name>", "Persona name (non-interactive)").option("-r, --role <role>", "Persona role (non-interactive)").option("-d, --description <text>", "Persona description").option("-i, --instructions <text>", "Behavior instructions").option("--traits <list>", "Comma-separated traits (e.g. impatient,curious)").option("--goals <list>", "Comma-separated goals").option("--auth-email <email>", "Login email for auth testing").option("--auth-password <pass>", "Login password for auth testing").option("--auth-login-path <path>", "Login page path (default: /login)").action(async (opts) => {
31595
31613
  try {
31614
+ if (opts.name && opts.role) {
31615
+ const projectId2 = opts.global ? undefined : resolveProject(opts.project);
31616
+ const traits2 = opts.traits ? opts.traits.split(",").map((t) => t.trim()).filter(Boolean) : [];
31617
+ const goals2 = opts.goals ? opts.goals.split(",").map((g) => g.trim()).filter(Boolean) : [];
31618
+ const persona2 = createPersona({
31619
+ name: opts.name.trim(),
31620
+ role: opts.role.trim(),
31621
+ description: opts.description?.trim() ?? "",
31622
+ instructions: opts.instructions?.trim() ?? "",
31623
+ traits: traits2,
31624
+ goals: goals2,
31625
+ projectId: projectId2,
31626
+ authEmail: opts.authEmail,
31627
+ authPassword: opts.authPassword,
31628
+ authLoginPath: opts.authLoginPath
31629
+ });
31630
+ log("");
31631
+ log(chalk6.green(`Created persona ${chalk6.bold(persona2.shortId)}: ${persona2.name}`));
31632
+ log(chalk6.dim(` Role: ${persona2.role}`));
31633
+ log(chalk6.dim(` Scope: ${persona2.projectId ? "project" : "global"}`));
31634
+ return;
31635
+ }
31636
+ const rl2 = createInterface({ input: process.stdin, output: process.stdout });
31637
+ const ask = (q) => new Promise((res) => rl2.question(q, res));
31596
31638
  const name = await ask("Name: ");
31597
31639
  if (!name.trim()) {
31598
31640
  logError(chalk6.red("Name is required"));
31599
- rl.close();
31641
+ rl2.close();
31600
31642
  process.exit(1);
31601
31643
  }
31602
31644
  const role = await ask("Role (e.g. first-time user, admin, power user): ");
31603
31645
  if (!role.trim()) {
31604
31646
  logError(chalk6.red("Role is required"));
31605
- rl.close();
31647
+ rl2.close();
31606
31648
  process.exit(1);
31607
31649
  }
31608
31650
  const description = await ask("Description (optional): ");
31609
31651
  const instructions = await ask("Instructions \u2014 how should this persona behave? (optional): ");
31610
31652
  const traitsInput = await ask("Traits (comma-separated, e.g. impatient,curious): ");
31611
31653
  const goalsInput = await ask("Goals (comma-separated): ");
31612
- rl.close();
31654
+ rl2.close();
31613
31655
  const projectId = opts.global ? undefined : resolveProject(opts.project);
31614
31656
  const traits = traitsInput.trim() ? traitsInput.split(",").map((t) => t.trim()).filter(Boolean) : [];
31615
31657
  const goals = goalsInput.trim() ? goalsInput.split(",").map((g) => g.trim()).filter(Boolean) : [];
@@ -31913,9 +31955,9 @@ goldenCmd.command("add").description("Add a golden answer check interactively").
31913
31955
  try {
31914
31956
  const { createGoldenAnswer: createGoldenAnswer2 } = await Promise.resolve().then(() => (init_golden_answers(), exports_golden_answers));
31915
31957
  const ask = (prompt) => {
31916
- const rl = createInterface({ input: process.stdin, output: process.stdout });
31917
- return new Promise((resolve2) => rl.question(prompt, (ans) => {
31918
- rl.close();
31958
+ const rl2 = createInterface({ input: process.stdin, output: process.stdout });
31959
+ return new Promise((resolve2) => rl2.question(prompt, (ans) => {
31960
+ rl2.close();
31919
31961
  resolve2(ans.trim());
31920
31962
  }));
31921
31963
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/testers",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "description": "AI-powered QA testing CLI — spawns cheap AI agents to test web apps with headless browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",