@hasna/testers 0.0.24 → 0.0.26

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/dist/cli/index.js CHANGED
@@ -17171,6 +17171,10 @@ async function runByFilter(options) {
17171
17171
  if (options.scenarioIds && options.scenarioIds.length > 0) {
17172
17172
  const all = listScenarios({ projectId: options.projectId });
17173
17173
  scenarios = all.filter((s) => options.scenarioIds.includes(s.id) || options.scenarioIds.includes(s.shortId));
17174
+ if (scenarios.length === 0 && options.projectId) {
17175
+ const global2 = listScenarios({});
17176
+ scenarios = global2.filter((s) => options.scenarioIds.includes(s.id) || options.scenarioIds.includes(s.shortId));
17177
+ }
17174
17178
  } else {
17175
17179
  scenarios = listScenarios({
17176
17180
  projectId: options.projectId,
@@ -27115,7 +27119,7 @@ import chalk6 from "chalk";
27115
27119
  // package.json
27116
27120
  var package_default = {
27117
27121
  name: "@hasna/testers",
27118
- version: "0.0.24",
27122
+ version: "0.0.26",
27119
27123
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
27120
27124
  type: "module",
27121
27125
  main: "dist/index.js",
@@ -27473,7 +27477,14 @@ function initProject(options) {
27473
27477
  const projectPath = options.path ?? dir;
27474
27478
  const project = ensureProject(name, projectPath);
27475
27479
  const starterInputs = getStarterScenarios(framework ?? { name: "Unknown", features: [] }, project.id);
27476
- const scenarios = starterInputs.map((input) => createScenario(input));
27480
+ const existingScenarios = listScenarios({ projectId: project.id });
27481
+ const scenarios = existingScenarios.length > 0 ? existingScenarios : starterInputs.map((input) => {
27482
+ try {
27483
+ return createScenario(input);
27484
+ } catch {
27485
+ return null;
27486
+ }
27487
+ }).filter((s) => s !== null);
27477
27488
  const configDir = getTestersDir();
27478
27489
  const configPath = join13(configDir, "config.json");
27479
27490
  if (!existsSync11(configDir)) {
@@ -30088,7 +30099,7 @@ Shutting down scheduler daemon...`));
30088
30099
  process.exit(1);
30089
30100
  }
30090
30101
  });
30091
- program2.command("init").description("Initialize a new testing project").option("-n, --name <name>", "Project name").option("-u, --url <url>", "Base URL").option("-p, --path <path>", "Project path").option("--ci <provider>", "Generate CI workflow (github)").action(async (opts) => {
30102
+ program2.command("init").description("Initialize a new testing project").option("-n, --name <name>", "Project name").option("-u, --url <url>", "Base URL").option("-p, --path <path>", "Project path").option("--ci <provider>", "Generate CI workflow (github)").option("-y, --yes", "Skip interactive prompts (non-interactive mode)", false).action(async (opts) => {
30092
30103
  try {
30093
30104
  const { project, scenarios, framework, url } = initProject({
30094
30105
  name: opts.name,
@@ -30124,6 +30135,8 @@ program2.command("init").description("Initialize a new testing project").option(
30124
30135
  log(chalk6.yellow(` Unknown CI provider: ${opts.ci}. Supported: github`));
30125
30136
  }
30126
30137
  log("");
30138
+ if (opts.yes)
30139
+ return;
30127
30140
  const rl2 = createInterface({ input: process.stdin, output: process.stdout });
30128
30141
  const ask = (q) => new Promise((resolve2) => rl2.question(q, resolve2));
30129
30142
  try {
@@ -30431,16 +30444,21 @@ program2.command("chain <scenario-id>").description("Add a dependency to a scena
30431
30444
  process.exit(1);
30432
30445
  }
30433
30446
  });
30434
- program2.command("unchain <scenario-id>").description("Remove a dependency from a scenario").requiredOption("--from <id>", "Dependency to remove").action((scenarioId, opts) => {
30447
+ program2.command("unchain <scenario-id>").description("Remove a dependency from a scenario").requiredOption("--depends-on <id>", "Dependency to remove (alias: --from)").option("--from <id>", "Dependency to remove (alias for --depends-on)").action((scenarioId, opts) => {
30435
30448
  try {
30436
30449
  const scenario = getScenario(scenarioId) ?? getScenarioByShortId(scenarioId);
30437
30450
  if (!scenario) {
30438
30451
  logError(chalk6.red(`Scenario not found: ${scenarioId}`));
30439
30452
  process.exit(1);
30440
30453
  }
30441
- const dep = getScenario(opts.from) ?? getScenarioByShortId(opts.from);
30454
+ const depId = opts.dependsOn ?? opts.from;
30455
+ if (!depId) {
30456
+ logError(chalk6.red("Specify the dependency to remove with --depends-on <id>"));
30457
+ process.exit(1);
30458
+ }
30459
+ const dep = getScenario(depId) ?? getScenarioByShortId(depId);
30442
30460
  if (!dep) {
30443
- logError(chalk6.red(`Dependency not found: ${opts.from}`));
30461
+ logError(chalk6.red(`Dependency not found: ${depId}`));
30444
30462
  process.exit(1);
30445
30463
  }
30446
30464
  removeDependency(scenario.id, dep.id);
@@ -30508,8 +30526,12 @@ flowCmd.command("create <name>").description("Create a flow from scenario IDs").
30508
30526
  process.exit(1);
30509
30527
  }
30510
30528
  });
30511
- flowCmd.command("list").description("List all flows").option("--project <id>", "Project ID").action((opts) => {
30529
+ flowCmd.command("list").description("List all flows").option("--project <id>", "Project ID").option("--json", "Output as JSON", false).action((opts) => {
30512
30530
  const flows = listFlows(resolveProject(opts.project) ?? undefined);
30531
+ if (opts.json) {
30532
+ log(JSON.stringify(flows, null, 2));
30533
+ return;
30534
+ }
30513
30535
  if (flows.length === 0) {
30514
30536
  log(chalk6.dim(`
30515
30537
  No flows found.
@@ -30956,10 +30978,14 @@ SCAN_COMMON_OPTIONS(scanCmd.command("all <url>").description("Run all scanners:
30956
30978
  process.exit(1);
30957
30979
  }
30958
30980
  });
30959
- scanCmd.command("issues").description("List tracked scan issues").option("--status <status>", "Filter by status: open|resolved|regressed").option("--type <type>", "Filter by type: console_error|network_error|broken_link|performance").option("--project <id>", "Filter by project ID").option("--limit <n>", "Max results", "50").action((opts) => {
30981
+ scanCmd.command("issues").description("List tracked scan issues").option("--status <status>", "Filter by status: open|resolved|regressed").option("--type <type>", "Filter by type: console_error|network_error|broken_link|performance").option("--project <id>", "Filter by project ID").option("--limit <n>", "Max results", "50").option("--json", "Output as JSON", false).action((opts) => {
30960
30982
  try {
30961
30983
  const { listScanIssues: listScanIssues2 } = (init_scan_issues(), __toCommonJS(exports_scan_issues));
30962
30984
  const issues = listScanIssues2({ status: opts.status, type: opts.type, projectId: opts.project, limit: parseInt(opts.limit) });
30985
+ if (opts.json) {
30986
+ log(JSON.stringify(issues, null, 2));
30987
+ return;
30988
+ }
30963
30989
  if (issues.length === 0) {
30964
30990
  log(chalk6.dim("No scan issues found."));
30965
30991
  return;
@@ -31951,9 +31977,24 @@ evalCmd.command("rag <url>").description("Run RAG quality evaluation \u2014 fait
31951
31977
  }
31952
31978
  });
31953
31979
  var goldenCmd = program2.command("golden").description("Manage golden answer checks for hallucination detection");
31954
- goldenCmd.command("add").description("Add a golden answer check interactively").option("--project <id>", "Project ID").action(async (opts) => {
31980
+ goldenCmd.command("add").description("Add a golden answer check (interactive if no --question given)").option("--project <id>", "Project ID").option("-q, --question <text>", "Question the endpoint should answer (non-interactive)").option("-a, --answer <text>", "Expected golden answer (non-interactive)").option("-e, --endpoint <path>", "Endpoint path or URL (non-interactive)").option("--judge-model <model>", "Model to use as judge").action(async (opts) => {
31955
31981
  try {
31956
31982
  const { createGoldenAnswer: createGoldenAnswer2 } = await Promise.resolve().then(() => (init_golden_answers(), exports_golden_answers));
31983
+ if (opts.question && opts.answer && opts.endpoint) {
31984
+ const projectId2 = resolveProject(opts.project);
31985
+ const golden2 = createGoldenAnswer2({
31986
+ question: opts.question,
31987
+ goldenAnswer: opts.answer,
31988
+ endpoint: opts.endpoint,
31989
+ judgeModel: opts.judgeModel || undefined,
31990
+ projectId: projectId2
31991
+ });
31992
+ log(chalk6.green(`
31993
+ Created golden answer check ${chalk6.bold(golden2.shortId)}`));
31994
+ log(` Endpoint: ${golden2.endpoint}`);
31995
+ log(` Question: ${golden2.question.slice(0, 60)}`);
31996
+ return;
31997
+ }
31957
31998
  const ask = (prompt) => {
31958
31999
  const rl2 = createInterface({ input: process.stdin, output: process.stdout });
31959
32000
  return new Promise((resolve2) => rl2.question(prompt, (ans) => {
package/dist/index.js CHANGED
@@ -15528,6 +15528,10 @@ async function runByFilter(options) {
15528
15528
  if (options.scenarioIds && options.scenarioIds.length > 0) {
15529
15529
  const all = listScenarios({ projectId: options.projectId });
15530
15530
  scenarios = all.filter((s) => options.scenarioIds.includes(s.id) || options.scenarioIds.includes(s.shortId));
15531
+ if (scenarios.length === 0 && options.projectId) {
15532
+ const global2 = listScenarios({});
15533
+ scenarios = global2.filter((s) => options.scenarioIds.includes(s.id) || options.scenarioIds.includes(s.shortId));
15534
+ }
15531
15535
  } else {
15532
15536
  scenarios = listScenarios({
15533
15537
  projectId: options.projectId,
@@ -16329,7 +16333,14 @@ function initProject(options) {
16329
16333
  const projectPath = options.path ?? dir;
16330
16334
  const project = ensureProject(name, projectPath);
16331
16335
  const starterInputs = getStarterScenarios(framework ?? { name: "Unknown", features: [] }, project.id);
16332
- const scenarios = starterInputs.map((input) => createScenario(input));
16336
+ const existingScenarios = listScenarios({ projectId: project.id });
16337
+ const scenarios = existingScenarios.length > 0 ? existingScenarios : starterInputs.map((input) => {
16338
+ try {
16339
+ return createScenario(input);
16340
+ } catch {
16341
+ return null;
16342
+ }
16343
+ }).filter((s) => s !== null);
16333
16344
  const configDir = getTestersDir();
16334
16345
  const configPath = join13(configDir, "config.json");
16335
16346
  if (!existsSync11(configDir)) {
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/lib/init.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAI7D,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAmDjE;AAID,wBAAgB,mBAAmB,CACjC,SAAS,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,EAC/C,SAAS,EAAE,MAAM,GAChB,mBAAmB,EAAE,CAkNvB;AAID,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;IAC1C,SAAS,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,EAAE,CAAC;IAC/C,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAuC5D"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/lib/init.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAiB,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAI7D,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAmDjE;AAID,wBAAgB,mBAAmB,CACjC,SAAS,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,EAC/C,SAAS,EAAE,MAAM,GAChB,mBAAmB,EAAE,CAkNvB;AAID,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;IAC1C,SAAS,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,EAAE,CAAC;IAC/C,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CA0C5D"}
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"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"}
package/dist/mcp/index.js CHANGED
@@ -16178,6 +16178,10 @@ async function runByFilter(options) {
16178
16178
  if (options.scenarioIds && options.scenarioIds.length > 0) {
16179
16179
  const all = listScenarios({ projectId: options.projectId });
16180
16180
  scenarios = all.filter((s) => options.scenarioIds.includes(s.id) || options.scenarioIds.includes(s.shortId));
16181
+ if (scenarios.length === 0 && options.projectId) {
16182
+ const global2 = listScenarios({});
16183
+ scenarios = global2.filter((s) => options.scenarioIds.includes(s.id) || options.scenarioIds.includes(s.shortId));
16184
+ }
16181
16185
  } else {
16182
16186
  scenarios = listScenarios({
16183
16187
  projectId: options.projectId,
@@ -22990,6 +22994,22 @@ async function runApiChecksByFilter(filter) {
22990
22994
  // src/mcp/index.ts
22991
22995
  init_personas();
22992
22996
  init_paths();
22997
+ var cliArgs = new Set(process.argv.slice(2));
22998
+ if (cliArgs.has("--help") || cliArgs.has("-h")) {
22999
+ console.log(`Usage: testers-mcp [options]
23000
+
23001
+ Open Testers MCP server (stdio transport)
23002
+
23003
+ Options:
23004
+ -h, --help Show this help message
23005
+ -V, --version Show version
23006
+ `);
23007
+ process.exit(0);
23008
+ }
23009
+ if (cliArgs.has("--version") || cliArgs.has("-V")) {
23010
+ console.log("0.0.1");
23011
+ process.exit(0);
23012
+ }
22993
23013
  function json(data) {
22994
23014
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
22995
23015
  }
@@ -23281,18 +23301,6 @@ server.tool("list_agents", "List all registered agents", {}, async () => {
23281
23301
  return errorResponse(error);
23282
23302
  }
23283
23303
  });
23284
- server.tool("import_from_todos", "Import test scenarios from the todos database", {
23285
- projectName: exports_external.string().optional().describe("Todos project name to filter by"),
23286
- tags: exports_external.array(exports_external.string()).optional().describe("Tags to filter todos tasks"),
23287
- projectId: exports_external.string().optional().describe("Target project ID for imported scenarios")
23288
- }, async ({ projectName, tags, projectId }) => {
23289
- try {
23290
- const result = importFromTodos({ projectName, tags, projectId });
23291
- return json(result);
23292
- } catch (error) {
23293
- return errorResponse(error);
23294
- }
23295
- });
23296
23304
  server.tool("get_status", "Get system status: DB path, API key, scenario and run counts", {}, async () => {
23297
23305
  try {
23298
23306
  const config = loadConfig();
@@ -23323,97 +23331,6 @@ server.tool("scenario_exists", "Check whether a scenario with the given name exi
23323
23331
  return errorResponse(error);
23324
23332
  }
23325
23333
  });
23326
- server.tool("create_schedule", {
23327
- name: exports_external.string().describe("Schedule name"),
23328
- cronExpression: exports_external.string().describe("Cron expression (5-field)"),
23329
- url: exports_external.string().describe("Target URL to test"),
23330
- tags: exports_external.array(exports_external.string()).optional().describe("Filter scenarios by tags"),
23331
- priority: exports_external.string().optional().describe("Filter scenarios by priority"),
23332
- model: exports_external.string().optional().describe(MODEL_DESC),
23333
- headed: exports_external.boolean().optional().describe("Run headed"),
23334
- parallel: exports_external.number().optional().describe("Parallel count"),
23335
- projectId: exports_external.string().optional().describe("Project ID")
23336
- }, async (params) => {
23337
- try {
23338
- const schedule = createSchedule({
23339
- name: params.name,
23340
- cronExpression: params.cronExpression,
23341
- url: params.url,
23342
- scenarioFilter: { tags: params.tags, priority: params.priority },
23343
- model: params.model,
23344
- headed: params.headed,
23345
- parallel: params.parallel,
23346
- projectId: params.projectId
23347
- });
23348
- const nextRun = getNextRunTime(schedule.cronExpression);
23349
- return json({ ...schedule, nextRunAt: nextRun.toISOString() });
23350
- } catch (e) {
23351
- return errorResponse(e);
23352
- }
23353
- });
23354
- server.tool("list_schedules", {
23355
- projectId: exports_external.string().optional(),
23356
- enabled: exports_external.boolean().optional(),
23357
- limit: exports_external.number().optional()
23358
- }, async (params) => {
23359
- try {
23360
- const schedules = listSchedules({ projectId: params.projectId, enabled: params.enabled, limit: params.limit });
23361
- return json({ items: schedules, total: schedules.length });
23362
- } catch (e) {
23363
- return errorResponse(e);
23364
- }
23365
- });
23366
- server.tool("enable_schedule", { id: exports_external.string().describe("Schedule ID") }, async (params) => {
23367
- try {
23368
- const schedule = updateSchedule(params.id, { enabled: true });
23369
- return json(schedule);
23370
- } catch (e) {
23371
- return errorResponse(e);
23372
- }
23373
- });
23374
- server.tool("disable_schedule", { id: exports_external.string().describe("Schedule ID") }, async (params) => {
23375
- try {
23376
- const schedule = updateSchedule(params.id, { enabled: false });
23377
- return json(schedule);
23378
- } catch (e) {
23379
- return errorResponse(e);
23380
- }
23381
- });
23382
- server.tool("delete_schedule", { id: exports_external.string().describe("Schedule ID") }, async (params) => {
23383
- try {
23384
- const deleted = deleteSchedule(params.id);
23385
- if (!deleted)
23386
- return errorResponse(notFoundErr(params.id, "Schedule"));
23387
- return json({ deleted: true, id: params.id });
23388
- } catch (e) {
23389
- return errorResponse(e);
23390
- }
23391
- });
23392
- server.tool("wait_for_run", "Poll a run until it reaches a terminal status (passed, failed, error, or cancelled). Blocks until done or timeout.", {
23393
- runId: exports_external.string().describe("Run ID to wait for"),
23394
- timeoutMs: exports_external.number().optional().describe("Max wait time in ms (default 300000)"),
23395
- pollIntervalMs: exports_external.number().optional().describe("Poll interval in ms (default 3000)")
23396
- }, async ({ runId, timeoutMs = 300000, pollIntervalMs = 3000 }) => {
23397
- try {
23398
- const terminalStatuses = new Set(["passed", "failed", "error", "cancelled"]);
23399
- const deadline = Date.now() + timeoutMs;
23400
- while (Date.now() < deadline) {
23401
- const run = getRun(runId);
23402
- if (!run)
23403
- return errorResponse(notFoundErr(runId, "Run"));
23404
- if (terminalStatuses.has(run.status)) {
23405
- const results = getResultsByRun(runId);
23406
- const passed = results.filter((r) => r.status === "passed").length;
23407
- const failed = results.filter((r) => r.status === "failed" || r.status === "error").length;
23408
- return json({ ...run, passedCount: passed, failedCount: failed });
23409
- }
23410
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
23411
- }
23412
- return errorResponse(Object.assign(new Error(`Run ${runId} did not complete within ${timeoutMs}ms`), { name: "TimeoutError" }));
23413
- } catch (error) {
23414
- return errorResponse(error);
23415
- }
23416
- });
23417
23334
  server.tool("get_run_stats", "Get aggregate statistics for a run: pass rate, cost, token usage, duration", {
23418
23335
  runId: exports_external.string().describe("Run ID")
23419
23336
  }, async ({ runId }) => {
@@ -18871,6 +18871,10 @@ async function runByFilter(options) {
18871
18871
  if (options.scenarioIds && options.scenarioIds.length > 0) {
18872
18872
  const all = listScenarios({ projectId: options.projectId });
18873
18873
  scenarios = all.filter((s) => options.scenarioIds.includes(s.id) || options.scenarioIds.includes(s.shortId));
18874
+ if (scenarios.length === 0 && options.projectId) {
18875
+ const global2 = listScenarios({});
18876
+ scenarios = global2.filter((s) => options.scenarioIds.includes(s.id) || options.scenarioIds.includes(s.shortId));
18877
+ }
18874
18878
  } else {
18875
18879
  scenarios = listScenarios({
18876
18880
  projectId: options.projectId,
@@ -20016,6 +20020,25 @@ function listEnvironments(projectId) {
20016
20020
 
20017
20021
  // src/server/index.ts
20018
20022
  init_types();
20023
+ var cliArgs = new Set(process.argv.slice(2));
20024
+ if (cliArgs.has("--help") || cliArgs.has("-h")) {
20025
+ console.log(`Usage: testers-serve [options]
20026
+
20027
+ Open Testers HTTP server
20028
+
20029
+ Options:
20030
+ -h, --help Show this help message
20031
+ -V, --version Show version
20032
+
20033
+ Environment:
20034
+ TESTERS_PORT Port to bind (default: 19450)
20035
+ `);
20036
+ process.exit(0);
20037
+ }
20038
+ if (cliArgs.has("--version") || cliArgs.has("-V")) {
20039
+ console.log("0.0.1");
20040
+ process.exit(0);
20041
+ }
20019
20042
  function parseUrl(req) {
20020
20043
  const url = new URL(req.url);
20021
20044
  return { pathname: url.pathname, searchParams: url.searchParams };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/testers",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
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",
@@ -79,4 +79,4 @@
79
79
  "cli",
80
80
  "mcp"
81
81
  ]
82
- }
82
+ }