@qulib/core 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +135 -8
  2. package/dist/__tests__/cli-smoke-fixture.d.ts +2 -0
  3. package/dist/__tests__/cli-smoke-fixture.d.ts.map +1 -0
  4. package/dist/__tests__/cli-smoke-fixture.js +58 -0
  5. package/dist/__tests__/fixture-server.d.ts +6 -0
  6. package/dist/__tests__/fixture-server.d.ts.map +1 -0
  7. package/dist/__tests__/fixture-server.js +141 -0
  8. package/dist/analyze.d.ts.map +1 -1
  9. package/dist/analyze.js +84 -5
  10. package/dist/cli/auth-login-run.d.ts.map +1 -1
  11. package/dist/cli/auth-login-run.js +26 -2
  12. package/dist/cli/index.js +12 -6
  13. package/dist/index.d.ts +6 -5
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +5 -5
  16. package/dist/llm/providers/anthropic.js +1 -1
  17. package/dist/phases/observe.js +2 -2
  18. package/dist/phases/think-finalize.d.ts.map +1 -1
  19. package/dist/phases/think-finalize.js +7 -1
  20. package/dist/phases/think.js +1 -1
  21. package/dist/schemas/automation-maturity.schema.d.ts +8 -0
  22. package/dist/schemas/automation-maturity.schema.d.ts.map +1 -1
  23. package/dist/schemas/automation-maturity.schema.js +1 -0
  24. package/dist/schemas/repo-analysis.schema.d.ts +7 -0
  25. package/dist/schemas/repo-analysis.schema.d.ts.map +1 -1
  26. package/dist/telemetry/telemetry.interface.d.ts +1 -1
  27. package/dist/telemetry/telemetry.interface.d.ts.map +1 -1
  28. package/dist/tools/apply-auth.d.ts +4 -0
  29. package/dist/tools/apply-auth.d.ts.map +1 -0
  30. package/dist/tools/apply-auth.js +35 -0
  31. package/dist/tools/auth/apply.d.ts +4 -0
  32. package/dist/tools/auth/apply.d.ts.map +1 -0
  33. package/dist/tools/auth/apply.js +35 -0
  34. package/dist/tools/auth/block-gap.d.ts +9 -0
  35. package/dist/tools/auth/block-gap.d.ts.map +1 -0
  36. package/dist/tools/auth/block-gap.js +52 -0
  37. package/dist/tools/auth/custom-providers.d.ts +15 -0
  38. package/dist/tools/auth/custom-providers.d.ts.map +1 -0
  39. package/dist/tools/auth/custom-providers.js +62 -0
  40. package/dist/tools/auth/detect.d.ts +23 -0
  41. package/dist/tools/auth/detect.d.ts.map +1 -0
  42. package/dist/tools/auth/detect.js +526 -0
  43. package/dist/tools/auth/detector.d.ts +23 -0
  44. package/dist/tools/auth/detector.d.ts.map +1 -0
  45. package/dist/tools/auth/detector.js +526 -0
  46. package/dist/tools/auth/explore.d.ts +4 -0
  47. package/dist/tools/auth/explore.d.ts.map +1 -0
  48. package/dist/tools/auth/explore.js +346 -0
  49. package/dist/tools/auth/explorer.d.ts +4 -0
  50. package/dist/tools/auth/explorer.d.ts.map +1 -0
  51. package/dist/tools/auth/explorer.js +346 -0
  52. package/dist/tools/auth/gaps.d.ts +9 -0
  53. package/dist/tools/auth/gaps.d.ts.map +1 -0
  54. package/dist/tools/auth/gaps.js +52 -0
  55. package/dist/tools/auth/oauth-providers.d.ts +7 -0
  56. package/dist/tools/auth/oauth-providers.d.ts.map +1 -0
  57. package/dist/tools/auth/oauth-providers.js +21 -0
  58. package/dist/tools/auth/providers.d.ts +7 -0
  59. package/dist/tools/auth/providers.d.ts.map +1 -0
  60. package/dist/tools/auth/providers.js +21 -0
  61. package/dist/tools/auth/surface-analyzer.d.ts +4 -0
  62. package/dist/tools/auth/surface-analyzer.d.ts.map +1 -0
  63. package/dist/tools/auth/surface-analyzer.js +170 -0
  64. package/dist/tools/auth/surface.d.ts +4 -0
  65. package/dist/tools/auth/surface.d.ts.map +1 -0
  66. package/dist/tools/auth/surface.js +170 -0
  67. package/dist/tools/auth/user-providers.d.ts +15 -0
  68. package/dist/tools/auth/user-providers.d.ts.map +1 -0
  69. package/dist/tools/auth/user-providers.js +62 -0
  70. package/dist/tools/auth-block-gap.d.ts +6 -0
  71. package/dist/tools/auth-block-gap.d.ts.map +1 -1
  72. package/dist/tools/auth-block-gap.js +42 -9
  73. package/dist/tools/auth-detector.d.ts +9 -8
  74. package/dist/tools/auth-detector.d.ts.map +1 -1
  75. package/dist/tools/auth-detector.js +106 -8
  76. package/dist/tools/explorers/browser.d.ts +3 -0
  77. package/dist/tools/explorers/browser.d.ts.map +1 -0
  78. package/dist/tools/explorers/browser.js +13 -0
  79. package/dist/tools/explorers/cypress-explorer.d.ts +8 -0
  80. package/dist/tools/explorers/cypress-explorer.d.ts.map +1 -0
  81. package/dist/tools/explorers/cypress-explorer.js +5 -0
  82. package/dist/tools/explorers/cypress.d.ts +8 -0
  83. package/dist/tools/explorers/cypress.d.ts.map +1 -0
  84. package/dist/tools/explorers/cypress.js +5 -0
  85. package/dist/tools/explorers/explorer.interface.d.ts +7 -0
  86. package/dist/tools/explorers/explorer.interface.d.ts.map +1 -0
  87. package/dist/tools/explorers/explorer.interface.js +1 -0
  88. package/dist/tools/explorers/factory.d.ts +4 -0
  89. package/dist/tools/explorers/factory.d.ts.map +1 -0
  90. package/dist/tools/explorers/factory.js +12 -0
  91. package/dist/tools/explorers/playwright-explorer.d.ts +8 -0
  92. package/dist/tools/explorers/playwright-explorer.d.ts.map +1 -0
  93. package/dist/tools/explorers/playwright-explorer.js +172 -0
  94. package/dist/tools/explorers/playwright.d.ts +8 -0
  95. package/dist/tools/explorers/playwright.d.ts.map +1 -0
  96. package/dist/tools/explorers/playwright.js +172 -0
  97. package/dist/tools/explorers/types.d.ts +7 -0
  98. package/dist/tools/explorers/types.d.ts.map +1 -0
  99. package/dist/tools/explorers/types.js +1 -0
  100. package/dist/tools/playwright-explorer.js +1 -1
  101. package/dist/tools/repo/detect-framework.d.ts +15 -0
  102. package/dist/tools/repo/detect-framework.d.ts.map +1 -0
  103. package/dist/tools/repo/detect-framework.js +153 -0
  104. package/dist/tools/repo/framework-detector.d.ts +15 -0
  105. package/dist/tools/repo/framework-detector.d.ts.map +1 -0
  106. package/dist/tools/repo/framework-detector.js +153 -0
  107. package/dist/tools/repo/scan.d.ts +19 -0
  108. package/dist/tools/repo/scan.d.ts.map +1 -0
  109. package/dist/tools/repo/scan.js +181 -0
  110. package/dist/tools/repo/scanner.d.ts +19 -0
  111. package/dist/tools/repo/scanner.d.ts.map +1 -0
  112. package/dist/tools/repo/scanner.js +181 -0
  113. package/dist/tools/scoring/automation-maturity.d.ts +4 -0
  114. package/dist/tools/scoring/automation-maturity.d.ts.map +1 -0
  115. package/dist/tools/scoring/automation-maturity.js +231 -0
  116. package/dist/tools/scoring/gap-engine.d.ts +8 -0
  117. package/dist/tools/scoring/gap-engine.d.ts.map +1 -0
  118. package/dist/tools/scoring/gap-engine.js +138 -0
  119. package/dist/tools/scoring/gaps.d.ts +8 -0
  120. package/dist/tools/scoring/gaps.d.ts.map +1 -0
  121. package/dist/tools/scoring/gaps.js +138 -0
  122. package/dist/tools/scoring/public-surface.d.ts +5 -0
  123. package/dist/tools/scoring/public-surface.d.ts.map +1 -0
  124. package/dist/tools/scoring/public-surface.js +13 -0
  125. package/package.json +3 -3
package/README.md CHANGED
@@ -77,6 +77,24 @@ qulib analyze --url https://app.example.com --auth-storage-state ./qulib-storage
77
77
 
78
78
  The storage state is just a JSON file of cookies and localStorage — keep it private, treat it like a credential.
79
79
 
80
+ #### Storage state is validated before crawl
81
+
82
+ Qulib now validates the provided storage state before doing any work. If the file is missing, unreadable, empty, on the wrong origin, or carries a session that is already expired, Qulib stops with an honest `blocked` result (no fake `releaseConfidence`) and a structured gap explaining how to recover. The validator reports one of these stable reason codes:
83
+
84
+ | Reason code | Meaning |
85
+ | ------------------------- | ----------------------------------------------------------------------- |
86
+ | `missing-file` | Path passed to `--auth-storage-state` does not exist. |
87
+ | `unreadable-file` | File exists but the process can't read it (permissions). |
88
+ | `invalid-json` | File is present and readable but not valid JSON. |
89
+ | `no-auth-cookies` | File parses, but has zero cookies and zero localStorage entries. |
90
+ | `wrong-origin` | Session redirects to a different origin (host/port/scheme mismatch). |
91
+ | `expired-or-unauthorized` | Loaded session shows the login form again, or the app returns 401/403. |
92
+ | `unknown` | Validation could not be completed for an unexpected reason. |
93
+
94
+ Origin matching is strict — `https://app.example` and `https://www.app.example` are different origins, as are `http://localhost:3000` and `http://localhost:4000`. Re-run `qulib auth login` against the same origin you plan to `analyze`.
95
+
96
+ Relatedly, `qulib auth login` will now refuse to save a storage state if the browser ends the flow on a different origin than `--base-url` (a federated/SSO redirect that never returned to the app). This prevents Qulib from quietly persisting an IdP-domain session that would later produce false-confidence scans.
97
+
80
98
  ### Multi-path auth exploration (`explore-auth`)
81
99
 
82
100
  For unfamiliar apps (especially enterprise SSO with several buttons), run **`qulib explore-auth --url <url>`** before `analyze`. The JSON lists every detected path (built-in OAuth names like Google/Clever, **heuristic** unknown buttons such as tenant-specific SSO labels, password forms, and magic-link copy) plus **`suggestedAgentBehavior`** for the agent.
@@ -194,16 +212,24 @@ TypeScript (strict, NodeNext), Commander, Zod, Playwright, @axe-core/playwright,
194
212
  ```text
195
213
  src/
196
214
  adapters/ # test rendering adapters
197
- analyze.ts # programmatic API (also used by @qulib/mcp)
198
- cli/ # CLI entry
199
- harness/ # state + decision logging
200
- llm/ # LLM contracts
201
- phases/ # observe / think / act
202
- reporters/ # JSON + Markdown reports
203
- schemas/ # Zod schemas
204
- tools/ # explorers, auth, gap engine, repo scanner
215
+ analyze.ts # programmatic API (also used by @qulib/mcp)
216
+ cli/ # CLI entry
217
+ harness/ # state + decision logging
218
+ llm/ # LLM contracts
219
+ phases/ # observe / think / act
220
+ reporters/ # JSON + Markdown reports
221
+ schemas/ # Zod schemas
222
+ telemetry/ # event sink + URL redaction
223
+ tools/
224
+ auth/ # detection, exploration, validation, providers, gap builders
225
+ explorers/ # browser launch, Playwright/Cypress crawlers, factory
226
+ repo/ # repo scanner, framework detection
227
+ scoring/ # gap engine, automation maturity, public surface
228
+ __tests__/ # integration and wiring tests live in __tests__/ in each folder
205
229
  ```
206
230
 
231
+ A contributor map of which folder to touch for each kind of change lives at [`docs/source-map.md`](../../docs/source-map.md).
232
+
207
233
  Repo rules: see [`CLAUDE.md`](../../CLAUDE.md).
208
234
 
209
235
  ## Configuration
@@ -251,6 +277,107 @@ npm run analyze -- --url https://example.com --ephemeral > report.bundle.json
251
277
  npm run clean
252
278
  ```
253
279
 
280
+ ## Minimum config
281
+
282
+ Smallest legal `qulib.config.ts`:
283
+
284
+ ```ts
285
+ import type { HarnessConfig } from './src/schemas/config.schema.js';
286
+
287
+ const config: HarnessConfig = {
288
+ maxPagesToScan: 20,
289
+ maxDepth: 3,
290
+ timeoutMs: 30000,
291
+ };
292
+
293
+ export default config;
294
+ ```
295
+
296
+ All other fields inherit from schema defaults or CLI/runtime defaults.
297
+
298
+ ## Scan walkthroughs (copy-paste)
299
+
300
+ ### 1) Public scan
301
+
302
+ ```bash
303
+ npx @qulib/core analyze --url https://yourapp.com
304
+ ```
305
+
306
+ ### 2) Auth-blocked scan (honest blocked mode)
307
+
308
+ ```bash
309
+ npx @qulib/core analyze --url https://yourapp.com/auth
310
+ ```
311
+
312
+ When auth blocks access and no auth config is supplied, Qulib reports `status: "blocked"` (or `partial` if it could still crawl some public pages). This is intentional honesty, not a failure mode.
313
+
314
+ ### 3) Authenticated scan with storage state
315
+
316
+ ```bash
317
+ # Capture once (manual OAuth/SSO-safe flow)
318
+ qulib auth init --base-url https://yourapp.com
319
+
320
+ # Reuse saved session
321
+ qulib analyze --url https://yourapp.com --auth-storage-state ./qulib-storage-state.json
322
+ ```
323
+
324
+ ## Sample report (fixture baseline)
325
+
326
+ From the local fixture baseline used in v0.5.0 PR 1/2:
327
+
328
+ ```json
329
+ {
330
+ "status": "complete",
331
+ "releaseConfidence": 68,
332
+ "gaps": [
333
+ "... 4 total gap items ..."
334
+ ]
335
+ }
336
+ ```
337
+
338
+ Use these as conservative reference numbers:
339
+ - public fixture (`/`): `releaseConfidence: 68/100`, `gaps: 4`
340
+ - auth-wall fixture (`/auth`): `releaseConfidence: 24/100`, `gaps: 2`
341
+ - broken fixture (`/broken`): `releaseConfidence: 0/100`, `gaps: 6`
342
+
343
+ ## MCP tools quick map
344
+
345
+ | Tool | When to use | Key input |
346
+ |---|---|---|
347
+ | `analyze_app` | Main QA scan for release confidence + gaps | `url`, optional `auth`, optional LLM knobs |
348
+ | `detect_auth` | Fast single-pass auth pattern guess | `url`, optional `timeoutMs` |
349
+ | `explore_auth` | Deeper auth-path discovery on unfamiliar apps | `url`, optional `timeoutMs` |
350
+ | `qulib_score_automation` | Score local repo automation maturity | absolute `repoPath`, optional `includeFullDimensions` |
351
+
352
+ ## Output directories
353
+
354
+ Qulib writes runtime artifacts to:
355
+
356
+ - `.scan-state/` — intermediate state (discovered routes, gap analysis snapshots, decision log)
357
+ - `output/` — final `report.json` and `report.md`
358
+
359
+ Both are gitignored and safe to delete; Qulib recreates them on the next non-ephemeral run.
360
+
361
+ ## ANTHROPIC_API_KEY (LLM scenarios)
362
+
363
+ For MCP-hosted usage, set `ANTHROPIC_API_KEY` in your host's `env` block:
364
+
365
+ ```json
366
+ {
367
+ "mcpServers": {
368
+ "qulib": {
369
+ "command": "npx",
370
+ "args": ["@qulib/mcp"],
371
+ "env": {
372
+ "ANTHROPIC_API_KEY": "sk-ant-..."
373
+ }
374
+ }
375
+ }
376
+ }
377
+ ```
378
+
379
+ Without this key, Qulib still runs deterministic checks (crawl, a11y, links, console, scoring) and falls back to template scenarios instead of LLM-generated ones.
380
+
254
381
  ## Playwright browsers
255
382
 
256
383
  ```bash
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli-smoke-fixture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-smoke-fixture.d.ts","sourceRoot":"","sources":["../../src/__tests__/cli-smoke-fixture.ts"],"names":[],"mappings":""}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Offline CLI smoke: spawn `node bin/qulib.js analyze --url <fixture>` against
3
+ * the local fixture server and assert the CLI exited 0. Runnable script (not a
4
+ * node:test file) — invoked in CI by `node --import tsx/esm src/__tests__/cli-smoke-fixture.ts`.
5
+ *
6
+ * Removes the live `https://example.com` dependency from CI's smoke-test-cli job.
7
+ *
8
+ * The fixture server runs in this process; the CLI is spawned as a child. We use
9
+ * async `spawn` (not `spawnSync`) so the parent event loop stays free to serve
10
+ * the child's HTTP requests against the fixture.
11
+ */
12
+ import { spawn } from 'node:child_process';
13
+ import { dirname, resolve } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { startFixtureServer } from './fixture-server.js';
16
+ const __dir = dirname(fileURLToPath(import.meta.url));
17
+ const cliPath = resolve(__dir, '../../bin/qulib.js');
18
+ function runCli(url) {
19
+ return new Promise((resolvePromise, rejectPromise) => {
20
+ const child = spawn('node', [cliPath, 'analyze', '--url', url, '--ephemeral'], {
21
+ stdio: ['ignore', 'pipe', 'pipe'],
22
+ });
23
+ const stdoutChunks = [];
24
+ const stderrChunks = [];
25
+ child.stdout.on('data', (chunk) => stdoutChunks.push(chunk));
26
+ child.stderr.on('data', (chunk) => stderrChunks.push(chunk));
27
+ const timer = setTimeout(() => {
28
+ child.kill('SIGKILL');
29
+ rejectPromise(new Error('CLI smoke timed out after 120s'));
30
+ }, 120_000);
31
+ child.on('error', (err) => {
32
+ clearTimeout(timer);
33
+ rejectPromise(err);
34
+ });
35
+ child.on('close', (code) => {
36
+ clearTimeout(timer);
37
+ resolvePromise({
38
+ exitCode: code ?? -1,
39
+ stdout: Buffer.concat(stdoutChunks).toString('utf8'),
40
+ stderr: Buffer.concat(stderrChunks).toString('utf8'),
41
+ });
42
+ });
43
+ });
44
+ }
45
+ function assertCliPassed(result) {
46
+ if (result.exitCode !== 0) {
47
+ throw new Error(`CLI exited with code ${result.exitCode}\n--- stdout ---\n${result.stdout}\n--- stderr ---\n${result.stderr}`);
48
+ }
49
+ }
50
+ const handle = await startFixtureServer();
51
+ try {
52
+ const result = await runCli(`${handle.baseUrl}/`);
53
+ assertCliPassed(result);
54
+ console.log('[cli-smoke] ✔ CLI exited 0 against fixture public surface');
55
+ }
56
+ finally {
57
+ await handle.close();
58
+ }
@@ -0,0 +1,6 @@
1
+ export interface FixtureServerHandle {
2
+ baseUrl: string;
3
+ close: () => Promise<void>;
4
+ }
5
+ export declare function startFixtureServer(): Promise<FixtureServerHandle>;
6
+ //# sourceMappingURL=fixture-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture-server.d.ts","sourceRoot":"","sources":["../../src/__tests__/fixture-server.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAyGD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,mBAAmB,CAAC,CA0CvE"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Deterministic Node.js fixture server for offline Qulib integration tests.
3
+ *
4
+ * Serves the static HTML files in `packages/core/fixtures/` over loopback so
5
+ * the test suite never depends on a live website. Used by
6
+ * `analyze.fixtures.test.ts` and any future offline integration coverage.
7
+ *
8
+ * Never imported by product code. Helpers are private to this module; only
9
+ * `startFixtureServer` and `FixtureServerHandle` are exported.
10
+ */
11
+ import { createServer } from 'node:http';
12
+ import { readFile, stat } from 'node:fs/promises';
13
+ import { dirname, join, resolve } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ const HTML_CONTENT_TYPE = 'text/html; charset=utf-8';
16
+ const TEXT_CONTENT_TYPE = 'text/plain; charset=utf-8';
17
+ function resolveFixturesDir() {
18
+ const here = dirname(fileURLToPath(import.meta.url));
19
+ return resolve(here, '../../fixtures');
20
+ }
21
+ function routeToFile(pathname) {
22
+ if (pathname.includes('..'))
23
+ return null;
24
+ const fixturesDir = resolveFixturesDir();
25
+ if (pathname === '/' || pathname === '/index.html') {
26
+ return join(fixturesDir, 'public/index.html');
27
+ }
28
+ if (pathname === '/about') {
29
+ return join(fixturesDir, 'public/about.html');
30
+ }
31
+ if (pathname === '/features') {
32
+ return join(fixturesDir, 'public/features.html');
33
+ }
34
+ if (pathname === '/docs') {
35
+ return join(fixturesDir, 'public/index.html');
36
+ }
37
+ if (pathname === '/auth') {
38
+ return join(fixturesDir, 'auth-wall/index.html');
39
+ }
40
+ if (pathname === '/authenticated' || pathname.startsWith('/authenticated/')) {
41
+ return join(fixturesDir, 'authenticated/index.html');
42
+ }
43
+ if (pathname === '/broken') {
44
+ return join(fixturesDir, 'broken/index.html');
45
+ }
46
+ return null;
47
+ }
48
+ async function readFixture(filePath) {
49
+ return readFile(filePath);
50
+ }
51
+ function respond(res, status, body, contentType) {
52
+ const buf = typeof body === 'string' ? Buffer.from(body, 'utf8') : body;
53
+ res.writeHead(status, {
54
+ 'Content-Type': contentType,
55
+ 'Content-Length': buf.length,
56
+ 'Cache-Control': 'no-store',
57
+ });
58
+ res.end(buf);
59
+ }
60
+ function respondNotFound(res) {
61
+ respond(res, 404, 'Not found', TEXT_CONTENT_TYPE);
62
+ }
63
+ function respondServerError(res, message) {
64
+ respond(res, 500, `Fixture server error: ${message}`, TEXT_CONTENT_TYPE);
65
+ }
66
+ function respondMethodNotAllowed(res) {
67
+ res.writeHead(405, {
68
+ Allow: 'GET',
69
+ 'Content-Type': TEXT_CONTENT_TYPE,
70
+ 'Cache-Control': 'no-store',
71
+ });
72
+ res.end('Method Not Allowed');
73
+ }
74
+ async function handleRequest(req, res) {
75
+ try {
76
+ if (req.method !== 'GET') {
77
+ respondMethodNotAllowed(res);
78
+ return;
79
+ }
80
+ const rawUrl = req.url ?? '/';
81
+ let pathname;
82
+ try {
83
+ pathname = new URL(rawUrl, 'http://127.0.0.1').pathname;
84
+ }
85
+ catch {
86
+ respondNotFound(res);
87
+ return;
88
+ }
89
+ const filePath = routeToFile(pathname);
90
+ if (filePath === null) {
91
+ respondNotFound(res);
92
+ return;
93
+ }
94
+ const body = await readFixture(filePath);
95
+ respond(res, 200, body, HTML_CONTENT_TYPE);
96
+ }
97
+ catch (err) {
98
+ const message = err instanceof Error ? err.message : String(err);
99
+ respondServerError(res, message);
100
+ }
101
+ }
102
+ export async function startFixtureServer() {
103
+ const fixturesDir = resolveFixturesDir();
104
+ try {
105
+ const s = await stat(fixturesDir);
106
+ if (!s.isDirectory()) {
107
+ throw new Error(`fixtures path is not a directory: ${fixturesDir}`);
108
+ }
109
+ }
110
+ catch (err) {
111
+ const detail = err instanceof Error ? err.message : String(err);
112
+ throw new Error(`Fixture directory not found at ${fixturesDir}: ${detail}`);
113
+ }
114
+ const server = createServer((req, res) => {
115
+ void handleRequest(req, res);
116
+ });
117
+ await new Promise((resolvePromise, rejectPromise) => {
118
+ server.once('error', rejectPromise);
119
+ server.listen(0, '127.0.0.1', () => {
120
+ server.off('error', rejectPromise);
121
+ resolvePromise();
122
+ });
123
+ });
124
+ const address = server.address();
125
+ if (address === null || typeof address === 'string') {
126
+ server.close();
127
+ throw new Error('Fixture server did not return a usable address after listen');
128
+ }
129
+ const baseUrl = `http://127.0.0.1:${address.port}`;
130
+ return {
131
+ baseUrl,
132
+ close: () => new Promise((resolvePromise, rejectPromise) => {
133
+ server.close((err) => {
134
+ if (err)
135
+ rejectPromise(err);
136
+ else
137
+ resolvePromise();
138
+ });
139
+ }),
140
+ };
141
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../src/analyze.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAChG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAU7F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAGxE,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,0GAA0G;IAC1G,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,sFAAsF;IACtF,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,WAAW,EAAE,WAAW,CAAC;IACzB,8GAA8G;IAC9G,cAAc,EAAE,cAAc,CAAC;IAC/B,aAAa,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,0HAA0H;IAC1H,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;CACrC;AAcD,wBAAsB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA8JhF"}
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../src/analyze.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAChG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAU7F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAGxE,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,0GAA0G;IAC1G,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,sFAAsF;IACtF,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,WAAW,EAAE,WAAW,CAAC;IACzB,8GAA8G;IAC9G,cAAc,EAAE,cAAc,CAAC;IAC/B,aAAa,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,0HAA0H;IAC1H,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;CACrC;AAcD,wBAAsB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAqQhF"}
package/dist/analyze.js CHANGED
@@ -4,11 +4,11 @@ import { PublicSurfaceSchema } from './schemas/public-surface.schema.js';
4
4
  import { observe } from './phases/observe.js';
5
5
  import { think } from './phases/think.js';
6
6
  import { act } from './phases/act.js';
7
- import { detectAuth } from './tools/auth-detector.js';
8
- import { analyzeGaps, computeCoverageScore, computeQualityScoreFromGaps } from './tools/gap-engine.js';
9
- import { analyzeAuthSurfaceGaps } from './tools/auth-surface-analyzer.js';
10
- import { buildPublicSurface } from './tools/public-surface.js';
11
- import { buildAuthBlockGap } from './tools/auth-block-gap.js';
7
+ import { detectAuth, validateStorageState } from './tools/auth/detect.js';
8
+ import { analyzeGaps, computeCoverageScore, computeQualityScoreFromGaps } from './tools/scoring/gaps.js';
9
+ import { analyzeAuthSurfaceGaps } from './tools/auth/surface.js';
10
+ import { buildPublicSurface } from './tools/scoring/public-surface.js';
11
+ import { buildAuthBlockGap, buildStorageStateInvalidGap } from './tools/auth/gaps.js';
12
12
  import { finalizeGapAnalysisFromDraft } from './phases/think-finalize.js';
13
13
  import { emitTelemetry, redactUrlForTelemetry } from './telemetry/emit.js';
14
14
  function logScanEnd(progress, result) {
@@ -40,6 +40,85 @@ export async function analyzeApp(options) {
40
40
  hasAuth: Boolean(options.config.auth),
41
41
  });
42
42
  progress?.info(`Starting scan → ${options.url} maxPagesToScan=${options.config.maxPagesToScan}`);
43
+ if (options.config.auth?.type === 'storage-state') {
44
+ progress?.info('Validating provided storage state before crawl…');
45
+ const validation = await validateStorageState(options.url, options.config.auth.path, options.config.timeoutMs);
46
+ let targetOriginForTelemetry;
47
+ try {
48
+ targetOriginForTelemetry = new URL(options.url).origin;
49
+ }
50
+ catch {
51
+ targetOriginForTelemetry = '[unparseable-target-url]';
52
+ }
53
+ emitTelemetry(options.telemetry, 'auth.storage-state.validated', sessionId, {
54
+ targetOrigin: targetOriginForTelemetry,
55
+ valid: validation.valid,
56
+ reasonCode: validation.reasonCode,
57
+ storageStateProvided: true,
58
+ });
59
+ if (!validation.valid) {
60
+ progress?.warn(`Storage state rejected (${validation.reasonCode}): ${validation.reason}. Skipping crawl.`);
61
+ decisionLog.push({
62
+ timestamp: new Date().toISOString(),
63
+ phase: 'observe',
64
+ decision: 'storage-state-invalid',
65
+ reason: `${validation.reasonCode}: ${validation.reason}`,
66
+ metadata: {
67
+ reasonCode: validation.reasonCode,
68
+ targetOrigin: targetOriginForTelemetry,
69
+ },
70
+ });
71
+ const invalidGap = buildStorageStateInvalidGap({
72
+ url: options.url,
73
+ reasonCode: validation.reasonCode === 'ok' ? 'unknown' : validation.reasonCode,
74
+ reason: validation.reason,
75
+ });
76
+ const draft = {
77
+ analyzedAt: new Date().toISOString(),
78
+ mode: 'auth-required',
79
+ releaseConfidence: 0,
80
+ coveragePagesScanned: 0,
81
+ coverageBudgetExceeded: false,
82
+ coverageWarning: 'auth-required',
83
+ gaps: [invalidGap],
84
+ };
85
+ const costContext = {
86
+ mode: 'auth-required',
87
+ coveragePagesScanned: 0,
88
+ releaseConfidence: 0,
89
+ gaps: [invalidGap],
90
+ };
91
+ const gapAnalysis = await finalizeGapAnalysisFromDraft(draft, options.config, artifacts, costContext);
92
+ const emptyAuthRoutes = RouteInventorySchema.parse({
93
+ scannedAt: new Date().toISOString(),
94
+ baseUrl: options.url,
95
+ routes: [],
96
+ pagesSkipped: 0,
97
+ budgetExceeded: false,
98
+ });
99
+ await act(gapAnalysis, options.config, artifacts);
100
+ const blockedResult = {
101
+ status: 'blocked',
102
+ coverageScore: null,
103
+ releaseConfidence: 0,
104
+ gaps: gapAnalysis.gaps,
105
+ gapAnalysis,
106
+ routeInventory: emptyAuthRoutes,
107
+ repoInventory: null,
108
+ decisionLog,
109
+ publicSurface: null,
110
+ };
111
+ logScanEnd(progress, blockedResult);
112
+ emitTelemetry(options.telemetry, 'scan.blocked', sessionId, {
113
+ status: blockedResult.status,
114
+ coverageScore: blockedResult.coverageScore,
115
+ releaseConfidence: blockedResult.releaseConfidence,
116
+ gapCount: blockedResult.gaps.length,
117
+ reasonCode: validation.reasonCode,
118
+ });
119
+ return blockedResult;
120
+ }
121
+ }
43
122
  let detectedAuth;
44
123
  let authWall = false;
45
124
  if (!options.config.auth && !options.skipAuthDetection) {
@@ -1 +1 @@
1
- {"version":3,"file":"auth-login-run.d.ts","sourceRoot":"","sources":["../../src/cli/auth-login-run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAqB5D,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAEhE;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4GhB"}
1
+ {"version":3,"file":"auth-login-run.d.ts","sourceRoot":"","sources":["../../src/cli/auth-login-run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAsB5D,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAEhE;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoIhB"}
@@ -1,4 +1,5 @@
1
- import { BUILT_IN_OAUTH_PROVIDERS } from '../tools/oauth-providers.js';
1
+ import { BUILT_IN_OAUTH_PROVIDERS } from '../tools/auth/providers.js';
2
+ import { waitForReturnToOrigin } from '../tools/auth/detect.js';
2
3
  const builtInOAuthIds = new Set(BUILT_IN_OAUTH_PROVIDERS.map((p) => p.id));
3
4
  function escapeRegExp(s) {
4
5
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -109,8 +110,31 @@ export async function runAutomatedAuthLogin(params) {
109
110
  await sleep(250);
110
111
  }
111
112
  }
113
+ const originReturn = await waitForReturnToOrigin(page, params.baseUrlHint, params.timeoutMs);
114
+ if (!originReturn.returned) {
115
+ let targetOrigin = '<unknown>';
116
+ let finalOrigin = '<unknown>';
117
+ try {
118
+ targetOrigin = new URL(params.baseUrlHint).origin;
119
+ }
120
+ catch {
121
+ /* targetOrigin stays <unknown> */
122
+ }
123
+ try {
124
+ finalOrigin = new URL(originReturn.finalUrl).origin;
125
+ }
126
+ catch {
127
+ /* finalOrigin stays <unknown> */
128
+ }
129
+ throw new Error(`Login flow did not return to the app origin (expected ${targetOrigin}, final ${finalOrigin}). ` +
130
+ `Refusing to save the storage state — it would belong to the wrong domain and produce ` +
131
+ `false-confidence scans. Retry the login (the federated provider may need a redirect tweak) ` +
132
+ `or capture the session manually with \`qulib auth init --base-url ${params.baseUrlHint}\`.`);
133
+ }
112
134
  if (!confirmed) {
113
- console.error('[qulib] Could not confirm login success. Storage state saved; verify manually before relying on it.');
135
+ console.error('[qulib] Could not confirm login success heuristically, but the browser ended on the app origin. ' +
136
+ 'Storage state will be saved; verify the session before relying on it (run `qulib analyze` ' +
137
+ 'and check that releaseConfidence is not null).');
114
138
  }
115
139
  const fs = await import('node:fs/promises');
116
140
  const pathMod = await import('node:path');
package/dist/cli/index.js CHANGED
@@ -8,8 +8,8 @@ const requirePkg = createRequire(import.meta.url);
8
8
  const pkg = requirePkg('../../package.json');
9
9
  import { HarnessConfigSchema } from '../schemas/config.schema.js';
10
10
  import { analyzeApp } from '../analyze.js';
11
- import { detectAuth } from '../tools/auth-detector.js';
12
- import { exploreAuth } from '../tools/auth-explorer.js';
11
+ import { detectAuth } from '../tools/auth/detect.js';
12
+ import { exploreAuth } from '../tools/auth/explore.js';
13
13
  import { assertExactlyOneCredentialSource, parseCredentialsJsonString, resolveAuthLoginConfig, } from './auth-login-resolve.js';
14
14
  import { runAutomatedAuthLogin } from './auth-login-run.js';
15
15
  const program = new Command();
@@ -33,11 +33,14 @@ function redactConfigForLog(config) {
33
33
  base.auth = {
34
34
  ...config.auth,
35
35
  credentials: {
36
- username: config.auth.credentials.username,
36
+ username: '***',
37
37
  password: '***',
38
38
  },
39
39
  };
40
40
  }
41
+ if (config.auth?.type === 'storage-state') {
42
+ base.auth = { type: 'storage-state', path: '<provided>' };
43
+ }
41
44
  return base;
42
45
  }
43
46
  function mergeAuthFromCli(config, options) {
@@ -95,6 +98,7 @@ async function runAnalyze(options) {
95
98
  repoPath: options.repo,
96
99
  config,
97
100
  writeArtifacts,
101
+ skipAuthDetection: options.skipAuthDetection,
98
102
  });
99
103
  if (ephemeral) {
100
104
  console.log(JSON.stringify({
@@ -154,6 +158,7 @@ program
154
158
  .option('--config <file>', 'Path to config file (relative to cwd)', 'qulib.config.ts')
155
159
  .option('--adapter <type>', 'Override default test adapter (playwright, cypress-e2e, cypress-component, api)', 'playwright')
156
160
  .option('--ephemeral', 'Do not write to disk — return full report as JSON on stdout (use for MCP/CI)', false)
161
+ .option('--skip-auth-detection', 'Crawl the public surface even if auth is detected (useful for sites with sign-in CTAs on public pages)', false)
157
162
  .option('--auth-storage-state <path>', 'Path to a storage state JSON file (use after `qulib auth init`)')
158
163
  .option('--auth-form-login', 'Use form-login; requires --login-url, credentials, and selectors', false)
159
164
  .option('--login-url <url>', 'Form login page URL (required with --auth-form-login)')
@@ -176,6 +181,7 @@ program
176
181
  repo: options.repo,
177
182
  configFile: options.config,
178
183
  ephemeral: options.ephemeral,
184
+ skipAuthDetection: Boolean(options.skipAuthDetection),
179
185
  authStorageState: options.authStorageState,
180
186
  authFormLogin,
181
187
  loginUrl,
@@ -212,7 +218,7 @@ providersCmd
212
218
  .command('list')
213
219
  .description('List user-local providers registered on this machine')
214
220
  .action(async () => {
215
- const { listUserProviders } = await import('../tools/user-providers.js');
221
+ const { listUserProviders } = await import('../tools/auth/custom-providers.js');
216
222
  const providers = listUserProviders();
217
223
  console.log(JSON.stringify(providers, null, 2));
218
224
  });
@@ -229,7 +235,7 @@ providersCmd
229
235
  catch {
230
236
  throw new Error(`Invalid regex pattern: ${opts.pattern}`);
231
237
  }
232
- const { addUserProvider } = await import('../tools/user-providers.js');
238
+ const { addUserProvider } = await import('../tools/auth/custom-providers.js');
233
239
  addUserProvider({ id: opts.id, label: opts.label, pattern: opts.pattern });
234
240
  console.log(`[qulib] Added provider "${opts.label}" (id: ${opts.id}) to ~/.qulib/providers.json`);
235
241
  });
@@ -238,7 +244,7 @@ providersCmd
238
244
  .description('Remove a user-local provider by id')
239
245
  .requiredOption('--id <id>', 'Provider id to remove')
240
246
  .action(async (opts) => {
241
- const { removeUserProvider } = await import('../tools/user-providers.js');
247
+ const { removeUserProvider } = await import('../tools/auth/custom-providers.js');
242
248
  const removed = removeUserProvider(opts.id);
243
249
  console.log(removed ? `[qulib] Removed "${opts.id}"` : `[qulib] No provider with id "${opts.id}" found`);
244
250
  });
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  export { analyzeApp } from './analyze.js';
2
- export { detectAuth } from './tools/auth-detector.js';
3
- export { exploreAuth } from './tools/auth-explorer.js';
4
- export { addUserProvider, removeUserProvider, listUserProviders } from './tools/user-providers.js';
5
- export { scanRepo } from './tools/repo-scanner.js';
6
- export { computeAutomationMaturity } from './tools/automation-maturity.js';
2
+ export { detectAuth, validateStorageState, evaluateStorageStateValidity, preflightStorageStateFile, waitForReturnToOrigin, } from './tools/auth/detect.js';
3
+ export type { StorageStateInvalidReason, StorageStateValidationResult, } from './tools/auth/detect.js';
4
+ export { exploreAuth } from './tools/auth/explore.js';
5
+ export { addUserProvider, removeUserProvider, listUserProviders } from './tools/auth/custom-providers.js';
6
+ export { scanRepo } from './tools/repo/scan.js';
7
+ export { computeAutomationMaturity } from './tools/scoring/automation-maturity.js';
7
8
  export { createProvider } from './llm/provider-registry.js';
8
9
  export { resolveMaxOutputTokensPerLlmCall } from './schemas/config.schema.js';
9
10
  export { resolveScanStateBaseDir, resolveReportDir } from './harness/state-manager.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnG,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACvF,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjF,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EACV,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EACV,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,2BAA2B,EAC3B,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,4BAA4B,EAC5B,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1G,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACvF,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjF,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EACV,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EACV,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,2BAA2B,EAC3B,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC"}