@hasna/testers 0.0.51 → 0.0.52

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
@@ -41,7 +41,7 @@ testers workflow fanout --project alumia --workers 6 --url https://preview.examp
41
41
  testers workflow fanout wf_abc,wf_def wf_xyz --workers 12 --url https://preview.example.com --json
42
42
  ```
43
43
 
44
- `--workers` is bounded to 1-12 concurrent sandboxes. Use `--dry-run` to inspect the remote commands and upload plans without spawning sandboxes.
44
+ `--workers` is bounded to 1-12 concurrent sandboxes. Fanout preflights provider credentials, required sandbox environment references, `rsync`, and app source directories before launching workers. Use `--dry-run` to inspect the remote commands, upload plans, and preflight checks without spawning sandboxes.
45
45
 
46
46
  ### Common Flags
47
47
 
package/dist/cli/index.js CHANGED
@@ -60463,8 +60463,11 @@ var exports_workflow_fanout = {};
60463
60463
  __export(exports_workflow_fanout, {
60464
60464
  runWorkflowFanout: () => runWorkflowFanout,
60465
60465
  resolveWorkflowFanoutSelection: () => resolveWorkflowFanoutSelection,
60466
- normalizeFanoutWorkerCount: () => normalizeFanoutWorkerCount
60466
+ normalizeFanoutWorkerCount: () => normalizeFanoutWorkerCount,
60467
+ checkWorkflowFanoutReadiness: () => checkWorkflowFanoutReadiness
60467
60468
  });
60469
+ import { spawnSync as spawnSync2 } from "child_process";
60470
+ import { existsSync as existsSync19, statSync as statSync5 } from "fs";
60468
60471
  function splitWorkflowIds(ids) {
60469
60472
  return (ids ?? []).flatMap((item) => item.split(",")).map((item) => item.trim()).filter(Boolean);
60470
60473
  }
@@ -60497,6 +60500,89 @@ function resolveWorkflowFanoutSelection(options) {
60497
60500
  }
60498
60501
  return filtered;
60499
60502
  }
60503
+ async function checkWorkflowFanoutReadiness(workflows, dependencies = {}) {
60504
+ const checks = [];
60505
+ const env = dependencies.env ?? process.env;
60506
+ for (const [provider, providerWorkflows] of groupWorkflowsByProvider(workflows)) {
60507
+ const envKey = PROVIDER_ENV_KEYS[provider];
60508
+ if (!envKey) {
60509
+ checks.push({
60510
+ name: `provider:${provider}`,
60511
+ ok: true,
60512
+ required: false,
60513
+ message: `No built-in credential preflight for sandbox provider "${provider}"`,
60514
+ workflows: providerWorkflows.map((workflow) => workflow.name)
60515
+ });
60516
+ continue;
60517
+ }
60518
+ const apiKey = await resolveProviderApiKey(provider, env, dependencies.providerApiKeyResolver);
60519
+ checks.push({
60520
+ name: `provider:${provider}`,
60521
+ ok: Boolean(apiKey),
60522
+ required: true,
60523
+ message: apiKey ? `Sandbox provider "${provider}" credential is available` : `Missing sandbox provider credential for "${provider}". Set ${envKey} or configure providers.${provider}.api_key with sandboxes config`,
60524
+ workflows: providerWorkflows.map((workflow) => workflow.name),
60525
+ details: { provider, envKey }
60526
+ });
60527
+ }
60528
+ const needsRsync = workflows.filter((workflow) => (workflow.execution.sandboxSyncStrategy ?? "rsync") === "rsync" || Boolean(workflow.execution.appSourceDir));
60529
+ if (needsRsync.length > 0) {
60530
+ const commandExists = dependencies.commandExists ?? defaultCommandExists;
60531
+ const rsyncOk = commandExists("rsync");
60532
+ checks.push({
60533
+ name: "tool:rsync",
60534
+ ok: rsyncOk,
60535
+ required: true,
60536
+ message: rsyncOk ? "rsync is available for sandbox uploads and app source bundling" : "Missing rsync. Install rsync or use --sandbox-sync archive for workflows without app source bundling",
60537
+ workflows: needsRsync.map((workflow) => workflow.name)
60538
+ });
60539
+ }
60540
+ const missingAppSources = workflows.filter((workflow) => workflow.execution.appSourceDir).filter((workflow) => {
60541
+ const sourceDir = workflow.execution.appSourceDir;
60542
+ return !existsSync19(sourceDir) || !statSync5(sourceDir).isDirectory();
60543
+ });
60544
+ if (missingAppSources.length > 0) {
60545
+ checks.push({
60546
+ name: "app-source",
60547
+ ok: false,
60548
+ required: true,
60549
+ message: "One or more workflow app source directories are missing or are not directories",
60550
+ workflows: missingAppSources.map((workflow) => workflow.name),
60551
+ details: {
60552
+ sources: missingAppSources.map((workflow) => ({
60553
+ workflowId: workflow.id,
60554
+ workflowName: workflow.name,
60555
+ appSourceDir: workflow.execution.appSourceDir
60556
+ }))
60557
+ }
60558
+ });
60559
+ }
60560
+ const { requiredMissing, optionalMissing } = collectMissingSandboxEnvRefs(workflows, env, dependencies.credentialResolver);
60561
+ if (requiredMissing.length > 0) {
60562
+ checks.push({
60563
+ name: "env:required",
60564
+ ok: false,
60565
+ required: true,
60566
+ message: "One or more required sandbox environment references could not be resolved",
60567
+ workflows: [...new Set(requiredMissing.map((item) => item.workflowName))],
60568
+ details: { missing: requiredMissing }
60569
+ });
60570
+ }
60571
+ if (optionalMissing.length > 0) {
60572
+ checks.push({
60573
+ name: "env:optional",
60574
+ ok: false,
60575
+ required: false,
60576
+ message: "One or more optional sandbox environment references are not set and will be omitted",
60577
+ workflows: [...new Set(optionalMissing.map((item) => item.workflowName))],
60578
+ details: { missing: optionalMissing }
60579
+ });
60580
+ }
60581
+ return {
60582
+ ok: checks.every((check) => check.ok || !check.required),
60583
+ checks
60584
+ };
60585
+ }
60500
60586
  async function mapWithConcurrency(items, limit, worker) {
60501
60587
  const output = new Array(items.length);
60502
60588
  let next = 0;
@@ -60512,7 +60598,38 @@ async function mapWithConcurrency(items, limit, worker) {
60512
60598
  async function runWorkflowFanout(options, dependencies = {}) {
60513
60599
  const workers = normalizeFanoutWorkerCount(options.workers);
60514
60600
  const workflows = resolveWorkflowFanoutSelection(options);
60515
- const { runTestingWorkflow: runOne = runTestingWorkflow, ...workflowDependencies } = dependencies;
60601
+ const {
60602
+ runTestingWorkflow: runOne = runTestingWorkflow,
60603
+ preflight: preflightOverride,
60604
+ providerApiKeyResolver,
60605
+ commandExists,
60606
+ credentialResolver,
60607
+ env,
60608
+ ...workflowDependencies
60609
+ } = dependencies;
60610
+ const preflight = preflightOverride ? await preflightOverride(workflows) : await checkWorkflowFanoutReadiness(workflows, {
60611
+ providerApiKeyResolver,
60612
+ commandExists,
60613
+ credentialResolver,
60614
+ env
60615
+ });
60616
+ if (!options.dryRun && !preflight.ok) {
60617
+ const error = `Preflight failed: ${summarizePreflightFailures(preflight)}`;
60618
+ return {
60619
+ status: "failed",
60620
+ workers,
60621
+ total: workflows.length,
60622
+ passed: 0,
60623
+ failed: workflows.length,
60624
+ preflight,
60625
+ items: workflows.map((workflow) => ({
60626
+ workflowId: workflow.id,
60627
+ workflowName: workflow.name,
60628
+ status: "failed",
60629
+ error
60630
+ }))
60631
+ };
60632
+ }
60516
60633
  const items = await mapWithConcurrency(workflows, workers, async (workflow) => {
60517
60634
  try {
60518
60635
  const output = await runOne(workflow.id, {
@@ -60559,12 +60676,81 @@ async function runWorkflowFanout(options, dependencies = {}) {
60559
60676
  total: items.length,
60560
60677
  passed,
60561
60678
  failed,
60562
- items
60679
+ items,
60680
+ preflight
60563
60681
  };
60564
60682
  }
60683
+ function groupWorkflowsByProvider(workflows) {
60684
+ const byProvider = new Map;
60685
+ for (const workflow of workflows) {
60686
+ const provider = workflow.execution.provider ?? "e2b";
60687
+ byProvider.set(provider, [...byProvider.get(provider) ?? [], workflow]);
60688
+ }
60689
+ return byProvider;
60690
+ }
60691
+ async function resolveProviderApiKey(provider, env, resolver) {
60692
+ if (resolver)
60693
+ return resolver(provider, env);
60694
+ const envKey = PROVIDER_ENV_KEYS[provider];
60695
+ if (envKey && env[envKey])
60696
+ return env[envKey];
60697
+ try {
60698
+ const mod = await import("@hasna/sandboxes");
60699
+ if (provider === "e2b" || provider === "daytona" || provider === "modal") {
60700
+ return mod.getProviderApiKey?.(provider);
60701
+ }
60702
+ } catch {}
60703
+ return;
60704
+ }
60705
+ function defaultCommandExists(command) {
60706
+ const result = spawnSync2(command, ["--version"], { encoding: "utf8" });
60707
+ return !result.error && result.status === 0;
60708
+ }
60709
+ function collectMissingSandboxEnvRefs(workflows, env, credentialResolver) {
60710
+ const requiredMissing = [];
60711
+ const optionalMissing = [];
60712
+ for (const workflow of workflows) {
60713
+ for (const [key, value] of Object.entries(workflow.execution.env ?? {})) {
60714
+ if (value.startsWith("$?")) {
60715
+ const name = value.slice(2).trim();
60716
+ if (name && env[name] === undefined) {
60717
+ optionalMissing.push({ workflowId: workflow.id, workflowName: workflow.name, key, reference: value });
60718
+ }
60719
+ continue;
60720
+ }
60721
+ if (!isResolvableEnvReference(value))
60722
+ continue;
60723
+ if (resolveSandboxEnvReference(value, env, credentialResolver) === null) {
60724
+ requiredMissing.push({ workflowId: workflow.id, workflowName: workflow.name, key, reference: value });
60725
+ }
60726
+ }
60727
+ }
60728
+ return { requiredMissing, optionalMissing };
60729
+ }
60730
+ function isResolvableEnvReference(value) {
60731
+ return value.startsWith("$") || value.startsWith("@secrets:");
60732
+ }
60733
+ function resolveSandboxEnvReference(value, env, credentialResolver) {
60734
+ if (value.startsWith("$")) {
60735
+ const varName = value.slice(1).trim();
60736
+ return varName ? env[varName] ?? null : null;
60737
+ }
60738
+ return (credentialResolver ?? resolveCredential)(value);
60739
+ }
60740
+ function summarizePreflightFailures(preflight) {
60741
+ const requiredFailures = preflight.checks.filter((check) => !check.ok && check.required);
60742
+ return requiredFailures.length > 0 ? requiredFailures.map((check) => check.message).join("; ") : "required checks did not pass";
60743
+ }
60744
+ var PROVIDER_ENV_KEYS;
60565
60745
  var init_workflow_fanout = __esm(() => {
60566
60746
  init_workflows();
60567
60747
  init_workflow_runner();
60748
+ init_secrets_resolver();
60749
+ PROVIDER_ENV_KEYS = {
60750
+ e2b: "E2B_API_KEY",
60751
+ daytona: "DAYTONA_API_KEY",
60752
+ modal: "MODAL_TOKEN_ID"
60753
+ };
60568
60754
  });
60569
60755
 
60570
60756
  // node_modules/@ai-sdk/provider/dist/index.mjs
@@ -95224,7 +95410,7 @@ import chalk6 from "chalk";
95224
95410
  // package.json
95225
95411
  var package_default = {
95226
95412
  name: "@hasna/testers",
95227
- version: "0.0.51",
95413
+ version: "0.0.52",
95228
95414
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
95229
95415
  type: "module",
95230
95416
  main: "dist/index.js",
@@ -97411,7 +97597,7 @@ init_ci();
97411
97597
  init_assertions();
97412
97598
  init_paths();
97413
97599
  init_sessions();
97414
- import { existsSync as existsSync19, mkdirSync as mkdirSync15 } from "fs";
97600
+ import { existsSync as existsSync20, mkdirSync as mkdirSync15 } from "fs";
97415
97601
 
97416
97602
  // src/lib/repo-discovery.ts
97417
97603
  init_paths();
@@ -98440,7 +98626,7 @@ var CONFIG_DIR5 = getTestersDir();
98440
98626
  var CONFIG_PATH4 = join21(CONFIG_DIR5, "config.json");
98441
98627
  function getActiveProject() {
98442
98628
  try {
98443
- if (existsSync19(CONFIG_PATH4)) {
98629
+ if (existsSync20(CONFIG_PATH4)) {
98444
98630
  const raw = JSON.parse(readFileSync11(CONFIG_PATH4, "utf-8"));
98445
98631
  return raw.activeProject ?? undefined;
98446
98632
  }
@@ -99371,7 +99557,7 @@ program2.command("export [format]").description("Export scenarios as JSON (defau
99371
99557
  return;
99372
99558
  }
99373
99559
  const outputDir = opts.output ?? ".";
99374
- if (!existsSync19(outputDir)) {
99560
+ if (!existsSync20(outputDir)) {
99375
99561
  mkdirSync15(outputDir, { recursive: true });
99376
99562
  }
99377
99563
  for (const s2 of scenarios) {
@@ -99574,11 +99760,11 @@ projectCmd.command("export-open <id>").description("Register a testers project i
99574
99760
  projectCmd.command("use <name>").description("Set active project (find or create)").option("--json", "Output as JSON", false).action((name21, opts) => {
99575
99761
  try {
99576
99762
  const project = ensureProject(name21, process.cwd());
99577
- if (!existsSync19(CONFIG_DIR5)) {
99763
+ if (!existsSync20(CONFIG_DIR5)) {
99578
99764
  mkdirSync15(CONFIG_DIR5, { recursive: true });
99579
99765
  }
99580
99766
  let config2 = {};
99581
- if (existsSync19(CONFIG_PATH4)) {
99767
+ if (existsSync20(CONFIG_PATH4)) {
99582
99768
  try {
99583
99769
  config2 = JSON.parse(readFileSync11(CONFIG_PATH4, "utf-8"));
99584
99770
  } catch {}
@@ -100193,7 +100379,7 @@ program2.command("ci [provider]").description("Print or write a CI workflow (def
100193
100379
  if (opts.output) {
100194
100380
  const outPath = resolve5(opts.output);
100195
100381
  const outDir = outPath.replace(/\/[^/]*$/, "");
100196
- if (outDir && !existsSync19(outDir)) {
100382
+ if (outDir && !existsSync20(outDir)) {
100197
100383
  mkdirSync15(outDir, { recursive: true });
100198
100384
  }
100199
100385
  writeFileSync7(outPath, workflow, "utf-8");
@@ -100228,7 +100414,7 @@ program2.command("init").description("Initialize a new testing project").option(
100228
100414
  }
100229
100415
  if (opts.ci === "github") {
100230
100416
  const workflowDir = join21(process.cwd(), ".github", "workflows");
100231
- if (!existsSync19(workflowDir)) {
100417
+ if (!existsSync20(workflowDir)) {
100232
100418
  mkdirSync15(workflowDir, { recursive: true });
100233
100419
  }
100234
100420
  const workflowPath = join21(workflowDir, "testers.yml");
@@ -102004,6 +102190,19 @@ workflowCmd.command("fanout [ids...]").description("Run multiple saved sandbox w
102004
102190
  if (opts.json || opts.dryRun) {
102005
102191
  log(JSON.stringify(result, null, 2));
102006
102192
  } else {
102193
+ const preflightChecks = result.preflight?.checks ?? [];
102194
+ const failedRequiredChecks = preflightChecks.filter((check2) => !check2.ok && check2.required);
102195
+ const warnings = preflightChecks.filter((check2) => !check2.ok && !check2.required);
102196
+ if (failedRequiredChecks.length > 0 || warnings.length > 0) {
102197
+ const label = failedRequiredChecks.length > 0 ? chalk6.red("failed") : chalk6.yellow("warnings");
102198
+ log(chalk6.bold(`Preflight ${label}:`));
102199
+ for (const check2 of failedRequiredChecks) {
102200
+ log(` ${chalk6.red("failed")} ${check2.message}`);
102201
+ }
102202
+ for (const check2 of warnings) {
102203
+ log(` ${chalk6.yellow("warning")} ${check2.message}`);
102204
+ }
102205
+ }
102007
102206
  const status = result.status === "passed" ? chalk6.green("passed") : chalk6.red("failed");
102008
102207
  log(chalk6.bold(`Sandbox workflow fanout ${status}: ${result.passed}/${result.total} passed with ${result.workers} worker(s)`));
102009
102208
  for (const item of result.items) {
@@ -26,11 +26,37 @@ export interface WorkflowFanoutResult {
26
26
  passed: number;
27
27
  failed: number;
28
28
  items: WorkflowFanoutItem[];
29
+ preflight?: WorkflowFanoutPreflightResult;
29
30
  }
30
31
  export interface WorkflowFanoutDependencies extends WorkflowRunnerDependencies {
31
32
  runTestingWorkflow?: typeof runTestingWorkflow;
33
+ preflight?: (workflows: TestingWorkflow[]) => WorkflowFanoutPreflightResult | Promise<WorkflowFanoutPreflightResult>;
34
+ providerApiKeyResolver?: (provider: string, env: Record<string, string | undefined>) => string | undefined | Promise<string | undefined>;
35
+ commandExists?: (command: string) => boolean;
36
+ credentialResolver?: (value: string) => string | null;
37
+ env?: Record<string, string | undefined>;
38
+ }
39
+ export interface WorkflowFanoutPreflightCheck {
40
+ name: string;
41
+ ok: boolean;
42
+ required: boolean;
43
+ message: string;
44
+ workflows?: string[];
45
+ details?: Record<string, unknown>;
46
+ }
47
+ export interface WorkflowFanoutPreflightResult {
48
+ ok: boolean;
49
+ checks: WorkflowFanoutPreflightCheck[];
50
+ }
51
+ interface WorkflowFanoutPreflightDependencies {
52
+ providerApiKeyResolver?: WorkflowFanoutDependencies["providerApiKeyResolver"];
53
+ commandExists?: WorkflowFanoutDependencies["commandExists"];
54
+ credentialResolver?: WorkflowFanoutDependencies["credentialResolver"];
55
+ env?: Record<string, string | undefined>;
32
56
  }
33
57
  export declare function normalizeFanoutWorkerCount(value: number | undefined): number;
34
58
  export declare function resolveWorkflowFanoutSelection(options: Pick<WorkflowFanoutOptions, "workflowIds" | "projectId" | "tags" | "includeDisabled">): TestingWorkflow[];
59
+ export declare function checkWorkflowFanoutReadiness(workflows: TestingWorkflow[], dependencies?: WorkflowFanoutPreflightDependencies): Promise<WorkflowFanoutPreflightResult>;
35
60
  export declare function runWorkflowFanout(options: WorkflowFanoutOptions, dependencies?: WorkflowFanoutDependencies): Promise<WorkflowFanoutResult>;
61
+ export {};
36
62
  //# sourceMappingURL=workflow-fanout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-fanout.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-fanout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,KAAK,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AACpH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,WAAW,qBAAsB,SAAQ,kBAAkB;IAC/D,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,0BAA2B,SAAQ,0BAA0B;IAC5E,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,CAAC;CAChD;AASD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAM5E;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,iBAAiB,CAAC,GAAG,eAAe,EAAE,CA8BhK;AAuBD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,qBAAqB,EAC9B,YAAY,GAAE,0BAA+B,GAC5C,OAAO,CAAC,oBAAoB,CAAC,CAyD/B"}
1
+ {"version":3,"file":"workflow-fanout.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-fanout.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,KAAK,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEpH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,WAAW,qBAAsB,SAAQ,kBAAkB;IAC/D,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,6BAA6B,CAAC;CAC3C;AAED,MAAM,WAAW,0BAA2B,SAAQ,0BAA0B;IAC5E,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,CAAC;IAC/C,SAAS,CAAC,EAAE,CAAC,SAAS,EAAE,eAAe,EAAE,KAAK,6BAA6B,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAAC;IACrH,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACzI,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,6BAA6B;IAC5C,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,4BAA4B,EAAE,CAAC;CACxC;AAED,UAAU,mCAAmC;IAC3C,sBAAsB,CAAC,EAAE,0BAA0B,CAAC,wBAAwB,CAAC,CAAC;IAC9E,aAAa,CAAC,EAAE,0BAA0B,CAAC,eAAe,CAAC,CAAC;IAC5D,kBAAkB,CAAC,EAAE,0BAA0B,CAAC,oBAAoB,CAAC,CAAC;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC1C;AAeD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAM5E;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,iBAAiB,CAAC,GAAG,eAAe,EAAE,CA8BhK;AAED,wBAAsB,4BAA4B,CAChD,SAAS,EAAE,eAAe,EAAE,EAC5B,YAAY,GAAE,mCAAwC,GACrD,OAAO,CAAC,6BAA6B,CAAC,CAiGxC;AAuBD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,qBAAqB,EAC9B,YAAY,GAAE,0BAA+B,GAC5C,OAAO,CAAC,oBAAoB,CAAC,CA4F/B"}
package/dist/mcp/index.js CHANGED
@@ -52,7 +52,7 @@ var package_default;
52
52
  var init_package = __esm(() => {
53
53
  package_default = {
54
54
  name: "@hasna/testers",
55
- version: "0.0.51",
55
+ version: "0.0.52",
56
56
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
57
57
  type: "module",
58
58
  main: "dist/index.js",
@@ -47090,7 +47090,7 @@ import { join as join14 } from "path";
47090
47090
  // package.json
47091
47091
  var package_default = {
47092
47092
  name: "@hasna/testers",
47093
- version: "0.0.51",
47093
+ version: "0.0.52",
47094
47094
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
47095
47095
  type: "module",
47096
47096
  main: "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/testers",
3
- "version": "0.0.51",
3
+ "version": "0.0.52",
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",