webveil 0.0.0 → 0.1.1

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 (78) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +326 -0
  3. package/dist/cli.d.ts +58 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +91 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/core/backends/custom.d.ts +15 -0
  8. package/dist/core/backends/custom.d.ts.map +1 -0
  9. package/dist/core/backends/custom.js +106 -0
  10. package/dist/core/backends/custom.js.map +1 -0
  11. package/dist/core/backends/registry.d.ts +13 -0
  12. package/dist/core/backends/registry.d.ts.map +1 -0
  13. package/dist/core/backends/registry.js +31 -0
  14. package/dist/core/backends/registry.js.map +1 -0
  15. package/dist/core/backends/searxng.d.ts +8 -0
  16. package/dist/core/backends/searxng.d.ts.map +1 -0
  17. package/dist/core/backends/searxng.js +43 -0
  18. package/dist/core/backends/searxng.js.map +1 -0
  19. package/dist/core/backends/tavily-compat.d.ts +10 -0
  20. package/dist/core/backends/tavily-compat.d.ts.map +1 -0
  21. package/dist/core/backends/tavily-compat.js +85 -0
  22. package/dist/core/backends/tavily-compat.js.map +1 -0
  23. package/dist/core/backends/types.d.ts +48 -0
  24. package/dist/core/backends/types.d.ts.map +1 -0
  25. package/dist/core/backends/types.js +5 -0
  26. package/dist/core/backends/types.js.map +1 -0
  27. package/dist/core/baseurl.d.ts +42 -0
  28. package/dist/core/baseurl.d.ts.map +1 -0
  29. package/dist/core/baseurl.js +79 -0
  30. package/dist/core/baseurl.js.map +1 -0
  31. package/dist/core/config.d.ts +39 -0
  32. package/dist/core/config.d.ts.map +1 -0
  33. package/dist/core/config.js +72 -0
  34. package/dist/core/config.js.map +1 -0
  35. package/dist/core/egress.d.ts +46 -0
  36. package/dist/core/egress.d.ts.map +1 -0
  37. package/dist/core/egress.js +113 -0
  38. package/dist/core/egress.js.map +1 -0
  39. package/dist/core/extract.d.ts +45 -0
  40. package/dist/core/extract.d.ts.map +1 -0
  41. package/dist/core/extract.js +36 -0
  42. package/dist/core/extract.js.map +1 -0
  43. package/dist/core/fetch.d.ts +42 -0
  44. package/dist/core/fetch.d.ts.map +1 -0
  45. package/dist/core/fetch.js +76 -0
  46. package/dist/core/fetch.js.map +1 -0
  47. package/dist/core/http.d.ts +8 -0
  48. package/dist/core/http.d.ts.map +1 -0
  49. package/dist/core/http.js +49 -0
  50. package/dist/core/http.js.map +1 -0
  51. package/dist/core/search.d.ts +34 -0
  52. package/dist/core/search.d.ts.map +1 -0
  53. package/dist/core/search.js +92 -0
  54. package/dist/core/search.js.map +1 -0
  55. package/dist/core/security.d.ts +35 -0
  56. package/dist/core/security.d.ts.map +1 -0
  57. package/dist/core/security.js +141 -0
  58. package/dist/core/security.js.map +1 -0
  59. package/dist/index.d.ts +22 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +40 -0
  62. package/dist/index.js.map +1 -0
  63. package/package.json +62 -2
  64. package/src/cli.ts +106 -0
  65. package/src/core/backends/custom.ts +159 -0
  66. package/src/core/backends/registry.ts +41 -0
  67. package/src/core/backends/searxng.ts +70 -0
  68. package/src/core/backends/tavily-compat.ts +156 -0
  69. package/src/core/backends/types.ts +61 -0
  70. package/src/core/baseurl.ts +104 -0
  71. package/src/core/config.ts +106 -0
  72. package/src/core/egress.ts +134 -0
  73. package/src/core/extract.ts +82 -0
  74. package/src/core/fetch.ts +132 -0
  75. package/src/core/http.ts +62 -0
  76. package/src/core/search.ts +140 -0
  77. package/src/core/security.ts +141 -0
  78. package/src/index.ts +82 -0
@@ -0,0 +1,31 @@
1
+ // backend registry — a tiny `name -> Backend` dispatcher (concept trimmed from
2
+ // pi-search-hub's registry). Each backend registers a factory keyed by its config
3
+ // `backend` name; `getBackend` resolves the name to a constructed Backend (handed
4
+ // the resolved config so it knows its instance baseUrl / apiKey) and fails clearly
5
+ // on an unknown name. Later backend tasks (tavily-compat, custom) append their own
6
+ // registrations to FACTORIES below.
7
+ import { createSearxngBackend } from './searxng.js';
8
+ import { createTavilyCompatBackend } from './tavily-compat.js';
9
+ import { createCustomBackend } from './custom.js';
10
+ /** name -> factory. New backends add an entry here. */
11
+ const FACTORIES = {
12
+ searxng: createSearxngBackend,
13
+ 'tavily-compat': createTavilyCompatBackend,
14
+ custom: createCustomBackend,
15
+ };
16
+ /** The backend names the registry can resolve. */
17
+ export function backendNames() {
18
+ return Object.keys(FACTORIES);
19
+ }
20
+ /**
21
+ * Resolve a backend name to a constructed Backend. Throws clearly on an unknown
22
+ * name (listing the known ones) so a misconfigured `backend` fails loud, never
23
+ * silently no-ops.
24
+ */
25
+ export function getBackend(name, config) {
26
+ const factory = FACTORIES[name];
27
+ if (!factory)
28
+ throw new Error(`webveil: unknown backend '${name}' (known: ${backendNames().join(', ')})`);
29
+ return factory(config);
30
+ }
31
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/core/backends/registry.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,kFAAkF;AAClF,kFAAkF;AAClF,mFAAmF;AACnF,mFAAmF;AACnF,oCAAoC;AAIpC,OAAO,EAAC,oBAAoB,EAAC,MAAM,cAAc,CAAC;AAClD,OAAO,EAAC,yBAAyB,EAAC,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAC,mBAAmB,EAAC,MAAM,aAAa,CAAC;AAKhD,uDAAuD;AACvD,MAAM,SAAS,GAAmC;IACjD,OAAO,EAAE,oBAAoB;IAC7B,eAAe,EAAE,yBAAyB;IAC1C,MAAM,EAAE,mBAAmB;CAC3B,CAAC;AAEF,kDAAkD;AAClD,MAAM,UAAU,YAAY;IAC3B,OAAO,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,MAAc;IACtD,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,OAAO;QACX,MAAM,IAAI,KAAK,CACd,6BAA6B,IAAI,aAAa,YAAY,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC1E,CAAC;IACH,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Config } from '../config.js';
2
+ import type { Backend } from './types.js';
3
+ /**
4
+ * Build a SearXNG backend bound to the configured instance. The returned backend
5
+ * only ever touches the network via the injected `http` helper.
6
+ */
7
+ export declare function createSearxngBackend(config: Config): Backend;
8
+ //# sourceMappingURL=searxng.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searxng.d.ts","sourceRoot":"","sources":["../../../src/core/backends/searxng.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAC,OAAO,EAAoC,MAAM,YAAY,CAAC;AAsC3E;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAqB5D"}
@@ -0,0 +1,43 @@
1
+ // searxng backend — the keyless, self-hosted metasearch default. Queries a
2
+ // SearXNG instance's JSON API (`/search?format=json`) THROUGH the handed `http`
3
+ // helper (never a direct fetch, so egress is not bypassable) and normalizes the
4
+ // response into SearchResult[].
5
+ function str(value) {
6
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
7
+ }
8
+ /** Normalize one SearXNG hit; drop entries without a usable url + title. */
9
+ function toResult(hit) {
10
+ const url = str(hit.url);
11
+ const title = str(hit.title);
12
+ if (!url || !title)
13
+ return undefined;
14
+ const snippet = str(hit.content);
15
+ return snippet ? { title, url, snippet } : { title, url };
16
+ }
17
+ /** Build the SearXNG JSON search URL for a query against the instance baseUrl. */
18
+ function buildUrl(baseUrl, query) {
19
+ const url = new URL('search', baseUrl.endsWith('/') ? baseUrl : baseUrl + '/');
20
+ url.searchParams.set('q', query);
21
+ url.searchParams.set('format', 'json');
22
+ return url.toString();
23
+ }
24
+ /**
25
+ * Build a SearXNG backend bound to the configured instance. The returned backend
26
+ * only ever touches the network via the injected `http` helper.
27
+ */
28
+ export function createSearxngBackend(config) {
29
+ const baseUrl = config.baseUrl;
30
+ return {
31
+ async search(query, http, options = {}) {
32
+ const body = await http.fetchJson(buildUrl(baseUrl, query), { headers: { accept: 'application/json' }, signal: options.signal });
33
+ const results = Array.isArray(body.results) ? body.results : [];
34
+ const normalized = results
35
+ .map(toResult)
36
+ .filter((r) => r !== undefined);
37
+ return options.maxResults !== undefined
38
+ ? normalized.slice(0, options.maxResults)
39
+ : normalized;
40
+ },
41
+ };
42
+ }
43
+ //# sourceMappingURL=searxng.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searxng.js","sourceRoot":"","sources":["../../../src/core/backends/searxng.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,gFAAgF;AAChF,gFAAgF;AAChF,gCAAgC;AAiBhC,SAAS,GAAG,CAAC,KAAc;IAC1B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1E,CAAC;AAED,4EAA4E;AAC5E,SAAS,QAAQ,CAAC,GAAkB;IACnC,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACzB,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IACrC,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACjC,OAAO,OAAO,CAAC,CAAC,CAAC,EAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAC,CAAC,CAAC,CAAC,EAAC,KAAK,EAAE,GAAG,EAAC,CAAC;AACvD,CAAC;AAED,kFAAkF;AAClF,SAAS,QAAQ,CAAC,OAAe,EAAE,KAAa;IAC/C,MAAM,GAAG,GAAG,IAAI,GAAG,CAClB,QAAQ,EACR,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,GAAG,CAC/C,CAAC;IACF,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACjC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACvC,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACvB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAc;IAClD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAC/B,OAAO;QACN,KAAK,CAAC,MAAM,CACX,KAAa,EACb,IAAU,EACV,UAAyB,EAAE;YAE3B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAChC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,EACxB,EAAC,OAAO,EAAE,EAAC,MAAM,EAAE,kBAAkB,EAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAC,CAC/D,CAAC;YACF,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YAChE,MAAM,UAAU,GAAG,OAAO;iBACxB,GAAG,CAAC,QAAQ,CAAC;iBACb,MAAM,CAAC,CAAC,CAAC,EAAqB,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;YACpD,OAAO,OAAO,CAAC,UAAU,KAAK,SAAS;gBACtC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC;gBACzC,CAAC,CAAC,UAAU,CAAC;QACf,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { Config } from '../config.js';
2
+ import type { Backend } from './types.js';
3
+ /**
4
+ * Build a Tavily-compat backend bound to the configured instance. The returned
5
+ * backend only ever touches the network via the injected `http` helper. A Bearer
6
+ * header is added only when an apiKey is set (the covered instances are usually
7
+ * keyless).
8
+ */
9
+ export declare function createTavilyCompatBackend(config: Config): Backend;
10
+ //# sourceMappingURL=tavily-compat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tavily-compat.d.ts","sourceRoot":"","sources":["../../../src/core/backends/tavily-compat.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EACX,OAAO,EAOP,MAAM,YAAY,CAAC;AAqDpB;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA2EjE"}
@@ -0,0 +1,85 @@
1
+ // tavily-compat backend — a generic Tavily-shaped client (POST `/search` and an
2
+ // optional POST `/extract`) selected purely by `baseUrl`, so it covers
3
+ // orio-search / searcharvester / agent-search and any other Tavily-API-shaped
4
+ // instance. Both endpoints go THROUGH the handed `http` helper (never a direct
5
+ // fetch, so egress is not bypassable). `/search` normalizes to SearchResult[];
6
+ // `/extract` is exposed as the optional `Backend.fetch` a later task uses to
7
+ // override the distilly Extractor.
8
+ //
9
+ // Auth: a Bearer header is sent only when an apiKey is configured. The covered
10
+ // self-hosted instances are typically keyless, so a missing key is normal, not
11
+ // an error.
12
+ function str(value) {
13
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
14
+ }
15
+ /** Normalize one Tavily search hit; drop entries without a usable url + title. */
16
+ function toResult(hit) {
17
+ const url = str(hit.url);
18
+ const title = str(hit.title);
19
+ if (!url || !title)
20
+ return undefined;
21
+ const snippet = str(hit.content);
22
+ return snippet ? { title, url, snippet } : { title, url };
23
+ }
24
+ /** Resolve an endpoint path against the instance baseUrl. */
25
+ function endpoint(baseUrl, path) {
26
+ return new URL(path, baseUrl.endsWith('/') ? baseUrl : baseUrl + '/').toString();
27
+ }
28
+ /**
29
+ * Build a Tavily-compat backend bound to the configured instance. The returned
30
+ * backend only ever touches the network via the injected `http` helper. A Bearer
31
+ * header is added only when an apiKey is set (the covered instances are usually
32
+ * keyless).
33
+ */
34
+ export function createTavilyCompatBackend(config) {
35
+ const baseUrl = config.baseUrl;
36
+ const apiKey = config.apiKey;
37
+ function headers() {
38
+ const h = {
39
+ 'content-type': 'application/json',
40
+ accept: 'application/json',
41
+ };
42
+ if (apiKey)
43
+ h.authorization = `Bearer ${apiKey}`;
44
+ return h;
45
+ }
46
+ function post(path, payload, signal) {
47
+ return {
48
+ method: 'POST',
49
+ headers: headers(),
50
+ body: JSON.stringify(payload),
51
+ signal,
52
+ };
53
+ }
54
+ return {
55
+ async search(query, http, options = {}) {
56
+ const payload = { query };
57
+ if (options.maxResults !== undefined)
58
+ payload.max_results = options.maxResults;
59
+ const body = await http.fetchJson(endpoint(baseUrl, 'search'), post('search', payload, options.signal));
60
+ const results = Array.isArray(body.results) ? body.results : [];
61
+ const normalized = results
62
+ .map(toResult)
63
+ .filter((r) => r !== undefined);
64
+ return options.maxResults !== undefined
65
+ ? normalized.slice(0, options.maxResults)
66
+ : normalized;
67
+ },
68
+ async fetch(url, http, options = {}) {
69
+ // Tavily `/extract` has no `s/m/l/f` size knob (it has `format` /
70
+ // `extract_depth` instead); always request markdown. The default
71
+ // distilly Extractor owns webveil's size presets.
72
+ const body = await http.fetchJson(endpoint(baseUrl, 'extract'), post('extract', { urls: url, format: 'markdown' }, options.signal));
73
+ const failure = (body.failed_results ?? []).find((f) => str(f.url) === url);
74
+ if (failure)
75
+ throw new Error(`tavily-compat: /extract failed for ${url}: ${str(failure.error) ?? 'unknown error'}`);
76
+ const hit = (body.results ?? [])[0];
77
+ const markdown = hit ? str(hit.raw_content) : undefined;
78
+ if (markdown === undefined)
79
+ throw new Error(`tavily-compat: no extract result for ${url}`);
80
+ // Tavily `/extract` returns no `truncated` flag and no page title.
81
+ return { url, markdown, truncated: false };
82
+ },
83
+ };
84
+ }
85
+ //# sourceMappingURL=tavily-compat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tavily-compat.js","sourceRoot":"","sources":["../../../src/core/backends/tavily-compat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /** A single search hit. */
2
+ export interface SearchResult {
3
+ title: string;
4
+ url: string;
5
+ snippet?: string;
6
+ }
7
+ /** A fetched, extracted page as budget-bounded markdown. */
8
+ export interface FetchResult {
9
+ url: string;
10
+ title?: string;
11
+ markdown: string;
12
+ truncated: boolean;
13
+ }
14
+ export interface SearchOptions {
15
+ maxResults?: number;
16
+ signal?: AbortSignal;
17
+ }
18
+ export interface FetchOptions {
19
+ size?: 's' | 'm' | 'l' | 'f';
20
+ signal?: AbortSignal;
21
+ }
22
+ /** Options the http helper accepts for a single request. */
23
+ export interface HttpRequestOptions {
24
+ method?: string;
25
+ headers?: Record<string, string>;
26
+ body?: string;
27
+ /** Per-request timeout in ms (the helper aborts past this). */
28
+ timeoutMs?: number;
29
+ signal?: AbortSignal;
30
+ }
31
+ /**
32
+ * The proxied http helper handed to backends. Both methods route through the
33
+ * egress dispatcher; a backend never gets un-proxied transport of its own.
34
+ */
35
+ export interface Http {
36
+ fetchJson<T = unknown>(url: string, options?: HttpRequestOptions): Promise<T>;
37
+ fetchText(url: string, options?: HttpRequestOptions): Promise<string>;
38
+ }
39
+ /**
40
+ * A result/content source. `search` is required; `fetch` is optional (a backend
41
+ * may override the default distilly Extractor with its own `/extract`). Both are
42
+ * given the proxied `http` helper so they cannot escape the configured egress.
43
+ */
44
+ export interface Backend {
45
+ search(query: string, http: Http, options?: SearchOptions): Promise<SearchResult[]>;
46
+ fetch?(url: string, http: Http, options?: FetchOptions): Promise<FetchResult>;
47
+ }
48
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/backends/types.ts"],"names":[],"mappings":"AAIA,2BAA2B;AAC3B,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,4DAA4D;AAC5D,MAAM,WAAW,WAAW;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,4DAA4D;AAC5D,MAAM,WAAW,kBAAkB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,IAAI;IACpB,SAAS,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC9E,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACtE;AAED;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACvB,MAAM,CACL,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAC3B,KAAK,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CAC9E"}
@@ -0,0 +1,5 @@
1
+ // backend seam — the contract every result source (searxng | tavily-compat |
2
+ // custom) implements. A Backend is HANDED a proxied `http` helper (bound to the
3
+ // configured egress dispatcher) so it physically cannot bypass the egress.
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/backends/types.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,gFAAgF;AAChF,2EAA2E"}
@@ -0,0 +1,42 @@
1
+ import { type Dispatcher } from 'undici';
2
+ /** A parsed Unix-socket baseUrl: the socket file path + the app's base path. */
3
+ export interface UnixBaseUrl {
4
+ socketPath: string;
5
+ /** The app's base path (mount point); the backend appends `search` to it. */
6
+ httpPath: string;
7
+ }
8
+ /** Is this resolved `baseUrl` a Unix-domain-socket form (`unix:…`)? */
9
+ export declare function isUnixBaseUrl(baseUrl: string): boolean;
10
+ /**
11
+ * Parse a `unix:<socketPath>[:<httpPath>]` baseUrl into `{socketPath, httpPath}`.
12
+ * Splits on the FIRST `:` after the `unix:` scheme (socket paths conventionally
13
+ * carry no colon); an absent/empty `<httpPath>` defaults to `/`.
14
+ *
15
+ * Throws if the socket path is empty (there is nothing to connect to).
16
+ */
17
+ export declare function parseUnixBaseUrl(baseUrl: string): UnixBaseUrl;
18
+ /**
19
+ * The result of resolving a `baseUrl` for the BACKEND hop: the (possibly
20
+ * rewritten) HTTP base the backend builds its request URL on, plus an OPTIONAL
21
+ * undici `Dispatcher` to carry that hop. For a normal TCP baseUrl the dispatcher
22
+ * is `undefined` (the caller uses the config-wide egress dispatcher); for a
23
+ * `unix:` baseUrl it is a socket-bound `Agent` and the base is a synthetic
24
+ * `http://localhost<httpPath>`.
25
+ */
26
+ export interface BackendTransport {
27
+ baseUrl: string;
28
+ dispatcher?: Dispatcher;
29
+ }
30
+ /**
31
+ * Resolve a `baseUrl` into a backend-hop transport. For a `unix:` baseUrl this
32
+ * builds a socket-bound `Agent({connect:{socketPath}})` and a synthetic
33
+ * `http://localhost<httpPath>` base (the URL host is irrelevant to routing — the
34
+ * socket decides — and only becomes the `Host` header). For any other baseUrl it
35
+ * returns the baseUrl unchanged with NO dispatcher (the caller keeps using the
36
+ * shared config-wide egress dispatcher).
37
+ *
38
+ * NOTE: this is the BACKEND-hop transport only. It is never bound into the
39
+ * shared egress dispatcher, so `web_fetch`/SSRF egress is unaffected.
40
+ */
41
+ export declare function resolveBackendTransport(baseUrl: string): BackendTransport;
42
+ //# sourceMappingURL=baseurl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseurl.d.ts","sourceRoot":"","sources":["../../src/core/baseurl.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAQ,KAAK,UAAU,EAAC,MAAM,QAAQ,CAAC;AAK9C,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAgB7D;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,UAAU,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAQzE"}
@@ -0,0 +1,79 @@
1
+ // baseUrl transport parsing — the small, transport-AWARE helper that classifies
2
+ // a resolved `baseUrl` into either a normal TCP HTTP base or a Unix-domain-socket
3
+ // form, and (for the socket form) rewrites it into a synthetic `http://localhost`
4
+ // base that the transport-UNAWARE backends can build their request URL on top of.
5
+ //
6
+ // WHY THIS LIVES HERE (and not in egress.ts): the egress dispatcher
7
+ // (`buildDispatcher`) is built ONCE from config and SHARED by both the backend
8
+ // hop (search.ts) AND the arbitrary-public-URL fetch (fetch.ts). Binding a socket
9
+ // `Agent` into that shared dispatcher would route every `web_fetch` into the
10
+ // SearXNG socket. So the socket transport is scoped to the BACKEND `baseUrl` hop
11
+ // only: search.ts asks this helper to translate the baseUrl, gets back a real
12
+ // `http://localhost…` base (which the searxng backend's `new URL('search', base)`
13
+ // still works on, unchanged) plus the per-hop socket `Agent`, and leaves the
14
+ // fetch/SSRF egress path untouched.
15
+ //
16
+ // GRAMMAR (recorded decision, see the task's done record):
17
+ // unix:<socketPath>[:<httpPath>]
18
+ // - `<socketPath>`: the absolute path to the uWSGI Unix domain socket
19
+ // (e.g. /usr/local/searxng/run/socket). Must not contain a `:` (the parse
20
+ // splits on the FIRST `:` after the `unix:` scheme; conventional socket
21
+ // paths never contain a colon).
22
+ // - `<httpPath>`: OPTIONAL base path the SearXNG app is mounted under,
23
+ // defaulting to `/`. It is the SAME thing the TCP `baseUrl` encodes as its
24
+ // path: the backend appends `search` to it (`new URL('search', base + '/')`),
25
+ // so the install default is just `unix:/usr/local/searxng/run/socket` (the
26
+ // backend then requests `/search`). A non-root mount is `…/socket:/searxng`.
27
+ // A raw `unix:…` string is NOT a valid base for `new URL('search', …)`, so this
28
+ // translation MUST run BEFORE the backend builds its URL.
29
+ import { Agent } from 'undici';
30
+ /** The `unix:` scheme prefix this helper recognizes on a `baseUrl`. */
31
+ const UNIX_PREFIX = 'unix:';
32
+ /** Is this resolved `baseUrl` a Unix-domain-socket form (`unix:…`)? */
33
+ export function isUnixBaseUrl(baseUrl) {
34
+ return baseUrl.startsWith(UNIX_PREFIX);
35
+ }
36
+ /**
37
+ * Parse a `unix:<socketPath>[:<httpPath>]` baseUrl into `{socketPath, httpPath}`.
38
+ * Splits on the FIRST `:` after the `unix:` scheme (socket paths conventionally
39
+ * carry no colon); an absent/empty `<httpPath>` defaults to `/`.
40
+ *
41
+ * Throws if the socket path is empty (there is nothing to connect to).
42
+ */
43
+ export function parseUnixBaseUrl(baseUrl) {
44
+ const rest = baseUrl.slice(UNIX_PREFIX.length);
45
+ const sep = rest.indexOf(':');
46
+ const socketPath = sep === -1 ? rest : rest.slice(0, sep);
47
+ const rawHttpPath = sep === -1 ? '' : rest.slice(sep + 1);
48
+ if (!socketPath)
49
+ throw new Error(`webveil: malformed unix baseUrl ${JSON.stringify(baseUrl)} — ` +
50
+ `expected unix:<socketPath>[:<httpPath>] with a non-empty socket path`);
51
+ const httpPath = rawHttpPath
52
+ ? rawHttpPath.startsWith('/')
53
+ ? rawHttpPath
54
+ : '/' + rawHttpPath
55
+ : '/';
56
+ return { socketPath, httpPath };
57
+ }
58
+ /**
59
+ * Resolve a `baseUrl` into a backend-hop transport. For a `unix:` baseUrl this
60
+ * builds a socket-bound `Agent({connect:{socketPath}})` and a synthetic
61
+ * `http://localhost<httpPath>` base (the URL host is irrelevant to routing — the
62
+ * socket decides — and only becomes the `Host` header). For any other baseUrl it
63
+ * returns the baseUrl unchanged with NO dispatcher (the caller keeps using the
64
+ * shared config-wide egress dispatcher).
65
+ *
66
+ * NOTE: this is the BACKEND-hop transport only. It is never bound into the
67
+ * shared egress dispatcher, so `web_fetch`/SSRF egress is unaffected.
68
+ */
69
+ export function resolveBackendTransport(baseUrl) {
70
+ if (!isUnixBaseUrl(baseUrl))
71
+ return { baseUrl };
72
+ const { socketPath, httpPath } = parseUnixBaseUrl(baseUrl);
73
+ const dispatcher = new Agent({ connect: { socketPath } });
74
+ return {
75
+ baseUrl: `http://localhost${httpPath === '/' ? '' : httpPath}`,
76
+ dispatcher,
77
+ };
78
+ }
79
+ //# sourceMappingURL=baseurl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseurl.js","sourceRoot":"","sources":["../../src/core/baseurl.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,kFAAkF;AAClF,kFAAkF;AAClF,kFAAkF;AAClF,EAAE;AACF,oEAAoE;AACpE,+EAA+E;AAC/E,kFAAkF;AAClF,6EAA6E;AAC7E,iFAAiF;AACjF,8EAA8E;AAC9E,kFAAkF;AAClF,6EAA6E;AAC7E,oCAAoC;AACpC,EAAE;AACF,2DAA2D;AAC3D,mCAAmC;AACnC,0EAA0E;AAC1E,gFAAgF;AAChF,8EAA8E;AAC9E,sCAAsC;AACtC,2EAA2E;AAC3E,iFAAiF;AACjF,oFAAoF;AACpF,iFAAiF;AACjF,mFAAmF;AACnF,kFAAkF;AAClF,4DAA4D;AAE5D,OAAO,EAAC,KAAK,EAAkB,MAAM,QAAQ,CAAC;AAE9C,uEAAuE;AACvE,MAAM,WAAW,GAAG,OAAO,CAAC;AAS5B,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,OAAe;IAC5C,OAAO,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9B,MAAM,UAAU,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,WAAW,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAC1D,IAAI,CAAC,UAAU;QACd,MAAM,IAAI,KAAK,CACd,mCAAmC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK;YAC9D,sEAAsE,CACvE,CAAC;IACH,MAAM,QAAQ,GAAG,WAAW;QAC3B,CAAC,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC;YAC5B,CAAC,CAAC,WAAW;YACb,CAAC,CAAC,GAAG,GAAG,WAAW;QACpB,CAAC,CAAC,GAAG,CAAC;IACP,OAAO,EAAC,UAAU,EAAE,QAAQ,EAAC,CAAC;AAC/B,CAAC;AAeD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACtD,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;QAAE,OAAO,EAAC,OAAO,EAAC,CAAC;IAC9C,MAAM,EAAC,UAAU,EAAE,QAAQ,EAAC,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACzD,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,EAAC,OAAO,EAAE,EAAC,UAAU,EAAC,EAAC,CAAC,CAAC;IACtD,OAAO;QACN,OAAO,EAAE,mBAAmB,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;QAC9D,UAAU;KACV,CAAC;AACH,CAAC"}
@@ -0,0 +1,39 @@
1
+ /** How outbound HTTP leaves the machine. See egress.ts. */
2
+ export type Egress = {
3
+ mode: 'direct';
4
+ } | {
5
+ mode: 'http';
6
+ url: string;
7
+ } | {
8
+ mode: 'socks5';
9
+ url: string;
10
+ };
11
+ /** Page-size budget preset for fetch (passed through to distilly). */
12
+ export type FetchSize = 's' | 'm' | 'l' | 'f';
13
+ /** The fully-resolved config every webveil module consumes. */
14
+ export interface Config {
15
+ backend: string;
16
+ baseUrl: string;
17
+ apiKey?: string;
18
+ egress: Egress;
19
+ fetchSize: FetchSize;
20
+ }
21
+ /** A config file / env layer: any subset of the resolved shape. */
22
+ export type PartialConfig = Partial<Config>;
23
+ export interface ResolveOptions {
24
+ /** Directory the per-folder walk starts from. Defaults to process.cwd(). */
25
+ cwd?: string;
26
+ /** Environment to read overrides from. Defaults to process.env. */
27
+ env?: Record<string, string | undefined>;
28
+ /**
29
+ * Path to the global config file. Defaults to ~/.pi/agent/webveil.json.
30
+ * Tests point this at a temp dir to isolate the real home directory.
31
+ */
32
+ globalPath?: string;
33
+ }
34
+ /**
35
+ * Resolve the effective config. Higher-precedence layers override lower ones,
36
+ * key by key: env > project chain > global file > defaults.
37
+ */
38
+ export declare function resolveConfig(options?: ResolveOptions): Config;
39
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAUA,2DAA2D;AAC3D,MAAM,MAAM,MAAM,GACf;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GAChB;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,GAC3B;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,CAAC;AAEjC,sEAAsE;AACtE,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAE9C,+DAA+D;AAC/D,MAAM,WAAW,MAAM;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;CACrB;AAED,mEAAmE;AACnE,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC9B,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AA+CD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,CAalE"}
@@ -0,0 +1,72 @@
1
+ // config seam — per-folder resolution. Precedence (highest wins):
2
+ // env > nearest .pi/webveil.json (walking up from cwd) > global
3
+ // ~/.pi/agent/webveil.json > defaults.
4
+ // "Per folder = per account/egress." Each layer is a partial; later (lower)
5
+ // layers fill gaps the higher layers leave.
6
+ import { readFileSync } from 'node:fs';
7
+ import { homedir } from 'node:os';
8
+ import { dirname, join, parse } from 'node:path';
9
+ const DEFAULTS = {
10
+ backend: 'searxng',
11
+ baseUrl: 'http://127.0.0.1:8080',
12
+ egress: { mode: 'direct' },
13
+ fetchSize: 'm',
14
+ };
15
+ const PROJECT_FILE = join('.pi', 'webveil.json');
16
+ function readJson(path) {
17
+ let text;
18
+ try {
19
+ text = readFileSync(path, 'utf8');
20
+ }
21
+ catch {
22
+ return undefined; // absent file is fine; missing layers are expected
23
+ }
24
+ return JSON.parse(text);
25
+ }
26
+ /** The nearest `.pi/webveil.json` walking up from `cwd` (first found wins). */
27
+ function readProjectChain(cwd) {
28
+ let dir = cwd;
29
+ const { root } = parse(dir);
30
+ for (;;) {
31
+ const found = readJson(join(dir, PROJECT_FILE));
32
+ if (found)
33
+ return found;
34
+ if (dir === root)
35
+ return undefined;
36
+ dir = dirname(dir);
37
+ }
38
+ }
39
+ function readEnv(env) {
40
+ const layer = {};
41
+ if (env.WEBVEIL_BACKEND)
42
+ layer.backend = env.WEBVEIL_BACKEND;
43
+ if (env.WEBVEIL_BASE_URL)
44
+ layer.baseUrl = env.WEBVEIL_BASE_URL;
45
+ if (env.WEBVEIL_API_KEY)
46
+ layer.apiKey = env.WEBVEIL_API_KEY;
47
+ if (env.WEBVEIL_FETCH_SIZE)
48
+ layer.fetchSize = env.WEBVEIL_FETCH_SIZE;
49
+ const mode = env.WEBVEIL_EGRESS;
50
+ if (mode === 'direct')
51
+ layer.egress = { mode: 'direct' };
52
+ else if (mode === 'http' || mode === 'socks5')
53
+ layer.egress = { mode, url: env.WEBVEIL_EGRESS_URL ?? '' };
54
+ return layer;
55
+ }
56
+ /**
57
+ * Resolve the effective config. Higher-precedence layers override lower ones,
58
+ * key by key: env > project chain > global file > defaults.
59
+ */
60
+ export function resolveConfig(options = {}) {
61
+ const cwd = options.cwd ?? process.cwd();
62
+ const env = options.env ?? process.env;
63
+ const globalPath = options.globalPath ?? join(homedir(), '.pi', 'agent', 'webveil.json');
64
+ const layers = [
65
+ DEFAULTS,
66
+ readJson(globalPath) ?? {},
67
+ readProjectChain(cwd) ?? {},
68
+ readEnv(env),
69
+ ];
70
+ return Object.assign({}, ...layers);
71
+ }
72
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { Agent, type Dispatcher, ProxyAgent } from 'undici';
2
+ import type { Config } from './config.js';
3
+ /** Thrown when a configured egress proxy cannot be built. Never swallowed. */
4
+ export declare class EgressError extends Error {
5
+ constructor(message: string, options?: {
6
+ cause?: unknown;
7
+ });
8
+ }
9
+ /**
10
+ * Fail-loud guard for the false-confidence combo: a `unix:` (local-socket)
11
+ * backend `baseUrl` configured with a NON-direct egress (`http`/`socks5`). A
12
+ * Unix socket is inherently local, so proxying that hop is the same fake-
13
+ * anonymity footgun as proxying a loopback TCP baseUrl: webveil would route a
14
+ * pointless local call through the proxy while the backend (SearXNG) crawls the
15
+ * public web from the real IP, OUTSIDE webveil's egress. Refuse it and point at
16
+ * the real fix.
17
+ *
18
+ * OVERLAP SEAM (recorded): this is the loopback false-confidence family. The
19
+ * sibling task `fail-loud-on-proxied-loopback-backend` adds the broader guard
20
+ * for loopback TCP baseUrls (127.0.0.0/8, ::1, localhost). When it lands, fold
21
+ * THIS `unix:`-is-loopback-equivalent case into that single guard instead of
22
+ * keeping a parallel check here.
23
+ */
24
+ export declare function assertEgressAllowsBaseUrl(cfg: Config): void;
25
+ /**
26
+ * Build the undici Dispatcher for the config's egress mode:
27
+ * - direct → undefined (undici uses its default, un-proxied transport)
28
+ * - http → ProxyAgent
29
+ * - socks5 → socks dispatcher (undici Agent over a socks connector)
30
+ *
31
+ * Throws (fail loud) if a configured http/socks5 proxy cannot be built. It
32
+ * NEVER returns `undefined` (direct) as a fallback for a broken proxy.
33
+ */
34
+ export declare function buildDispatcher(cfg: Config): Dispatcher | undefined;
35
+ /** A WHATWG-compatible fetch bound to a specific egress dispatcher. */
36
+ export type EgressFetch = typeof globalThis.fetch;
37
+ /**
38
+ * Build an egress-bound WHATWG `fetch`: undici's `fetch` closed over the
39
+ * dispatcher from buildDispatcher(cfg). This is the `fetch` injected into
40
+ * distilly/fetch so distilly never has egress of its own. Same fail-loud
41
+ * guarantee: a broken proxy throws HERE (before any I/O), never goes un-proxied.
42
+ */
43
+ export declare function createEgressFetch(cfg: Config): EgressFetch;
44
+ export type { Dispatcher };
45
+ export { Agent, ProxyAgent };
46
+ //# sourceMappingURL=egress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"egress.d.ts","sourceRoot":"","sources":["../../src/core/egress.ts"],"names":[],"mappings":"AAQA,OAAO,EAAC,KAAK,EAAE,KAAK,UAAU,EAAE,UAAU,EAAuB,MAAM,QAAQ,CAAC;AAEhF,OAAO,KAAK,EAAC,MAAM,EAAS,MAAM,aAAa,CAAC;AAGhD,8EAA8E;AAC9E,qBAAa,WAAY,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAIxD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAU3D;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAiCnE;AAED,uEAAuE;AACvE,MAAM,MAAM,WAAW,GAAG,OAAO,UAAU,CAAC,KAAK,CAAC;AAElD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAU1D;AAED,YAAY,EAAC,UAAU,EAAC,CAAC;AACzB,OAAO,EAAC,KAAK,EAAE,UAAU,EAAC,CAAC"}