@ozzylabs/feedradar 0.1.4 → 0.1.5

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 (117) hide show
  1. package/README.ja.md +12 -6
  2. package/README.md +10 -5
  3. package/dist/agents/claude-code.d.ts +12 -1
  4. package/dist/agents/claude-code.d.ts.map +1 -1
  5. package/dist/agents/claude-code.js +9 -5
  6. package/dist/agents/claude-code.js.map +1 -1
  7. package/dist/agents/codex-cli.d.ts +7 -1
  8. package/dist/agents/codex-cli.d.ts.map +1 -1
  9. package/dist/agents/codex-cli.js +9 -5
  10. package/dist/agents/codex-cli.js.map +1 -1
  11. package/dist/agents/copilot.d.ts +7 -1
  12. package/dist/agents/copilot.d.ts.map +1 -1
  13. package/dist/agents/copilot.js +9 -5
  14. package/dist/agents/copilot.js.map +1 -1
  15. package/dist/agents/gemini-cli.d.ts +7 -1
  16. package/dist/agents/gemini-cli.d.ts.map +1 -1
  17. package/dist/agents/gemini-cli.js +9 -5
  18. package/dist/agents/gemini-cli.js.map +1 -1
  19. package/dist/agents/index.d.ts +1 -1
  20. package/dist/agents/index.d.ts.map +1 -1
  21. package/dist/agents/types.d.ts +33 -0
  22. package/dist/agents/types.d.ts.map +1 -1
  23. package/dist/cli/_progress.d.ts +138 -0
  24. package/dist/cli/_progress.d.ts.map +1 -0
  25. package/dist/cli/_progress.js +176 -0
  26. package/dist/cli/_progress.js.map +1 -0
  27. package/dist/cli/index.d.ts.map +1 -1
  28. package/dist/cli/index.js +2 -0
  29. package/dist/cli/index.js.map +1 -1
  30. package/dist/cli/research.d.ts +18 -20
  31. package/dist/cli/research.d.ts.map +1 -1
  32. package/dist/cli/research.js +318 -203
  33. package/dist/cli/research.js.map +1 -1
  34. package/dist/cli/review.d.ts +7 -0
  35. package/dist/cli/review.d.ts.map +1 -1
  36. package/dist/cli/review.js +46 -1
  37. package/dist/cli/review.js.map +1 -1
  38. package/dist/cli/source.d.ts +23 -2
  39. package/dist/cli/source.d.ts.map +1 -1
  40. package/dist/cli/source.js +425 -7
  41. package/dist/cli/source.js.map +1 -1
  42. package/dist/cli/update.d.ts +7 -0
  43. package/dist/cli/update.d.ts.map +1 -1
  44. package/dist/cli/update.js +41 -1
  45. package/dist/cli/update.js.map +1 -1
  46. package/dist/cli/watch.d.ts.map +1 -1
  47. package/dist/cli/watch.js +65 -3
  48. package/dist/cli/watch.js.map +1 -1
  49. package/dist/cli/workflow/generate-combined.d.ts +100 -0
  50. package/dist/cli/workflow/generate-combined.d.ts.map +1 -0
  51. package/dist/cli/workflow/generate-combined.js +387 -0
  52. package/dist/cli/workflow/generate-combined.js.map +1 -0
  53. package/dist/cli/workflow/generate-watch.d.ts +142 -0
  54. package/dist/cli/workflow/generate-watch.d.ts.map +1 -0
  55. package/dist/cli/workflow/generate-watch.js +338 -0
  56. package/dist/cli/workflow/generate-watch.js.map +1 -0
  57. package/dist/cli/workflow.d.ts +29 -0
  58. package/dist/cli/workflow.d.ts.map +1 -0
  59. package/dist/cli/workflow.js +66 -0
  60. package/dist/cli/workflow.js.map +1 -0
  61. package/dist/core/feeds/_fetch.d.ts +10 -0
  62. package/dist/core/feeds/_fetch.d.ts.map +1 -1
  63. package/dist/core/feeds/_fetch.js +182 -0
  64. package/dist/core/feeds/_fetch.js.map +1 -1
  65. package/dist/core/feeds/_jsonpath.d.ts +57 -0
  66. package/dist/core/feeds/_jsonpath.d.ts.map +1 -0
  67. package/dist/core/feeds/_jsonpath.js +207 -0
  68. package/dist/core/feeds/_jsonpath.js.map +1 -0
  69. package/dist/core/feeds/html-js.d.ts +8 -0
  70. package/dist/core/feeds/html-js.d.ts.map +1 -1
  71. package/dist/core/feeds/html-js.js +47 -1
  72. package/dist/core/feeds/html-js.js.map +1 -1
  73. package/dist/core/feeds/index.d.ts +1 -1
  74. package/dist/core/feeds/index.d.ts.map +1 -1
  75. package/dist/core/feeds/index.js +4 -0
  76. package/dist/core/feeds/index.js.map +1 -1
  77. package/dist/core/feeds/json-api.d.ts +3 -0
  78. package/dist/core/feeds/json-api.d.ts.map +1 -0
  79. package/dist/core/feeds/json-api.js +723 -0
  80. package/dist/core/feeds/json-api.js.map +1 -0
  81. package/dist/core/feeds/json-feed.d.ts +11 -0
  82. package/dist/core/feeds/json-feed.d.ts.map +1 -0
  83. package/dist/core/feeds/json-feed.js +242 -0
  84. package/dist/core/feeds/json-feed.js.map +1 -0
  85. package/dist/core/feeds/types.d.ts +123 -0
  86. package/dist/core/feeds/types.d.ts.map +1 -1
  87. package/dist/core/progress.d.ts +101 -0
  88. package/dist/core/progress.d.ts.map +1 -0
  89. package/dist/core/progress.js +212 -0
  90. package/dist/core/progress.js.map +1 -0
  91. package/dist/core/recipes.d.ts +138 -0
  92. package/dist/core/recipes.d.ts.map +1 -0
  93. package/dist/core/recipes.js +238 -0
  94. package/dist/core/recipes.js.map +1 -0
  95. package/dist/core/watcher.d.ts +61 -1
  96. package/dist/core/watcher.d.ts.map +1 -1
  97. package/dist/core/watcher.js +99 -2
  98. package/dist/core/watcher.js.map +1 -1
  99. package/dist/recipes/aws-whats-new.yaml +61 -0
  100. package/dist/recipes/dev-to.yaml +40 -0
  101. package/dist/schemas/index.d.ts +1 -0
  102. package/dist/schemas/index.d.ts.map +1 -1
  103. package/dist/schemas/index.js +1 -0
  104. package/dist/schemas/index.js.map +1 -1
  105. package/dist/schemas/recipe.d.ts +115 -0
  106. package/dist/schemas/recipe.d.ts.map +1 -0
  107. package/dist/schemas/recipe.js +54 -0
  108. package/dist/schemas/recipe.js.map +1 -0
  109. package/dist/schemas/source.d.ts +130 -0
  110. package/dist/schemas/source.d.ts.map +1 -1
  111. package/dist/schemas/source.js +130 -0
  112. package/dist/schemas/source.js.map +1 -1
  113. package/dist/templates/agents/AGENTS.md +31 -3
  114. package/dist/templates/feedradar.md +23 -8
  115. package/dist/templates/workflows/combined.template.yaml.tmpl +110 -0
  116. package/dist/templates/workflows/watch.template.yaml.tmpl +103 -0
  117. package/package.json +1 -2
@@ -0,0 +1,212 @@
1
+ /**
2
+ * ProgressReporter — UX abstraction for long-running CLI operations.
3
+ *
4
+ * Implements [ADR-0015 Progress Reporting UX](../../docs/adr/0015-progress-reporting-ux.md):
5
+ *
6
+ * - 3-layer model (phase markers / heartbeat spinner / side metrics)
7
+ * - TTY auto-detection with env / flag overrides (`RADAR_NO_PROGRESS=1`,
8
+ * `--quiet`, `--verbose`)
9
+ * - CI / non-TTY safe degradation: spinner becomes plain text and `\r`
10
+ * same-line updates are disabled
11
+ * - Zero new runtime dependencies — the spinner frame set
12
+ * (`⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`) is rotated by an internal `setInterval` and rendered via
13
+ * `process.stderr.write` + ANSI escape `\x1b[K\r`
14
+ *
15
+ * This is the **base** (#196) — only the interface, the factory, and the
16
+ * default reporter implementation. CLI integration (`research` / `review` /
17
+ * `update`) ships in #197, feed adapter integration in #198. Adapter call
18
+ * sites in `src/agents/*.ts` consume the optional `onProgress` callback
19
+ * defined alongside this module (see `src/agents/types.ts`).
20
+ */
21
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
22
+ const ANSI_CLEAR_LINE = "\x1b[K";
23
+ /**
24
+ * No-op reporter. Used by:
25
+ *
26
+ * - tests that do not care about progress output
27
+ * - `--quiet` / `RADAR_NO_PROGRESS=1` paths (see `createProgressReporter`)
28
+ * - adapter call sites where the caller did not opt into progress
29
+ *
30
+ * All six methods return immediately so wiring a `noopProgressReporter()` to
31
+ * an existing adapter is byte-equivalent to passing `undefined`.
32
+ */
33
+ export function noopProgressReporter() {
34
+ return {
35
+ phase() { },
36
+ start() { },
37
+ update() { },
38
+ succeed() { },
39
+ fail() { },
40
+ raw() { },
41
+ };
42
+ }
43
+ /**
44
+ * Construct a `ProgressReporter` honouring the ADR-0015 D2 priority table:
45
+ *
46
+ * env (`RADAR_NO_PROGRESS=1`) > flag (`level`) > TTY auto-detect
47
+ *
48
+ * - `level: "quiet"` → no-op (callers want zero output)
49
+ * - `RADAR_NO_PROGRESS=1` → no-op (CI escape hatch)
50
+ * - non-TTY + `level: "normal"` → plain text (phase markers + completion
51
+ * lines, NO spinner animation, NO `\r` overwrite)
52
+ * - TTY + `level: "normal"` → phase markers + spinner + same-line update
53
+ * - `level: "verbose"` → phase markers + spinner (if TTY) + `raw()` pass-
54
+ * through enabled (regardless of TTY)
55
+ *
56
+ * The reporter is self-contained: callers should not depend on internal
57
+ * state (e.g. the active spinner timer). The contract is the 6-method
58
+ * interface above.
59
+ */
60
+ export function createProgressReporter(opts) {
61
+ // Env escape hatch (D2 table row 1). Honoured even at `level: "verbose"`
62
+ // — CI environments must be able to opt out unconditionally.
63
+ if (process.env.RADAR_NO_PROGRESS === "1") {
64
+ return noopProgressReporter();
65
+ }
66
+ if (opts.level === "quiet") {
67
+ return noopProgressReporter();
68
+ }
69
+ const tty = opts.tty ?? Boolean(process.stderr.isTTY);
70
+ const stream = opts.stream ?? process.stderr;
71
+ const heartbeatMs = opts.heartbeatMs ?? 1000;
72
+ const now = opts.now ?? Date.now;
73
+ const verbose = opts.level === "verbose";
74
+ // Active spinner state. `null` means no spinner is running.
75
+ let active = null;
76
+ function renderSpinnerRow() {
77
+ if (!active)
78
+ return "";
79
+ const elapsedSec = Math.max(0, Math.floor((now() - active.startedAt) / 1000));
80
+ const mm = String(Math.floor(elapsedSec / 60)).padStart(2, "0");
81
+ const ss = String(elapsedSec % 60).padStart(2, "0");
82
+ const frame = SPINNER_FRAMES[active.frame % SPINNER_FRAMES.length];
83
+ const metricEntries = Object.entries(active.metrics);
84
+ const metricsSuffix = metricEntries.length > 0 ? ` ${metricEntries.map(([k, v]) => `${k}: ${v}`).join(" ")}` : "";
85
+ return `${frame} ${active.label} [${mm}:${ss}]${metricsSuffix}`;
86
+ }
87
+ function repaint() {
88
+ if (!active)
89
+ return;
90
+ if (tty) {
91
+ stream.write(`\r${ANSI_CLEAR_LINE}${renderSpinnerRow()}`);
92
+ }
93
+ }
94
+ function clearSpinnerLine() {
95
+ if (tty && active) {
96
+ stream.write(`\r${ANSI_CLEAR_LINE}`);
97
+ }
98
+ }
99
+ function stopSpinner() {
100
+ if (active?.timer) {
101
+ clearInterval(active.timer);
102
+ }
103
+ active = null;
104
+ }
105
+ return {
106
+ phase(name, info) {
107
+ // Phase markers always claim their own line; clear any active spinner
108
+ // overwrite first so we don't leave a half-painted row in scrollback.
109
+ clearSpinnerLine();
110
+ const suffix = info ? ` (${info})` : "";
111
+ stream.write(`${name}${suffix}\n`);
112
+ // Re-paint the spinner row on the new line so it continues to update.
113
+ repaint();
114
+ },
115
+ start(label) {
116
+ // If a previous spinner was still active, drop it silently — the new
117
+ // start supersedes it (caller bug, but we don't want to crash).
118
+ clearSpinnerLine();
119
+ stopSpinner();
120
+ active = {
121
+ label,
122
+ startedAt: now(),
123
+ frame: 0,
124
+ metrics: {},
125
+ timer: null,
126
+ };
127
+ if (tty) {
128
+ // Paint frame 0 immediately so the user sees something before the
129
+ // first heartbeat tick fires.
130
+ repaint();
131
+ if (heartbeatMs > 0) {
132
+ const timer = setInterval(() => {
133
+ if (!active)
134
+ return;
135
+ active.frame += 1;
136
+ repaint();
137
+ }, heartbeatMs);
138
+ // Don't keep the event loop alive just for the spinner — if the
139
+ // host process is otherwise idle (e.g. awaiting a child), we still
140
+ // want it to exit when the work is done.
141
+ if (typeof timer.unref === "function") {
142
+ timer.unref();
143
+ }
144
+ active.timer = timer;
145
+ }
146
+ }
147
+ else {
148
+ // Non-TTY plain-text degrade: one line per state transition, no `\r`.
149
+ stream.write(`${label}\n`);
150
+ }
151
+ },
152
+ update(metrics) {
153
+ if (!active)
154
+ return;
155
+ // Merge so successive `update` calls only need to pass the changed
156
+ // metric (e.g. page index ticks per fetch).
157
+ active.metrics = { ...active.metrics, ...metrics };
158
+ if (tty) {
159
+ repaint();
160
+ }
161
+ // On non-TTY we intentionally drop tick updates to avoid spamming the
162
+ // log with one line per page. The next phase / succeed / fail line
163
+ // re-states the final metric set.
164
+ },
165
+ succeed(label, duration) {
166
+ const elapsedMs = duration ?? (active ? now() - active.startedAt : 0);
167
+ const formatted = formatDuration(elapsedMs);
168
+ clearSpinnerLine();
169
+ stopSpinner();
170
+ stream.write(`${label} (${formatted})\n`);
171
+ },
172
+ fail(label, reason) {
173
+ clearSpinnerLine();
174
+ stopSpinner();
175
+ stream.write(`${label} — ${reason}\n`);
176
+ },
177
+ raw(text) {
178
+ if (!verbose)
179
+ return;
180
+ // Clear the spinner row before flushing pass-through so the agent's
181
+ // stdout / stderr line doesn't end up appended to the spinner frame.
182
+ // On non-TTY, the spinner doesn't share a line so clearing is a no-op.
183
+ clearSpinnerLine();
184
+ stream.write(text);
185
+ // If the chunk does not end in a newline, the spinner repaint would
186
+ // overwrite the tail of the chunk. Insert a soft newline before
187
+ // re-rendering so the chunk is preserved verbatim in scrollback.
188
+ if (active && tty && !text.endsWith("\n")) {
189
+ stream.write("\n");
190
+ }
191
+ repaint();
192
+ },
193
+ };
194
+ }
195
+ /**
196
+ * Format an elapsed-time in milliseconds as either `Nms`, `N.Ns`, or
197
+ * `Nm Ns`. Used by `succeed()` so the completion line carries the actual
198
+ * sub-task duration without the caller having to compute it.
199
+ */
200
+ function formatDuration(ms) {
201
+ if (ms < 1000) {
202
+ return `${ms}ms`;
203
+ }
204
+ if (ms < 60_000) {
205
+ const seconds = (ms / 1000).toFixed(1);
206
+ return `${seconds}s`;
207
+ }
208
+ const minutes = Math.floor(ms / 60_000);
209
+ const seconds = Math.floor((ms % 60_000) / 1000);
210
+ return `${minutes}m ${seconds}s`;
211
+ }
212
+ //# sourceMappingURL=progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/core/progress.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,138 @@
1
+ import { type RecipeFile } from "../schemas/recipe.js";
2
+ /**
3
+ * Recipe loader and CLI-args merger for `radar source recipes` /
4
+ * `radar source add --recipe <name>` (ADR-0012 §D3, strategy A — リポ同梱).
5
+ *
6
+ * Design notes:
7
+ *
8
+ * - Recipes live in `recipes/*.yaml` at the package root and are bundled
9
+ * into npm publish via the package.json `files` allowlist plus a copy
10
+ * step in `scripts/copy-skills.mjs` (`recipes/` → `dist/recipes/`). The
11
+ * resolver tries the compiled location first, then falls back to the
12
+ * source tree (used by the test suite and `pnpm test` runs that have
13
+ * not built `dist/` yet).
14
+ *
15
+ * - The directory is allowed to be empty (#178 adds the actual bundled
16
+ * recipes). When the directory is missing entirely the loader behaves
17
+ * the same as "no recipes" — the bundle is optional at the schema
18
+ * level. The CLI surfaces a friendly "no recipes" message instead of
19
+ * an error in that case.
20
+ *
21
+ * - Each recipe is independently parse-and-validate so one malformed
22
+ * YAML does not prevent the rest from being listed. `listRecipes`
23
+ * returns per-recipe `error` strings; `loadRecipe(name)` throws so
24
+ * `--recipe <name>` can hard-fail at `source add` time.
25
+ *
26
+ * - The recipe's identifier ( `--recipe <name>` match key) is the YAML
27
+ * filename stem. There is no inner "name" field that doubles as the
28
+ * match key — recipe authors rename the file to rename the recipe.
29
+ * `RecipeFile.name` is the *display name* (mirrors `Source.name`).
30
+ */
31
+ /** A recipe loaded from disk, paired with its filename-derived identifier. */
32
+ export interface LoadedRecipe {
33
+ /** Filename stem (e.g. `aws-whats-new` for `aws-whats-new.yaml`). */
34
+ name: string;
35
+ /** Absolute path of the recipe YAML, useful for error messages. */
36
+ path: string;
37
+ recipe: RecipeFile;
38
+ }
39
+ /** Entry returned by `listRecipes`, including malformed recipes (with `error`). */
40
+ export interface RecipeListEntry {
41
+ name: string;
42
+ path: string;
43
+ /** Parsed recipe, or `null` when this entry failed to load (see `error`). */
44
+ recipe: RecipeFile | null;
45
+ /** Human-readable error string when the entry could not be loaded. */
46
+ error?: string;
47
+ }
48
+ /** Options for the loader/lister to override the recipes directory (used by tests). */
49
+ export interface RecipeLoaderOptions {
50
+ recipesRoot?: string;
51
+ }
52
+ /**
53
+ * Resolve the directory holding the bundled recipes.
54
+ *
55
+ * Compiled layout (npm install): `dist/core/recipes.js` → `../recipes`.
56
+ * Source layout (tests / `pnpm test`): `src/core/recipes.ts` → `../../recipes`.
57
+ *
58
+ * We probe the compiled location first because that is the path users
59
+ * hit at runtime. Both paths can be present during local development
60
+ * (after `pnpm run build`); preferring compiled keeps the source tree
61
+ * from being the active asset directory by accident.
62
+ */
63
+ export declare function resolveRecipesRoot(): Promise<string>;
64
+ /**
65
+ * List all bundled recipes by reading every `*.yaml` file in the recipes
66
+ * directory.
67
+ *
68
+ * Behaviour:
69
+ *
70
+ * - Missing recipes directory → returns `[]` (treated as "no recipes",
71
+ * not an error). This matches the bootstrap state where #178 has not
72
+ * yet shipped the actual recipe files.
73
+ * - Each `.yaml` is independently parsed and Zod-validated. Failures are
74
+ * captured in the per-entry `error` field so partial corruption never
75
+ * prevents the rest from rendering.
76
+ * - Entries are sorted by `name` for deterministic output (tests rely on
77
+ * this; users get a stable display order).
78
+ */
79
+ export declare function listRecipes(opts?: RecipeLoaderOptions): Promise<RecipeListEntry[]>;
80
+ /**
81
+ * Load a single recipe by its filename stem (e.g. `aws-whats-new`).
82
+ *
83
+ * Throws on:
84
+ *
85
+ * - missing recipes directory (the bundle is absent)
86
+ * - unknown recipe name (the file does not exist)
87
+ * - malformed YAML or Zod-schema violation
88
+ *
89
+ * The error messages are user-facing — `source add --recipe` surfaces
90
+ * them via the CLI `error()` sink without further wrapping.
91
+ */
92
+ export declare function loadRecipe(name: string, opts?: RecipeLoaderOptions): Promise<LoadedRecipe>;
93
+ /**
94
+ * CLI overrides applied on top of a recipe when generating a Source.
95
+ *
96
+ * The whitelist is intentionally narrow:
97
+ *
98
+ * - `id` (required) — recipes never carry an `id`; the caller picks one
99
+ * per workspace
100
+ * - `name` (display name) — useful when a single recipe is applied
101
+ * multiple times to differentiate, or to localize
102
+ * - `tags` — workspace-level taxonomy that varies per install
103
+ * - `filters.keywords` / `filters.excludeKeywords` — the only fields a
104
+ * user is reliably expected to override; "what counts as a hit" is
105
+ * per-workspace
106
+ *
107
+ * Other fields (`pagination`, `jsonSelectors`, `selectors`, `js`,
108
+ * `http`, `url`, `kind`, `trustLevel`) are NOT overridable from the
109
+ * CLI. Recipe authors own these "structural" fields. Users edit the
110
+ * generated `sources/<id>.yaml` if they need to deviate further.
111
+ */
112
+ export interface RecipeOverrides {
113
+ /** Required — the source id chosen by the caller. */
114
+ id: string;
115
+ /** Optional override for `Source.name` (display name). */
116
+ name?: string;
117
+ /** Optional override for `Source.tags` (replaces, does not merge). */
118
+ tags?: string[];
119
+ /** Optional override for `filters.keywords` (replaces, does not merge). */
120
+ keywords?: string[];
121
+ /** Optional override for `filters.excludeKeywords` (replaces, does not merge). */
122
+ excludeKeywords?: string[];
123
+ }
124
+ /**
125
+ * Merge a recipe with CLI overrides to produce a plain object suitable
126
+ * for `SourceSchema.safeParse`.
127
+ *
128
+ * Override semantics: each field is *replaced* (not merged) when the
129
+ * override is present. This mirrors `source add` flag semantics
130
+ * (`--keywords a,b` replaces, never appends) and keeps the mental model
131
+ * uniform across `add` and `add --recipe`.
132
+ *
133
+ * `description` from the recipe is dropped — it is recipe metadata,
134
+ * not Source metadata. Strip it explicitly so the generated YAML does
135
+ * not carry a stray field that fails downstream schema validation.
136
+ */
137
+ export declare function mergeRecipeWithOverrides(recipe: RecipeFile, overrides: RecipeOverrides): Record<string, unknown>;
138
+ //# sourceMappingURL=recipes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipes.d.ts","sourceRoot":"","sources":["../../src/core/recipes.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,sBAAsB,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,uFAAuF;AACvF,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAS1D;AAWD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,WAAW,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CA8D5F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAyDvB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,EAAE,EAAE,MAAM,CAAC;IACX,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,eAAe,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA+CzB"}
@@ -0,0 +1,238 @@
1
+ import { access, readdir, readFile } from "node:fs/promises";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { parse as parseYaml } from "yaml";
5
+ import { RecipeFileSchema } from "../schemas/recipe.js";
6
+ /**
7
+ * Resolve the directory holding the bundled recipes.
8
+ *
9
+ * Compiled layout (npm install): `dist/core/recipes.js` → `../recipes`.
10
+ * Source layout (tests / `pnpm test`): `src/core/recipes.ts` → `../../recipes`.
11
+ *
12
+ * We probe the compiled location first because that is the path users
13
+ * hit at runtime. Both paths can be present during local development
14
+ * (after `pnpm run build`); preferring compiled keeps the source tree
15
+ * from being the active asset directory by accident.
16
+ */
17
+ export async function resolveRecipesRoot() {
18
+ const here = dirname(fileURLToPath(import.meta.url));
19
+ const compiled = resolve(here, "../recipes");
20
+ if (await pathExists(compiled)) {
21
+ return compiled;
22
+ }
23
+ // Source layout fallback. We walk two levels up from src/core/ to find
24
+ // the package root, then descend into `recipes/`.
25
+ return resolve(here, "../../recipes");
26
+ }
27
+ async function pathExists(p) {
28
+ try {
29
+ await access(p);
30
+ return true;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }
36
+ /**
37
+ * List all bundled recipes by reading every `*.yaml` file in the recipes
38
+ * directory.
39
+ *
40
+ * Behaviour:
41
+ *
42
+ * - Missing recipes directory → returns `[]` (treated as "no recipes",
43
+ * not an error). This matches the bootstrap state where #178 has not
44
+ * yet shipped the actual recipe files.
45
+ * - Each `.yaml` is independently parsed and Zod-validated. Failures are
46
+ * captured in the per-entry `error` field so partial corruption never
47
+ * prevents the rest from rendering.
48
+ * - Entries are sorted by `name` for deterministic output (tests rely on
49
+ * this; users get a stable display order).
50
+ */
51
+ export async function listRecipes(opts = {}) {
52
+ const root = opts.recipesRoot ?? (await resolveRecipesRoot());
53
+ if (!(await pathExists(root))) {
54
+ return [];
55
+ }
56
+ let entries;
57
+ try {
58
+ entries = await readdir(root);
59
+ }
60
+ catch {
61
+ return [];
62
+ }
63
+ // `.gitkeep` (or any other dotfile) must not be picked up as a recipe;
64
+ // the `*.yaml` glob is enforced by suffix rather than a separate
65
+ // exclude list.
66
+ const yamlFiles = entries.filter((f) => f.endsWith(".yaml")).sort();
67
+ const results = [];
68
+ for (const filename of yamlFiles) {
69
+ const path = join(root, filename);
70
+ const name = filename.slice(0, -".yaml".length);
71
+ let raw;
72
+ try {
73
+ raw = await readFile(path, "utf8");
74
+ }
75
+ catch (e) {
76
+ results.push({
77
+ name,
78
+ path,
79
+ recipe: null,
80
+ error: `failed to read: ${e instanceof Error ? e.message : String(e)}`,
81
+ });
82
+ continue;
83
+ }
84
+ let parsed;
85
+ try {
86
+ parsed = parseYaml(raw);
87
+ }
88
+ catch (e) {
89
+ results.push({
90
+ name,
91
+ path,
92
+ recipe: null,
93
+ error: `invalid YAML: ${e instanceof Error ? e.message : String(e)}`,
94
+ });
95
+ continue;
96
+ }
97
+ const result = RecipeFileSchema.safeParse(parsed);
98
+ if (!result.success) {
99
+ const issues = result.error.issues
100
+ .map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
101
+ .join("; ");
102
+ results.push({
103
+ name,
104
+ path,
105
+ recipe: null,
106
+ error: `schema validation failed: ${issues}`,
107
+ });
108
+ continue;
109
+ }
110
+ results.push({ name, path, recipe: result.data });
111
+ }
112
+ return results;
113
+ }
114
+ /**
115
+ * Load a single recipe by its filename stem (e.g. `aws-whats-new`).
116
+ *
117
+ * Throws on:
118
+ *
119
+ * - missing recipes directory (the bundle is absent)
120
+ * - unknown recipe name (the file does not exist)
121
+ * - malformed YAML or Zod-schema violation
122
+ *
123
+ * The error messages are user-facing — `source add --recipe` surfaces
124
+ * them via the CLI `error()` sink without further wrapping.
125
+ */
126
+ export async function loadRecipe(name, opts = {}) {
127
+ // Reject path-separator / traversal characters defensively. `--recipe`
128
+ // is positional CLI input and could be copied from arbitrary sources;
129
+ // the same posture as `isSafeSourceId` keeps the lookup confined to
130
+ // the recipes directory.
131
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name) || name.includes("..")) {
132
+ throw new Error(`invalid recipe name '${name}' (must match [A-Za-z0-9][A-Za-z0-9._-]*)`);
133
+ }
134
+ const root = opts.recipesRoot ?? (await resolveRecipesRoot());
135
+ if (!(await pathExists(root))) {
136
+ throw new Error(`no bundled recipes available (recipes/ not found at ${root}); recipe '${name}' cannot be resolved`);
137
+ }
138
+ const path = join(root, `${name}.yaml`);
139
+ if (!(await pathExists(path))) {
140
+ // Surface available names so the user can self-correct without having
141
+ // to run a second command. List failures are swallowed here (best
142
+ // effort) so the primary error message is the one the user sees.
143
+ let available = [];
144
+ try {
145
+ const all = await listRecipes({ recipesRoot: root });
146
+ available = all.map((r) => r.name);
147
+ }
148
+ catch {
149
+ // ignore — we already have the primary error to report
150
+ }
151
+ const hint = available.length === 0 ? "" : ` (available: ${available.join(", ")})`;
152
+ throw new Error(`recipe '${name}' not found${hint}`);
153
+ }
154
+ let raw;
155
+ try {
156
+ raw = await readFile(path, "utf8");
157
+ }
158
+ catch (e) {
159
+ throw new Error(`failed to read recipe '${name}': ${e instanceof Error ? e.message : String(e)}`);
160
+ }
161
+ let parsed;
162
+ try {
163
+ parsed = parseYaml(raw);
164
+ }
165
+ catch (e) {
166
+ throw new Error(`invalid YAML in recipe '${name}': ${e instanceof Error ? e.message : String(e)}`);
167
+ }
168
+ const result = RecipeFileSchema.safeParse(parsed);
169
+ if (!result.success) {
170
+ const issues = result.error.issues
171
+ .map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
172
+ .join("; ");
173
+ throw new Error(`recipe '${name}' failed schema validation: ${issues}`);
174
+ }
175
+ return { name, path, recipe: result.data };
176
+ }
177
+ /**
178
+ * Merge a recipe with CLI overrides to produce a plain object suitable
179
+ * for `SourceSchema.safeParse`.
180
+ *
181
+ * Override semantics: each field is *replaced* (not merged) when the
182
+ * override is present. This mirrors `source add` flag semantics
183
+ * (`--keywords a,b` replaces, never appends) and keeps the mental model
184
+ * uniform across `add` and `add --recipe`.
185
+ *
186
+ * `description` from the recipe is dropped — it is recipe metadata,
187
+ * not Source metadata. Strip it explicitly so the generated YAML does
188
+ * not carry a stray field that fails downstream schema validation.
189
+ */
190
+ export function mergeRecipeWithOverrides(recipe, overrides) {
191
+ // Build the candidate as a fresh object so the recipe object on disk
192
+ // is not mutated and we get control over field ordering in the output
193
+ // YAML (id first → kind → url → ...).
194
+ const candidate = {
195
+ id: overrides.id,
196
+ kind: recipe.kind,
197
+ url: recipe.url,
198
+ };
199
+ // Display name: caller override wins, then recipe.name, then nothing.
200
+ if (overrides.name !== undefined) {
201
+ candidate.name = overrides.name;
202
+ }
203
+ else if (recipe.name !== undefined) {
204
+ candidate.name = recipe.name;
205
+ }
206
+ // Tags: override replaces; otherwise inherit recipe tags (which defaults
207
+ // to []). Emit only when non-empty so the YAML stays minimal for the
208
+ // common case "no tags in either place".
209
+ const tags = overrides.tags ?? recipe.tags;
210
+ if (tags.length > 0) {
211
+ candidate.tags = tags;
212
+ }
213
+ // Filters: override the include/exclude keyword arrays; preserve the
214
+ // recipe's other filter knobs (matchMode / matchFields / caseSensitive)
215
+ // because those reflect adapter-specific tuning that the recipe author
216
+ // already picked.
217
+ const mergedFilters = {
218
+ ...recipe.filters,
219
+ keywords: overrides.keywords ?? recipe.filters.keywords,
220
+ excludeKeywords: overrides.excludeKeywords ?? recipe.filters.excludeKeywords,
221
+ };
222
+ candidate.filters = mergedFilters;
223
+ // Structural fields that the recipe owns. Drop undefined entries to
224
+ // keep the generated YAML free of explicit nulls.
225
+ if (recipe.selectors !== undefined)
226
+ candidate.selectors = recipe.selectors;
227
+ if (recipe.js !== undefined)
228
+ candidate.js = recipe.js;
229
+ if (recipe.http !== undefined)
230
+ candidate.http = recipe.http;
231
+ if (recipe.pagination !== undefined)
232
+ candidate.pagination = recipe.pagination;
233
+ if (recipe.jsonSelectors !== undefined)
234
+ candidate.jsonSelectors = recipe.jsonSelectors;
235
+ candidate.trustLevel = recipe.trustLevel;
236
+ return candidate;
237
+ }
238
+ //# sourceMappingURL=recipes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipes.js","sourceRoot":"","sources":["../../src/core/recipes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAmB,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAwDzE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC7C,IAAI,MAAM,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/B,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,uEAAuE;IACvE,kDAAkD;IAClD,OAAO,OAAO,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;AACxC,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,CAAS;IACjC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAA4B,EAAE;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,IAAI,CAAC,MAAM,kBAAkB,EAAE,CAAC,CAAC;IAC9D,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,uEAAuE;IACvE,iEAAiE;IACjE,gBAAgB;IAChB,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAEpE,MAAM,OAAO,GAAsB,EAAE,CAAC;IACtC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAChD,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI;gBACJ,IAAI;gBACJ,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,mBAAmB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;aACvE,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI;gBACJ,IAAI;gBACJ,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,iBAAiB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;aACrE,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;iBAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;iBAC3D,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI;gBACJ,IAAI;gBACJ,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,6BAA6B,MAAM,EAAE;aAC7C,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAY,EACZ,OAA4B,EAAE;IAE9B,uEAAuE;IACvE,sEAAsE;IACtE,oEAAoE;IACpE,yBAAyB;IACzB,IAAI,CAAC,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,2CAA2C,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,IAAI,CAAC,MAAM,kBAAkB,EAAE,CAAC,CAAC;IAC9D,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,uDAAuD,IAAI,cAAc,IAAI,sBAAsB,CACpG,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;IACxC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAC9B,sEAAsE;QACtE,kEAAkE;QAClE,iEAAiE;QACjE,IAAI,SAAS,GAAa,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;YACrD,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,uDAAuD;QACzD,CAAC;QACD,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QACnF,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,cAAc,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,0BAA0B,IAAI,MAAM,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACjF,CAAC;IACJ,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,2BAA2B,IAAI,MAAM,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAClF,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAC3D,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,+BAA+B,MAAM,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;AAC7C,CAAC;AAkCD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAkB,EAClB,SAA0B;IAE1B,qEAAqE;IACrE,sEAAsE;IACtE,sCAAsC;IACtC,MAAM,SAAS,GAA4B;QACzC,EAAE,EAAE,SAAS,CAAC,EAAE;QAChB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,GAAG,EAAE,MAAM,CAAC,GAAG;KAChB,CAAC;IAEF,sEAAsE;IACtE,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACjC,SAAS,CAAC,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC;IAClC,CAAC;SAAM,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACrC,SAAS,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IAC/B,CAAC;IAED,yEAAyE;IACzE,qEAAqE;IACrE,yCAAyC;IACzC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC;IAC3C,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,SAAS,CAAC,IAAI,GAAG,IAAI,CAAC;IACxB,CAAC;IAED,qEAAqE;IACrE,wEAAwE;IACxE,uEAAuE;IACvE,kBAAkB;IAClB,MAAM,aAAa,GAAG;QACpB,GAAG,MAAM,CAAC,OAAO;QACjB,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ;QACvD,eAAe,EAAE,SAAS,CAAC,eAAe,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe;KAC7E,CAAC;IACF,SAAS,CAAC,OAAO,GAAG,aAAa,CAAC;IAElC,oEAAoE;IACpE,kDAAkD;IAClD,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS;QAAE,SAAS,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IAC3E,IAAI,MAAM,CAAC,EAAE,KAAK,SAAS;QAAE,SAAS,CAAC,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC;IACtD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,SAAS,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IAC5D,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS;QAAE,SAAS,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IAC9E,IAAI,MAAM,CAAC,aAAa,KAAK,SAAS;QAAE,SAAS,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;IAEvF,SAAS,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IAEzC,OAAO,SAAS,CAAC;AACnB,CAAC"}