prowl-tools 0.1.3 → 0.1.4
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/LICENSE +1 -1
- package/NOTICE +1 -1
- package/README.md +115 -3
- package/dist/{chunk-NXXGJOBG.js → chunk-MBAIGNVO.js} +38 -13
- package/dist/chunk-MBAIGNVO.js.map +1 -0
- package/dist/{chunk-T7YLXF6X.js → chunk-ZEFVTKQT.js} +1773 -756
- package/dist/chunk-ZEFVTKQT.js.map +1 -0
- package/dist/index.cjs +2008 -973
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +41 -30
- package/dist/index.js.map +1 -1
- package/dist/lib.cjs +1848 -807
- package/dist/lib.cjs.map +1 -1
- package/dist/lib.d.cts +314 -17
- package/dist/lib.d.ts +314 -17
- package/dist/lib.js +28 -4
- package/dist/{loader-5RDNTJHH.js → loader-XMTDB6OL.js} +2 -2
- package/package.json +2 -2
- package/dist/chunk-NXXGJOBG.js.map +0 -1
- package/dist/chunk-T7YLXF6X.js.map +0 -1
- /package/dist/{loader-5RDNTJHH.js.map → loader-XMTDB6OL.js.map} +0 -0
package/LICENSE
CHANGED
package/NOTICE
CHANGED
package/README.md
CHANGED
|
@@ -422,15 +422,19 @@ assertions:
|
|
|
422
422
|
Config lives at `.prowl/config.yml`. All options with defaults:
|
|
423
423
|
|
|
424
424
|
```yaml
|
|
425
|
-
#
|
|
425
|
+
# Execution target. Defaults to the web target; existing configs work unchanged.
|
|
426
426
|
target:
|
|
427
|
-
|
|
427
|
+
type: "web" # "web" (default) or "macos" (experimental)
|
|
428
|
+
url: "http://localhost:3000" # Required for web targets
|
|
429
|
+
# For the experimental macOS target instead:
|
|
430
|
+
# type: "macos"
|
|
431
|
+
# app: "com.example.App" # bundle id or /path/to/App.app (see "macOS Target")
|
|
428
432
|
|
|
429
433
|
# Browser settings
|
|
430
434
|
browser:
|
|
431
435
|
headless: true # false = show the browser window
|
|
432
436
|
slowMo: 0 # ms delay between actions (debugging)
|
|
433
|
-
timeout: 30000 # default page operation timeout
|
|
437
|
+
timeout: 30000 # default page operation timeout; macOS app launch timeout
|
|
434
438
|
|
|
435
439
|
# What gets saved per run
|
|
436
440
|
artifacts:
|
|
@@ -454,6 +458,7 @@ guardrails:
|
|
|
454
458
|
forbiddenSelectors: # selectors that steps cannot use
|
|
455
459
|
- "[data-danger]"
|
|
456
460
|
- ".delete-btn"
|
|
461
|
+
allowedApps: [] # macOS target only: bundle IDs, bundle names, or .app paths
|
|
457
462
|
|
|
458
463
|
# Auth state from `prowl login`
|
|
459
464
|
auth:
|
|
@@ -897,6 +902,113 @@ Templates cover auth flows (OAuth, 2FA), e-commerce (Stripe), admin panels, SaaS
|
|
|
897
902
|
|
|
898
903
|
---
|
|
899
904
|
|
|
905
|
+
## macOS Target (Experimental)
|
|
906
|
+
|
|
907
|
+
> **Experimental (PROWL-048).** Prowl can drive **native macOS apps** — including
|
|
908
|
+
> menu bar extras (`NSStatusItem` + `NSMenu`) — through Apple's Accessibility API,
|
|
909
|
+
> in addition to the web. The API, selector dialect, and step coverage may change.
|
|
910
|
+
> **Distribution is deferred:** the required helper binary is **not** shipped in the
|
|
911
|
+
> npm package; you build it locally (below).
|
|
912
|
+
|
|
913
|
+
### Enabling it
|
|
914
|
+
|
|
915
|
+
1. **Build the helper** (one time; requires the Swift toolchain / Xcode CLT):
|
|
916
|
+
|
|
917
|
+
```bash
|
|
918
|
+
cd macdriver
|
|
919
|
+
swift build -c release
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
Prowl finds the binary at `macdriver/.build/release/prowl-macdriver`, or at
|
|
923
|
+
`$PROWL_MACDRIVER_BIN` if set. If it is missing, Prowl fails with a clear
|
|
924
|
+
"build the helper" message rather than crashing.
|
|
925
|
+
|
|
926
|
+
2. **Point your config at a macOS target:**
|
|
927
|
+
|
|
928
|
+
```yaml
|
|
929
|
+
target:
|
|
930
|
+
type: macos
|
|
931
|
+
app: "com.example.App" # bundle id, or an absolute /path/to/App.app
|
|
932
|
+
guardrails:
|
|
933
|
+
allowedApps: # optional scope; empty = allow the target app
|
|
934
|
+
- "com.example.App"
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
When `target.app` is an app path, `allowedApps` may list the exact `.app`
|
|
938
|
+
path, the app bundle name (`Example` for `Example.app`), or the bundle id
|
|
939
|
+
from `Contents/Info.plist` when that file is readable.
|
|
940
|
+
|
|
941
|
+
3. **Grant Accessibility permission** (see below), then run a hunt as usual:
|
|
942
|
+
`prowl run my-macos-hunt`.
|
|
943
|
+
|
|
944
|
+
### Accessibility & Screen Recording permission
|
|
945
|
+
|
|
946
|
+
The **process that hosts** Prowl (your terminal — Terminal, iTerm, VS Code, or a CI
|
|
947
|
+
agent) must be granted **Accessibility** permission: **System Settings → Privacy &
|
|
948
|
+
Security → Accessibility**, then enable that app. macOS attributes the grant to the
|
|
949
|
+
hosting app, not to `prowl-macdriver`. Preflight from the helper:
|
|
950
|
+
|
|
951
|
+
```bash
|
|
952
|
+
macdriver/.build/release/prowl-macdriver check # prints {"trusted": <bool>}; prompts on first run
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
The `screenshot`/`assertScreenshot` steps additionally need **Screen Recording**
|
|
956
|
+
permission for the hosting app.
|
|
957
|
+
|
|
958
|
+
**CI notes (macOS runners):** headless CI cannot click "Allow" in a dialog, so grant
|
|
959
|
+
the permissions non-interactively before the run. On a self-hosted runner you can
|
|
960
|
+
pre-authorize the agent's host app with a TCC profile via MDM, or (on ephemeral
|
|
961
|
+
runners where it's acceptable) seed the TCC database, e.g.:
|
|
962
|
+
|
|
963
|
+
```bash
|
|
964
|
+
sudo sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db" \
|
|
965
|
+
"INSERT OR REPLACE INTO access VALUES('kTCCServiceAccessibility','<runner-app-bundle-id>',0,2,2,1,NULL,NULL,NULL,'UNUSED',NULL,0,1,NULL,NULL,NULL);"
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
GitHub-hosted macOS runners do not grant Accessibility, so the macOS target is aimed
|
|
969
|
+
at self-hosted / MDM-managed runners for now.
|
|
970
|
+
|
|
971
|
+
### Selector dialect (macOS)
|
|
972
|
+
|
|
973
|
+
Native selectors address accessibility identifiers, roles, and labels:
|
|
974
|
+
|
|
975
|
+
| Selector | Matches |
|
|
976
|
+
|---|---|
|
|
977
|
+
| `id=openSettings` | element whose `AXIdentifier` equals `openSettings` |
|
|
978
|
+
| `role=button[name="Save"]` | an `AXButton` whose title/description/value contains `Save` |
|
|
979
|
+
| `label="Email"` | element whose accessibility label equals `Email` |
|
|
980
|
+
| `text="Save"` or bare `Save` | element whose title/description/value contains the text |
|
|
981
|
+
| `statusItem` | opens the app's menu bar status-item menu |
|
|
982
|
+
| `menu=Preferences…` | opens the status-item menu and clicks that item |
|
|
983
|
+
|
|
984
|
+
`forbiddenSelectors` still applies (text patterns match via the same substring
|
|
985
|
+
semantics as the web target). Prefer `id=` (accessibility identifiers) — the native
|
|
986
|
+
analog of `data-testid`.
|
|
987
|
+
|
|
988
|
+
### Step compatibility
|
|
989
|
+
|
|
990
|
+
Portable steps run on **both** targets; web-only steps are rejected up front on the
|
|
991
|
+
macOS target (with a clear error), and `prowl login` / URL guardrails do not apply.
|
|
992
|
+
|
|
993
|
+
| Portable (web + macOS) | Web-only (rejected on macOS) |
|
|
994
|
+
|---|---|
|
|
995
|
+
| `click`, `fill`, `type`, `press` | `navigate`, `waitForUrl`, `waitForNetworkIdle` |
|
|
996
|
+
| `wait`, `waitForSelector` | `mockRoute` / `unmockRoute` |
|
|
997
|
+
| `assert: visible` / `notVisible` | `evalScript`, `runScript` |
|
|
998
|
+
| `screenshot`, `assertScreenshot` | `onDialog`, `select` / `selectOption` |
|
|
999
|
+
| `hover`, `scrollTo` | `setInputFiles`, `waitForDownload` |
|
|
1000
|
+
| `repeat`, `if`, `runHunt`, `copyText` | `scroll` (directional), `assert: urlIncludes` / `urlEquals` |
|
|
1001
|
+
|
|
1002
|
+
Notes: `press` maps Enter/Return/Space onto the element's activate action (other keys
|
|
1003
|
+
are unsupported); `type` fills the focused control; app teardown quits the target app
|
|
1004
|
+
after the run.
|
|
1005
|
+
|
|
1006
|
+
> Docs follow-up: the customer-facing docs site (`prowl-docs`) should gain a "macOS
|
|
1007
|
+
> target" page mirroring this section (target type + step-compatibility matrix +
|
|
1008
|
+
> permission setup); tracked separately from this repo.
|
|
1009
|
+
|
|
1010
|
+
---
|
|
1011
|
+
|
|
900
1012
|
## Troubleshooting
|
|
901
1013
|
|
|
902
1014
|
### "Could not find .prowl/config.yml"
|
|
@@ -7,6 +7,9 @@ import dotenv from "dotenv";
|
|
|
7
7
|
// src/config/schema.ts
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
|
+
// src/types/index.ts
|
|
11
|
+
var SUPPORTED_BROWSER_ENGINES = ["chromium", "firefox", "webkit"];
|
|
12
|
+
|
|
10
13
|
// src/config/hunt-name.ts
|
|
11
14
|
var HUNT_NAME_PATTERN = /^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*$/;
|
|
12
15
|
function isValidHuntName(name) {
|
|
@@ -21,15 +24,22 @@ function assertValidHuntName(name) {
|
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
// src/config/schema.ts
|
|
27
|
+
var webTargetSchema = z.object({
|
|
28
|
+
type: z.literal("web").optional(),
|
|
29
|
+
url: z.string().min(1)
|
|
30
|
+
}).strict();
|
|
31
|
+
var macosTargetSchema = z.object({
|
|
32
|
+
type: z.literal("macos"),
|
|
33
|
+
app: z.string().min(1)
|
|
34
|
+
}).strict();
|
|
35
|
+
var targetSchema = z.union([macosTargetSchema, webTargetSchema]);
|
|
24
36
|
var configSchema = z.object({
|
|
25
|
-
target:
|
|
26
|
-
url: z.string().min(1)
|
|
27
|
-
}),
|
|
37
|
+
target: targetSchema,
|
|
28
38
|
browser: z.object({
|
|
29
39
|
headless: z.boolean().optional(),
|
|
30
40
|
slowMo: z.number().optional(),
|
|
31
41
|
timeout: z.number().optional(),
|
|
32
|
-
engine: z.enum(
|
|
42
|
+
engine: z.enum(SUPPORTED_BROWSER_ENGINES).optional(),
|
|
33
43
|
channel: z.enum([
|
|
34
44
|
"chromium",
|
|
35
45
|
"chrome",
|
|
@@ -61,6 +71,7 @@ var configSchema = z.object({
|
|
|
61
71
|
guardrails: z.object({
|
|
62
72
|
maxSteps: z.number().optional(),
|
|
63
73
|
allowedDomains: z.array(z.string()).optional(),
|
|
74
|
+
allowedApps: z.array(z.string()).optional(),
|
|
64
75
|
forbiddenSelectors: z.array(z.string()).optional(),
|
|
65
76
|
selfHealing: z.boolean().optional()
|
|
66
77
|
}).optional(),
|
|
@@ -308,9 +319,11 @@ var huntSchema = z.object({
|
|
|
308
319
|
}).strict();
|
|
309
320
|
|
|
310
321
|
// src/config/loader.ts
|
|
322
|
+
var DEFAULT_WEB_URL = "http://localhost:3000";
|
|
311
323
|
var DEFAULT_CONFIG = {
|
|
312
324
|
target: {
|
|
313
|
-
|
|
325
|
+
type: "web",
|
|
326
|
+
url: DEFAULT_WEB_URL
|
|
314
327
|
},
|
|
315
328
|
browser: {
|
|
316
329
|
headless: true,
|
|
@@ -334,6 +347,7 @@ var DEFAULT_CONFIG = {
|
|
|
334
347
|
guardrails: {
|
|
335
348
|
maxSteps: 50,
|
|
336
349
|
allowedDomains: ["localhost", "127.0.0.1", "0.0.0.0"],
|
|
350
|
+
allowedApps: [],
|
|
337
351
|
forbiddenSelectors: ["[data-danger]", ".delete-btn"],
|
|
338
352
|
selfHealing: false
|
|
339
353
|
},
|
|
@@ -391,11 +405,18 @@ function resolveViewport(value) {
|
|
|
391
405
|
}
|
|
392
406
|
return value;
|
|
393
407
|
}
|
|
408
|
+
function resolveTarget(target) {
|
|
409
|
+
if (target && target.type === "macos") {
|
|
410
|
+
return { type: "macos", app: target.app };
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
type: "web",
|
|
414
|
+
url: target?.url ?? DEFAULT_WEB_URL
|
|
415
|
+
};
|
|
416
|
+
}
|
|
394
417
|
function mergeConfig(partial) {
|
|
395
418
|
return {
|
|
396
|
-
target:
|
|
397
|
-
url: partial.target?.url ?? DEFAULT_CONFIG.target.url
|
|
398
|
-
},
|
|
419
|
+
target: resolveTarget(partial.target),
|
|
399
420
|
browser: {
|
|
400
421
|
headless: partial.browser?.headless ?? DEFAULT_CONFIG.browser.headless,
|
|
401
422
|
slowMo: partial.browser?.slowMo ?? DEFAULT_CONFIG.browser.slowMo,
|
|
@@ -419,6 +440,7 @@ function mergeConfig(partial) {
|
|
|
419
440
|
guardrails: {
|
|
420
441
|
maxSteps: partial.guardrails?.maxSteps ?? DEFAULT_CONFIG.guardrails.maxSteps,
|
|
421
442
|
allowedDomains: partial.guardrails?.allowedDomains ?? DEFAULT_CONFIG.guardrails.allowedDomains,
|
|
443
|
+
allowedApps: partial.guardrails?.allowedApps ?? DEFAULT_CONFIG.guardrails.allowedApps,
|
|
422
444
|
forbiddenSelectors: partial.guardrails?.forbiddenSelectors ?? DEFAULT_CONFIG.guardrails.forbiddenSelectors,
|
|
423
445
|
selfHealing: partial.guardrails?.selfHealing ?? DEFAULT_CONFIG.guardrails.selfHealing
|
|
424
446
|
},
|
|
@@ -461,10 +483,12 @@ function loadConfig(configPath) {
|
|
|
461
483
|
const parsed = yaml.parse(raw) ?? {};
|
|
462
484
|
const validated = configSchema.parse(parsed);
|
|
463
485
|
const config = mergeConfig(validated);
|
|
464
|
-
config.
|
|
465
|
-
config.guardrails.allowedDomains
|
|
466
|
-
|
|
467
|
-
|
|
486
|
+
if (config.target.type === "web") {
|
|
487
|
+
config.guardrails.allowedDomains = ensureAllowedDomain(
|
|
488
|
+
config.guardrails.allowedDomains,
|
|
489
|
+
config.target.url
|
|
490
|
+
);
|
|
491
|
+
}
|
|
468
492
|
return { config, configPath: resolvedPath, configDir };
|
|
469
493
|
}
|
|
470
494
|
function loadHunt(huntName, configDir) {
|
|
@@ -528,6 +552,7 @@ function listHunts(configDir) {
|
|
|
528
552
|
}
|
|
529
553
|
|
|
530
554
|
export {
|
|
555
|
+
SUPPORTED_BROWSER_ENGINES,
|
|
531
556
|
configSchema,
|
|
532
557
|
stepSchema,
|
|
533
558
|
huntSchema,
|
|
@@ -543,4 +568,4 @@ export {
|
|
|
543
568
|
loadHuntMeta,
|
|
544
569
|
listHunts
|
|
545
570
|
};
|
|
546
|
-
//# sourceMappingURL=chunk-
|
|
571
|
+
//# sourceMappingURL=chunk-MBAIGNVO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/loader.ts","../src/config/schema.ts","../src/types/index.ts","../src/config/hunt-name.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport yaml from \"yaml\";\nimport dotenv from \"dotenv\";\nimport type { BrowserChannel, BrowserEngine, Config, Hunt, Target, Viewport } from \"../types/index.js\";\nimport { configSchema, huntSchema } from \"./schema.js\";\nimport { assertValidHuntName } from \"./hunt-name.js\";\n\nconst DEFAULT_WEB_URL = \"http://localhost:3000\";\n\nconst DEFAULT_CONFIG: Config = {\n target: {\n type: \"web\",\n url: DEFAULT_WEB_URL\n },\n browser: {\n headless: true,\n slowMo: 0,\n timeout: 30000,\n engine: \"chromium\",\n viewport: { width: 1280, height: 720 }\n },\n artifacts: {\n screenshots: \"on-failure\",\n networkHar: false,\n console: true,\n junit: false\n },\n assertions: {\n noConsoleErrors: true,\n noNetworkErrors: true,\n maxTotalTimeMs: 30000,\n networkIgnorePatterns: []\n },\n guardrails: {\n maxSteps: 50,\n allowedDomains: [\"localhost\", \"127.0.0.1\", \"0.0.0.0\"],\n allowedApps: [],\n forbiddenSelectors: [\"[data-danger]\", \".delete-btn\"],\n selfHealing: false\n },\n auth: {\n storageStatePath: \".prowl/auth-state.json\"\n },\n history: {\n maxRuns: 100\n }\n};\n\nexport const CONFIG_DIR = \".prowl\";\nexport const LEGACY_CONFIG_DIR = \".prowlqa\";\n\nlet legacyDirWarned = false;\n\n/** Warn (once per process) when a project still uses the legacy .prowlqa/ directory. */\nexport function warnLegacyConfigDir(): void {\n if (legacyDirWarned) {\n return;\n }\n legacyDirWarned = true;\n console.warn(\n 'Warning: the \".prowlqa/\" config directory is deprecated; rename it to \".prowl/\". ' +\n 'Support for \".prowlqa/\" will be removed in a future release.'\n );\n}\n\nexport function findConfigPath(startDir: string): string | null {\n let current = startDir;\n while (current) {\n // Prefer the new .prowl/ directory; fall back to the legacy .prowlqa/ at the same level.\n for (const dir of [CONFIG_DIR, LEGACY_CONFIG_DIR]) {\n const candidate = path.join(current, dir, \"config.yml\");\n if (fs.existsSync(candidate)) {\n return candidate;\n }\n }\n const parent = path.dirname(current);\n if (parent === current) {\n break;\n }\n current = parent;\n }\n return null;\n}\n\nconst VIEWPORT_PRESETS: Record<string, Viewport> = {\n mobile: { width: 375, height: 812 },\n tablet: { width: 768, height: 1024 },\n desktop: { width: 1280, height: 720 }\n};\n\nexport function resolveViewport(\n value: string | Viewport | undefined\n): Viewport {\n if (value === undefined) {\n return DEFAULT_CONFIG.browser.viewport;\n }\n if (typeof value === \"string\") {\n const preset = VIEWPORT_PRESETS[value];\n if (!preset) {\n throw new Error(`Unknown viewport preset: \"${value}\". Use mobile, tablet, or desktop.`);\n }\n return preset;\n }\n return value;\n}\n\n/**\n * Normalize the validated target into a canonical discriminated shape. A target\n * with `type: \"macos\"` becomes a macOS target; anything else (including the\n * legacy `{ url }` with no `type`) resolves to the web target, so pre-existing\n * configs are untouched.\n */\nfunction resolveTarget(target: Config[\"target\"] | undefined): Target {\n if (target && (target as { type?: string }).type === \"macos\") {\n return { type: \"macos\", app: (target as { app: string }).app };\n }\n return {\n type: \"web\",\n url: (target as { url?: string } | undefined)?.url ?? DEFAULT_WEB_URL\n };\n}\n\nfunction mergeConfig(partial: Partial<Config>): Config {\n return {\n target: resolveTarget(partial.target),\n browser: {\n headless: partial.browser?.headless ?? DEFAULT_CONFIG.browser.headless,\n slowMo: partial.browser?.slowMo ?? DEFAULT_CONFIG.browser.slowMo,\n timeout: partial.browser?.timeout ?? DEFAULT_CONFIG.browser.timeout,\n engine: (partial.browser as { engine?: BrowserEngine } | undefined)?.engine ?? DEFAULT_CONFIG.browser.engine,\n channel: (partial.browser as { channel?: BrowserChannel } | undefined)?.channel,\n viewport: resolveViewport((partial.browser as { viewport?: string | Viewport } | undefined)?.viewport)\n },\n artifacts: {\n screenshots: partial.artifacts?.screenshots ?? DEFAULT_CONFIG.artifacts.screenshots,\n networkHar: partial.artifacts?.networkHar ?? DEFAULT_CONFIG.artifacts.networkHar,\n console: partial.artifacts?.console ?? DEFAULT_CONFIG.artifacts.console,\n junit: partial.artifacts?.junit ?? DEFAULT_CONFIG.artifacts.junit\n },\n assertions: {\n noConsoleErrors:\n partial.assertions?.noConsoleErrors ?? DEFAULT_CONFIG.assertions.noConsoleErrors,\n noNetworkErrors:\n partial.assertions?.noNetworkErrors ?? DEFAULT_CONFIG.assertions.noNetworkErrors,\n maxTotalTimeMs:\n partial.assertions?.maxTotalTimeMs ?? DEFAULT_CONFIG.assertions.maxTotalTimeMs,\n networkIgnorePatterns:\n partial.assertions?.networkIgnorePatterns ??\n DEFAULT_CONFIG.assertions.networkIgnorePatterns\n },\n guardrails: {\n maxSteps: partial.guardrails?.maxSteps ?? DEFAULT_CONFIG.guardrails.maxSteps,\n allowedDomains: partial.guardrails?.allowedDomains ?? DEFAULT_CONFIG.guardrails.allowedDomains,\n allowedApps: partial.guardrails?.allowedApps ?? DEFAULT_CONFIG.guardrails.allowedApps,\n forbiddenSelectors:\n partial.guardrails?.forbiddenSelectors ?? DEFAULT_CONFIG.guardrails.forbiddenSelectors,\n selfHealing: partial.guardrails?.selfHealing ?? DEFAULT_CONFIG.guardrails.selfHealing\n },\n auth: {\n storageStatePath: partial.auth?.storageStatePath ?? (partial.auth !== undefined ? DEFAULT_CONFIG.auth.storageStatePath : undefined)\n },\n history: {\n maxRuns: partial.history?.maxRuns ?? DEFAULT_CONFIG.history.maxRuns\n },\n bugLog: partial.bugLog,\n tracing: partial.tracing,\n reliability: partial.reliability\n };\n}\n\nexport function ensureAllowedDomain(allowed: string[], urlValue: string): string[] {\n try {\n const host = new URL(urlValue).hostname;\n if (!allowed.includes(host)) {\n return [...allowed, host];\n }\n } catch {\n return allowed;\n }\n return allowed;\n}\n\nexport function loadConfig(configPath?: string): {\n config: Config;\n configPath: string;\n configDir: string;\n} {\n const resolvedPath = configPath\n ? path.resolve(configPath)\n : findConfigPath(process.cwd());\n\n if (!resolvedPath) {\n throw new Error(\"Could not find .prowl/config.yml. Run `prowl init` first.\");\n }\n\n if (!fs.existsSync(resolvedPath)) {\n throw new Error(`Config file not found at ${resolvedPath}`);\n }\n\n const configDir = path.dirname(resolvedPath);\n if (path.basename(configDir) === LEGACY_CONFIG_DIR) {\n warnLegacyConfigDir();\n }\n dotenv.config({ path: path.join(configDir, \".env\"), override: false });\n\n const raw = fs.readFileSync(resolvedPath, \"utf-8\");\n const parsed = yaml.parse(raw) ?? {};\n const validated = configSchema.parse(parsed);\n const config = mergeConfig(validated as Partial<Config>);\n\n // allowedDomains only applies to the web target; the macOS target scopes on\n // bundle IDs / process names via guardrails.allowedApps instead.\n if (config.target.type === \"web\") {\n config.guardrails.allowedDomains = ensureAllowedDomain(\n config.guardrails.allowedDomains,\n config.target.url\n );\n }\n\n return { config, configPath: resolvedPath, configDir };\n}\n\nexport function loadHunt(huntName: string, configDir: string): Hunt {\n assertValidHuntName(huntName);\n const huntPath = path.join(configDir, \"hunts\", `${huntName}.yml`);\n if (!fs.existsSync(huntPath)) {\n throw new Error(`Hunt file not found: ${huntPath}`);\n }\n const raw = fs.readFileSync(huntPath, \"utf-8\");\n const parsed = yaml.parse(raw) ?? {};\n const validated = huntSchema.parse(parsed);\n return validated as Hunt;\n}\n\nexport function loadHuntTags(huntName: string, configDir: string): string[] {\n assertValidHuntName(huntName);\n const huntPath = path.join(configDir, \"hunts\", `${huntName}.yml`);\n if (!fs.existsSync(huntPath)) {\n return [];\n }\n const raw = fs.readFileSync(huntPath, \"utf-8\");\n const parsed = yaml.parse(raw) ?? {};\n return Array.isArray(parsed.tags) ? parsed.tags : [];\n}\n\nexport function loadHuntMeta(huntName: string, configDir: string): { description?: string; tags: string[] } {\n assertValidHuntName(huntName);\n const huntPath = path.join(configDir, \"hunts\", `${huntName}.yml`);\n if (!fs.existsSync(huntPath)) {\n return { tags: [] };\n }\n const raw = fs.readFileSync(huntPath, \"utf-8\");\n const parsed = yaml.parse(raw) ?? {};\n return {\n description: typeof parsed.description === \"string\" ? parsed.description : undefined,\n tags: Array.isArray(parsed.tags) ? parsed.tags : []\n };\n}\n\nexport function listHunts(configDir: string): string[] {\n const huntsDir = path.join(configDir, \"hunts\");\n if (!fs.existsSync(huntsDir)) {\n return [];\n }\n const stats = fs.statSync(huntsDir);\n if (!stats.isDirectory()) {\n throw new Error(`Hunts path is not a directory: ${huntsDir}`);\n }\n\n const results: string[] = [];\n\n function scanDir(dir: string) {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isFile() && entry.name.endsWith(\".yml\")) {\n const fullPath = path.join(dir, entry.name);\n const relative = path.relative(huntsDir, fullPath);\n results.push(relative.replace(/\\.yml$/, \"\"));\n } else if (entry.isDirectory()) {\n scanDir(path.join(dir, entry.name));\n }\n }\n }\n\n scanDir(huntsDir);\n return results.sort((a, b) => a.localeCompare(b));\n}\n","import { z } from \"zod\";\nimport { SUPPORTED_BROWSER_ENGINES, type Step } from \"../types/index.js\";\nimport { isValidHuntName } from \"./hunt-name.js\";\n\n// Web target keeps `type` optional so pre-existing configs (`target: { url }`)\n// parse unchanged and default to the web target.\nconst webTargetSchema = z\n .object({\n type: z.literal(\"web\").optional(),\n url: z.string().min(1)\n })\n .strict();\n\n// macOS native target (experimental, PROWL-048): requires `type: \"macos\"` and an\n// app (bundle id or .app path); `url` is not accepted.\nconst macosTargetSchema = z\n .object({\n type: z.literal(\"macos\"),\n app: z.string().min(1)\n })\n .strict();\n\n// macos is tried first because it is the only branch with `type: \"macos\"`; a bare\n// `{ url }` (no type) falls through to web. An object with neither `url` nor a\n// macos `app` matches neither branch and is rejected with a union error.\nexport const targetSchema = z.union([macosTargetSchema, webTargetSchema]);\n\nexport const configSchema = z\n .object({\n target: targetSchema,\n browser: z\n .object({\n headless: z.boolean().optional(),\n slowMo: z.number().optional(),\n timeout: z.number().optional(),\n engine: z.enum(SUPPORTED_BROWSER_ENGINES).optional(),\n channel: z.enum([\n \"chromium\",\n \"chrome\", \"chrome-beta\", \"chrome-canary\", \"chrome-dev\",\n \"msedge\", \"msedge-beta\", \"msedge-canary\", \"msedge-dev\"\n ]).optional(),\n viewport: z\n .union([\n z.enum([\"mobile\", \"tablet\", \"desktop\"]),\n z.object({ width: z.number().int().positive(), height: z.number().int().positive() }).strict()\n ])\n .optional()\n })\n .optional(),\n artifacts: z\n .object({\n screenshots: z.enum([\"on-failure\", \"all\"]).optional(),\n networkHar: z.boolean().optional(),\n console: z.boolean().optional(),\n junit: z.boolean().optional()\n })\n .optional(),\n assertions: z\n .object({\n noConsoleErrors: z.boolean().optional(),\n noNetworkErrors: z.boolean().optional(),\n maxTotalTimeMs: z.number().optional(),\n networkIgnorePatterns: z.array(z.string()).optional()\n })\n .optional(),\n guardrails: z\n .object({\n maxSteps: z.number().optional(),\n allowedDomains: z.array(z.string()).optional(),\n allowedApps: z.array(z.string()).optional(),\n forbiddenSelectors: z.array(z.string()).optional(),\n selfHealing: z.boolean().optional()\n })\n .optional(),\n auth: z\n .object({\n storageStatePath: z.string().optional()\n })\n .optional(),\n history: z\n .object({\n maxRuns: z.number().int().positive().optional()\n })\n .optional(),\n bugLog: z\n .object({\n enabled: z.boolean().optional(),\n backlogPath: z.string().min(1).optional(),\n resolvedPath: z.string().min(1).optional()\n })\n .strict()\n .optional(),\n tracing: z\n .object({\n header: z.string().min(1).optional()\n })\n .strict()\n .optional(),\n reliability: z\n .object({\n flakyThreshold: z.number().min(0).max(1).optional()\n })\n .strict()\n .optional()\n })\n .strict();\n\nexport const navigateStepSchema = z.object({ navigate: z.string().min(1) }).strict();\nexport const clickStepSchema = z\n .object({\n click: z.union([z.object({ selector: z.string().min(1) }).strict(), z.string().min(1)])\n })\n .strict();\n\nconst singleKeyValueSchema = z\n .record(z.string().min(1), z.string())\n .refine((record) => Object.keys(record).length === 1, {\n message: \"Expected exactly one key-value pair\"\n });\n\nexport const fillStepSchema = z\n .object({\n fill: z.union([\n z.object({ selector: z.string().min(1), value: z.string() }).strict(),\n singleKeyValueSchema\n ])\n })\n .strict();\nexport const typeStepSchema = z.object({ type: z.string() }).strict();\nexport const pressStepSchema = z\n .object({\n press: z.object({ selector: z.string().min(1), key: z.string().min(1) }).strict()\n })\n .strict();\nexport const waitForSelectorStepSchema = z\n .object({\n waitForSelector: z\n .object({ selector: z.string().min(1), timeout: z.number().optional() })\n .strict()\n })\n .strict();\nexport const waitStepSchema = z\n .object({\n wait: z.union([\n z.string().min(1),\n z.object({ for: z.string().min(1), timeout: z.number().optional() }).strict()\n ])\n })\n .strict();\nexport const waitForUrlStepSchema = z\n .object({\n waitForUrl: z.object({ value: z.string().min(1), timeout: z.number().optional() }).strict()\n })\n .strict();\nexport const waitForNetworkIdleStepSchema = z\n .object({\n waitForNetworkIdle: z.object({ timeout: z.number().optional() }).strict()\n })\n .strict();\nexport const selectOptionStepSchema = z\n .object({\n selectOption: z.object({ selector: z.string().min(1), value: z.string() }).strict()\n })\n .strict();\nexport const selectStepSchema = z\n .object({\n select: singleKeyValueSchema\n })\n .strict();\nexport const onDialogStepSchema = z\n .object({\n onDialog: z.object({ action: z.enum([\"accept\", \"dismiss\"]) }).strict()\n })\n .strict();\nexport const setInputFilesStepSchema = z\n .object({\n setInputFiles: z\n .object({\n selector: z.string().min(1),\n files: z.union([z.string().min(1), z.array(z.string().min(1)).min(1)])\n })\n .strict()\n })\n .strict();\nexport const inlineAssertStepSchema = z\n .object({\n assert: z\n .object({\n visible: z.string().min(1).optional(),\n notVisible: z.string().min(1).optional(),\n urlIncludes: z.string().min(1).optional(),\n urlEquals: z.string().min(1).optional()\n })\n .strict()\n .refine(\n (value) =>\n [value.visible, value.notVisible, value.urlIncludes, value.urlEquals].filter(\n (entry) => entry !== undefined\n ).length === 1,\n {\n message: \"assert requires exactly one of visible, notVisible, urlIncludes, urlEquals\"\n }\n )\n })\n .strict();\nexport const runHuntStepSchema = z\n .object({\n runHunt: z.union([\n z\n .string()\n .min(1)\n .refine(isValidHuntName, {\n message:\n \"Invalid hunt name. Use only letters, numbers, hyphens, underscores, and forward slashes.\"\n }),\n z\n .object({\n name: z\n .string()\n .min(1)\n .refine(isValidHuntName, {\n message:\n \"Invalid hunt name. Use only letters, numbers, hyphens, underscores, and forward slashes.\"\n }),\n vars: z.record(z.string(), z.string()).optional()\n })\n .strict()\n ])\n })\n .strict();\nexport const hoverStepSchema = z\n .object({\n hover: z.object({ selector: z.string().min(1) }).strict()\n })\n .strict();\nexport const scrollStepSchema = z\n .object({\n scroll: z\n .object({\n direction: z.enum([\"up\", \"down\", \"left\", \"right\"]),\n amount: z.number().optional()\n })\n .strict()\n })\n .strict();\nexport const scrollToStepSchema = z\n .object({\n scrollTo: z.object({ selector: z.string().min(1) }).strict()\n })\n .strict();\nexport const screenshotStepSchema = z\n .object({\n screenshot: z.object({ name: z.string().optional() }).strict()\n })\n .strict();\n\n// Recursive step schemas use z.lazy() to reference stepSchema before it's defined\nexport const ifStepSchema = z\n .object({\n if: z\n .object({\n visible: z.string().min(1).optional(),\n notVisible: z.string().min(1).optional(),\n then: z.lazy(() => z.array(stepSchema).min(1)),\n else: z.lazy(() => z.array(stepSchema).min(1)).optional()\n })\n .strict()\n .refine(\n (value) =>\n [value.visible, value.notVisible].filter((v) => v !== undefined).length === 1,\n { message: \"if requires exactly one of visible or notVisible\" }\n )\n })\n .strict();\n\nexport const repeatStepSchema = z\n .object({\n repeat: z\n .object({\n times: z.number().int().positive().optional(),\n while: z\n .object({\n visible: z.string().min(1).optional(),\n notVisible: z.string().min(1).optional()\n })\n .strict()\n .refine(\n (value) =>\n [value.visible, value.notVisible].filter((v) => v !== undefined).length === 1,\n { message: \"while requires exactly one of visible or notVisible\" }\n )\n .optional(),\n maxIterations: z.number().int().positive().optional(),\n steps: z.lazy(() => z.array(stepSchema).min(1))\n })\n .strict()\n .refine((value) => !(value.times !== undefined && value.while !== undefined), {\n message: \"repeat requires either times or while, not both\"\n })\n .refine((value) => value.times !== undefined || value.while !== undefined, {\n message: \"repeat requires either times or while\"\n })\n .refine((value) => !(value.while !== undefined && value.maxIterations === undefined), {\n message: \"while requires maxIterations\"\n })\n })\n .strict();\n\nexport const mockRouteStepSchema = z\n .object({\n mockRoute: z\n .object({\n url: z.string().min(1),\n response: z\n .object({\n status: z.number().int(),\n contentType: z.string().min(1).optional(),\n body: z.string().min(1).optional(),\n file: z.string().min(1).optional()\n })\n .strict()\n .refine(\n (value) =>\n [value.body, value.file].filter((v) => v !== undefined).length === 1,\n { message: \"response requires exactly one of body or file\" }\n )\n })\n .strict()\n })\n .strict();\n\nexport const unmockRouteStepSchema = z\n .object({\n unmockRoute: z.union([\n z.string().min(1),\n z.object({ url: z.string().min(1) }).strict()\n ])\n })\n .strict();\n\nexport const evalScriptStepSchema = z\n .object({\n evalScript: z.union([\n z.string().min(1),\n z\n .object({\n expression: z.string().min(1),\n as: z.string().min(1).optional()\n })\n .strict()\n ])\n })\n .strict();\n\nexport const runScriptStepSchema = z\n .object({\n runScript: z.object({ file: z.string().min(1) }).strict()\n })\n .strict();\n\nexport const assertScreenshotStepSchema = z\n .object({\n assertScreenshot: z\n .object({\n name: z.string().min(1),\n threshold: z.number().min(0).max(1).optional()\n })\n .strict()\n })\n .strict();\n\nexport const copyTextStepSchema = z\n .object({\n copyText: z\n .object({\n selector: z.string().min(1),\n as: z.string().min(1)\n })\n .strict()\n })\n .strict();\n\nexport const waitForDownloadStepSchema = z\n .object({\n waitForDownload: z.union([\n z\n .object({\n filename: z.string().min(1).optional(),\n timeout: z.number().int().positive().optional()\n })\n .strict(),\n z.null()\n ])\n })\n .strict();\n\nexport const stepSchema: z.ZodType<Step> = z.union([\n navigateStepSchema,\n clickStepSchema,\n fillStepSchema,\n typeStepSchema,\n pressStepSchema,\n waitStepSchema,\n selectOptionStepSchema,\n selectStepSchema,\n onDialogStepSchema,\n setInputFilesStepSchema,\n inlineAssertStepSchema,\n runHuntStepSchema,\n waitForSelectorStepSchema,\n waitForUrlStepSchema,\n waitForNetworkIdleStepSchema,\n hoverStepSchema,\n scrollStepSchema,\n scrollToStepSchema,\n screenshotStepSchema,\n ifStepSchema,\n repeatStepSchema,\n mockRouteStepSchema,\n unmockRouteStepSchema,\n evalScriptStepSchema,\n runScriptStepSchema,\n assertScreenshotStepSchema,\n copyTextStepSchema,\n waitForDownloadStepSchema\n]);\n\nexport const assertionSchema = z.union([\n z.object({ selectorExists: z.string().min(1) }).strict(),\n z.object({ selectorNotExists: z.string().min(1) }).strict(),\n z.object({ urlIncludes: z.string().min(1) }).strict(),\n z.object({ urlEquals: z.string().min(1) }).strict(),\n z.object({ noConsoleErrors: z.boolean() }).strict(),\n z.object({ noNetworkErrors: z.boolean() }).strict()\n]);\n\nexport const huntSchema = z\n .object({\n name: z.string().optional(),\n description: z.string().optional(),\n tags: z.array(z.string().min(1)).optional(),\n vars: z.record(z.string(), z.string()).optional(),\n steps: z.array(stepSchema),\n assertions: z.array(assertionSchema).optional(),\n retry: z\n .object({\n maxRetries: z.number().int().min(0),\n delay: z.number().int().min(0).optional()\n })\n .strict()\n .optional()\n })\n .strict();\n","export const SUPPORTED_BROWSER_ENGINES = [\"chromium\", \"firefox\", \"webkit\"] as const;\n\nexport type BrowserEngine = (typeof SUPPORTED_BROWSER_ENGINES)[number];\n\nexport type BrowserChannel =\n | \"chromium\"\n | \"chrome\" | \"chrome-beta\" | \"chrome-canary\" | \"chrome-dev\"\n | \"msedge\" | \"msedge-beta\" | \"msedge-canary\" | \"msedge-dev\";\n\nexport type Viewport = {\n width: number;\n height: number;\n};\n\n/** Web execution target (default): drives a browser at `url`. */\nexport type WebTarget = {\n type: \"web\";\n url: string;\n};\n\n/**\n * macOS native execution target (experimental, PROWL-048). `app` is a bundle\n * identifier (e.g. `com.example.App`) or an absolute path to a `.app` bundle.\n * App-path guardrails are matched against the path, bundle name, and\n * `CFBundleIdentifier` from `Contents/Info.plist` when it is readable.\n */\nexport type MacosTarget = {\n type: \"macos\";\n app: string;\n};\n\nexport type Target = WebTarget | MacosTarget;\n\nexport type Config = {\n target: Target;\n browser: {\n headless: boolean;\n slowMo: number;\n timeout: number;\n engine: BrowserEngine;\n channel?: BrowserChannel;\n viewport: Viewport;\n };\n artifacts: {\n screenshots: \"on-failure\" | \"all\";\n networkHar: boolean;\n console: boolean;\n junit: boolean;\n };\n assertions: {\n noConsoleErrors: boolean;\n noNetworkErrors: boolean;\n maxTotalTimeMs: number;\n networkIgnorePatterns: string[];\n };\n guardrails: {\n maxSteps: number;\n allowedDomains: string[];\n /** Native scope analog of `allowedDomains`: allowed bundle IDs, app bundle names, or `.app` paths. */\n allowedApps: string[];\n forbiddenSelectors: string[];\n selfHealing: boolean;\n };\n auth: {\n storageStatePath?: string;\n };\n history: {\n maxRuns: number;\n };\n bugLog?: BugLogConfig;\n tracing?: TracingConfig;\n reliability?: ReliabilityConfig;\n};\n\nexport type ReliabilityConfig = {\n /** Flake score (0-1) at or above which a hunt is flagged flaky (default 0.3). */\n flakyThreshold?: number;\n};\n\nexport type TracingConfig = {\n /** Response header carrying the distributed-trace id (default \"traceparent\"). */\n header?: string;\n};\n\nexport type BugLogConfig = {\n enabled?: boolean;\n backlogPath?: string;\n resolvedPath?: string;\n};\n\nexport type HistoryEntry = {\n hunt: string;\n status: \"pass\" | \"fail\";\n durationMs: number;\n startedAt: string;\n runDir?: string;\n};\n\nexport type HistoryFile = {\n entries: HistoryEntry[];\n};\n\nexport type Hunt = {\n name?: string;\n description?: string;\n tags?: string[];\n vars?: Record<string, string>;\n steps: Step[];\n assertions?: Assertion[];\n retry?: {\n maxRetries: number;\n delay?: number;\n };\n};\n\nexport type NavigateStep = { navigate: string };\nexport type ClickStep = { click: { selector: string } | string };\nexport type FillStep = { fill: { selector: string; value: string } | Record<string, string> };\nexport type TypeStep = { type: string };\nexport type PressStep = { press: { selector: string; key: string } };\nexport type WaitForSelectorStep = { waitForSelector: { selector: string; timeout?: number } };\nexport type WaitStep = { wait: string | { for: string; timeout?: number } };\nexport type WaitForUrlStep = { waitForUrl: { value: string; timeout?: number } };\nexport type WaitForNetworkIdleStep = { waitForNetworkIdle: { timeout?: number } };\nexport type SelectOptionStep = { selectOption: { selector: string; value: string } };\nexport type SelectStep = { select: Record<string, string> };\nexport type OnDialogStep = { onDialog: { action: \"accept\" | \"dismiss\" } };\nexport type SetInputFilesStep = { setInputFiles: { selector: string; files: string | string[] } };\nexport type InlineAssertStep = {\n assert: {\n visible?: string;\n notVisible?: string;\n urlIncludes?: string;\n urlEquals?: string;\n };\n};\nexport type RunHuntStep = { runHunt: string | { name: string; vars?: Record<string, string> } };\nexport type HoverStep = { hover: { selector: string } };\nexport type ScrollStep = { scroll: { direction: \"up\" | \"down\" | \"left\" | \"right\"; amount?: number } };\nexport type ScrollToStep = { scrollTo: { selector: string } };\nexport type ScreenshotStep = { screenshot: { name?: string } };\nexport type IfStep = {\n if: {\n visible?: string;\n notVisible?: string;\n then: Step[];\n else?: Step[];\n };\n};\nexport type RepeatStep = {\n repeat: {\n times?: number;\n while?: { visible?: string; notVisible?: string };\n maxIterations?: number;\n steps: Step[];\n };\n};\nexport type MockRouteStep = {\n mockRoute: {\n url: string;\n response: {\n status: number;\n contentType?: string;\n body?: string;\n file?: string;\n };\n };\n};\nexport type UnmockRouteStep = { unmockRoute: string | { url: string } };\nexport type EvalScriptStep = {\n evalScript: string | { expression: string; as?: string };\n};\nexport type RunScriptStep = {\n runScript: { file: string };\n};\nexport type CopyTextStep = { copyText: { selector: string; as: string } };\nexport type WaitForDownloadStep = {\n waitForDownload: { filename?: string; timeout?: number } | null;\n};\nexport type AssertScreenshotStep = {\n assertScreenshot: {\n name: string;\n threshold?: number;\n };\n};\n\nexport type Step =\n | NavigateStep\n | ClickStep\n | FillStep\n | TypeStep\n | PressStep\n | WaitStep\n | SelectOptionStep\n | SelectStep\n | OnDialogStep\n | SetInputFilesStep\n | InlineAssertStep\n | RunHuntStep\n | WaitForSelectorStep\n | WaitForUrlStep\n | WaitForNetworkIdleStep\n | HoverStep\n | ScrollStep\n | ScrollToStep\n | ScreenshotStep\n | IfStep\n | RepeatStep\n | MockRouteStep\n | UnmockRouteStep\n | EvalScriptStep\n | RunScriptStep\n | AssertScreenshotStep\n | CopyTextStep\n | WaitForDownloadStep;\n\nexport type Assertion =\n | { selectorExists: string }\n | { selectorNotExists: string }\n | { urlIncludes: string }\n | { urlEquals: string }\n | { noConsoleErrors: boolean }\n | { noNetworkErrors: boolean };\n\nexport type StepResult = {\n type: string;\n status: \"pass\" | \"fail\";\n durationMs: number;\n selector?: string;\n value?: string;\n error?: string;\n screenshot?: string;\n /** Original selector when the step was completed via a self-healed selector. */\n healedFrom?: string;\n};\n\nexport type AssertionResult = {\n type: string;\n value?: string | boolean;\n status: \"pass\" | \"fail\";\n error?: string;\n};\n\nexport type RunArtifacts = {\n summary?: string;\n screenshots?: string[];\n console?: string;\n trace?: string;\n networkHar?: string;\n junit?: string;\n};\n\nexport type TraceCorrelation = {\n url: string;\n status: number;\n traceId: string;\n header: string;\n};\n\nexport type RunResult = {\n status: \"pass\" | \"fail\";\n exitCode: 0 | 1;\n startedAt: string;\n durationMs: number;\n hunt: string;\n targetUrl: string;\n steps: StepResult[];\n assertions: AssertionResult[];\n artifacts: RunArtifacts;\n traceCorrelations?: TraceCorrelation[];\n};\n\nexport type CiHuntResult = {\n hunt: string;\n status: \"pass\" | \"fail\" | \"skipped\";\n durationMs: number;\n runDir?: string;\n error?: string;\n};\n\nexport type CiStatus = \"pass\" | \"fail\" | \"no-hunts\" | \"all-skipped\";\n\nexport type CiFlakyHunt = {\n hunt: string;\n score: number;\n};\n\nexport type CiFailureCluster = {\n cause: string;\n stepType?: string;\n selector?: string;\n error: string;\n count: number;\n hunts: string[];\n};\n\nexport type CiResult = {\n status: CiStatus;\n startedAt: string;\n durationMs: number;\n totalHunts: number;\n passed: number;\n failed: number;\n skipped: number;\n hunts: CiHuntResult[];\n /** Hunts whose flake score is at/above the configured threshold (omitted when none). */\n flaky?: CiFlakyHunt[];\n /** Groups of failed hunts sharing a common cause (omitted when none). */\n clusters?: CiFailureCluster[];\n};\n","const HUNT_NAME_PATTERN = /^[A-Za-z0-9_-]+(?:\\/[A-Za-z0-9_-]+)*$/;\n\nexport function isValidHuntName(name: string): boolean {\n return HUNT_NAME_PATTERN.test(name);\n}\n\nexport function assertValidHuntName(name: string): void {\n if (!isValidHuntName(name)) {\n throw new Error(\n `Invalid hunt name: \"${name}\". Use only letters, numbers, hyphens, underscores, and forward slashes.`\n );\n }\n}\n"],"mappings":"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","names":[]}
|