@heripo/research-radar 5.0.6 → 5.2.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.
package/README.md CHANGED
@@ -23,7 +23,7 @@ Previously reported service metrics were $0.2–1 per issue and 15% CTR. These a
23
23
 
24
24
  - Type-safe TypeScript with strict interfaces
25
25
  - Provider pattern for swapping components (Crawling/Analysis/Content/Email)
26
- - 59 active crawling targets across heritage agencies, museums, academic societies
26
+ - 74 active crawling targets across heritage agencies, museums, academic societies, filtered at runtime by robots.txt
27
27
  - Multi LLM providers: OpenAI GPT-5 (analysis) + selectable content generation (OpenAI / Anthropic / Google)
28
28
  - Built-in retries, chain options, preview emails
29
29
 
@@ -70,7 +70,7 @@ For academic publications:
70
70
  npm install @heripo/research-radar '@llm-newsletter-kit/core@~3.0.0'
71
71
  ```
72
72
 
73
- **Requirements**: Node.js >= 24 and an ESM application. The package exports `dist/index.js` and TypeScript declarations (`dist/index.d.ts`), with a JavaScript sourcemap. The core engine is a peer dependency; the current supported range is `~3.0.0`.
73
+ **Requirements**: Node.js >= 24 and an ESM application. The package exports `dist/index.js` and TypeScript declarations (`dist/index.d.ts`), with a JavaScript sourcemap. The core engine is a peer dependency; the current supported range is `~3.0.5`. 3.0.5 is the floor because the newsletter generation prompt this package ships relies on the self-verification retry cap added there.
74
74
 
75
75
  Article analysis requires an OpenAI API key. Content generation requires a key for the selected provider (OpenAI / Anthropic / Google); OpenAI can use the same key for both. Keys are passed explicitly to the library; load environment variables in your application.
76
76
 
@@ -136,13 +136,15 @@ Candidate selection belongs to your repository. Persist `usedArticles` associati
136
136
 
137
137
  ### Optional generation settings
138
138
 
139
- | Option | Behavior |
140
- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
141
- | `logger` | Core `AppLogger` implementation, such as `console` |
142
- | `publishDate` | A real calendar date in `YYYY-MM-DD` format; invalid values throw. Defaults to the current date in `Asia/Seoul` (KST), regardless of server timezone |
143
- | `customFetch` | A `typeof fetch` implementation for crawling and parser API requests, such as a proxy adapter; does not configure LLM requests |
144
- | `templateOptions` | Default or KRAS newsletter branding and Markdown sections (see below) |
145
- | `previewNewsletter` | Fetch a saved `Newsletter` and send it through a supplied core `EmailService` |
139
+ | Option | Behavior |
140
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
141
+ | `logger` | Core `AppLogger` implementation, such as `console` |
142
+ | `publishDate` | A real calendar date in `YYYY-MM-DD` format; invalid values throw. Defaults to the current date in `Asia/Seoul` (KST), regardless of server timezone |
143
+ | `customFetch` | A `typeof fetch` implementation for crawling and parser API requests, such as a proxy adapter; does not configure LLM requests |
144
+ | `templateOptions` | Default or KRAS newsletter branding and Markdown sections (see below) |
145
+ | `promptProvider` | Core `PromptProvider` overriding the package's own LLM prompts entirely; omit to use the package's prompts, which fall back to core's defaults |
146
+ | `excavationReportSource` | Supplies 국가유산청 발굴조사 보고서 entries from the application; that board is then never crawled. Omit to crawl it as before |
147
+ | `previewNewsletter` | Fetch a saved `Newsletter` and send it through a supplied core `EmailService` |
146
148
 
147
149
  `previewNewsletter` requires `fetchNewsletterForPreview: () => Promise<Newsletter>`, `emailService` (with `send(message)`), and `emailMessage` (core `EmailMessage` without `subject`, `html`, or `text`). The core fills those three fields and skips preview delivery when no newsletter was created. Ensure the callback fetches the issue saved by this run.
148
150
 
@@ -165,16 +167,16 @@ Defaults below describe the checked-in code, not provider recommendations. This
165
167
 
166
168
  | Stage | Provider | Default model |
167
169
  | ------------------ | --------- | ------------------------ |
168
- | Tag classification | OpenAI | `gpt-5-mini` |
169
- | Image analysis | OpenAI | `gpt-5.1` |
170
- | Importance scoring | OpenAI | `gpt-5.1` |
171
- | Content generation | OpenAI | `gpt-5.4` |
170
+ | Tag classification | OpenAI | `gpt-5.6-luna` |
171
+ | Image analysis | OpenAI | `gpt-5.6-terra` |
172
+ | Importance scoring | OpenAI | `gpt-5.6-terra` |
173
+ | Content generation | OpenAI | `gpt-5.6-sol` |
172
174
  | Content generation | Anthropic | `claude-sonnet-4-6` |
173
175
  | Content generation | Google | `gemini-3.1-pro-preview` |
174
176
 
175
177
  Select content generation with `contentGeneration: { provider, apiKey, model? }`. `model` overrides that provider's default. Analysis models are configured in [analysis.provider.ts](./src/providers/analysis.provider.ts).
176
178
 
177
- [src/config/index.ts](./src/config/index.ts) defines Korean output (`outputLanguage: '한국어'`), the cultural heritage domain (`expertField: ['문화유산']`), brand name, `subscribePageUrl`, LLM `maxRetries: 5`, chain `stopAfterAttempt: 3`, and generation `temperature: 0.3`. Publication settings are `minimumArticleCountForIssue: 5` and `priorityArticleScoreThreshold: 8`; the core engine evaluates them. In the locked core 3.0.4 implementation, the count check skips **5 or fewer** candidates unless at least one has importance score >= 8. An empty candidate list is always skipped.
179
+ [src/config/index.ts](./src/config/index.ts) defines Korean output (`outputLanguage: '한국어'`), the cultural heritage domain (`expertField: ['문화유산']`), brand name, `subscribePageUrl`, LLM `maxRetries: 5`, chain `stopAfterAttempt: 3`, and generation `temperature: 0.3`. Publication settings are `minimumArticleCountForIssue: 5` and `priorityArticleScoreThreshold: 8`; the core engine evaluates them. In the locked core 3.0.5 implementation, the count check skips **5 or fewer** candidates unless at least one has importance score >= 8. An empty candidate list is always skipped.
178
180
 
179
181
  ## Crawling targets and parsers
180
182
 
@@ -182,16 +184,20 @@ Select content generation with `contentGeneration: { provider, apiKey, model? }`
182
184
 
183
185
  | Group | Active | Commented out |
184
186
  | ---------- | -----: | ------------: |
185
- | News | 48 | 10 |
186
- | Business | 4 | 0 |
187
- | Employment | 7 | 3 |
188
- | **Total** | **59** | **13** |
187
+ | News | 57 | 1 |
188
+ | Business | 5 | 0 |
189
+ | Employment | 12 | 0 |
190
+ | **Total** | **74** | **1** |
189
191
 
190
- The 13 commented targets are excluded from runtime configuration: one excavation status board is marked as low-value fragmented data, and 12 museum boards are marked as restricted by robots.txt. Their parser code remains in the repository.
192
+ Only the excavation status board stays commented out, as low-value fragmented data. Boards that a site's robots.txt restricts are configured normally and refused at runtime by the robots.txt check, so the configuration does not have to track each site's policy by hand. With current policies 14 of the 74 targets are refused.
193
+
194
+ Three targets are read from data.go.kr open APIs instead of scraped: 나라일터 (`PblJobService`) and 알리오 (`recruitment`) under Employment, and 나라장터 (`BidPublicInfoService`) under Business. Each is a single round of requests, and `publicDataApiKey` supplies the service key — omit it and they answer with an empty list without making a request. The boards carry every public-sector vacancy and procurement notice in the country, so both are narrowed before analysis. The job boards use `src/crawling/heritage-job-filter.ts`, whose exclusions are role nouns and hold up as patterns; roughly 2% of postings survive, about 2.6 a day. 나라장터 instead uses `src/crawling/heritage-triage.ts`, which asks a cheap model about 100 titles per request: heritage vocabulary cannot be settled by substring in Korean, and the deterministic filter remains behind it as the fallback when a batch fails.
195
+
196
+ Crawling fetches pass through that check (`src/crawling/robots.ts`) before reaching the network: a disallowed request is refused rather than sent, and a missing or unreachable robots.txt allows it. `robotsExemptOrigins` in `src/config/index.ts` lists origins exempted from the check, each with its reason.
191
197
 
192
198
  Sources include the Korea Heritage Service, National Research Institute of Cultural Heritage, National Research Institute of Maritime Heritage, Korea Heritage Agency, Korea Association of Archaeological Heritage, archaeological societies, and national museums.
193
199
 
194
- [src/parsers/](./src/parsers/) contains 20 organization-specific parser modules plus shared date and URL utilities. List parsers return `ParsedTargetListItem[]` (title, date, detail URL, date type, and optional source ID); detail parsers return Markdown `detailContent` and attachment/image flags. Parsers can be synchronous or asynchronous. KRAS, Yeongnam Archaeological Society, and maritime heritage sources use additional API requests for client-rendered content.
200
+ [src/parsers/](./src/parsers/) contains 23 organization-specific parser modules plus shared date and URL utilities. List parsers return `ParsedTargetListItem[]` (title, date, detail URL, date type, and optional source ID); detail parsers return Markdown `detailContent` and attachment/image flags. Parsers can be synchronous or asynchronous. KRAS, Yeongnam Archaeological Society, and maritime heritage sources use additional API requests for client-rendered content.
195
201
 
196
202
  `CrawlingProvider` uses a maximum concurrency of 5 and wraps the supplied fetch to route KRAS public detail URLs to its detail API, while retaining public URLs in article metadata. When constructing your own pipeline, use the provider's fetch together with its target groups.
197
203
 
@@ -203,7 +209,7 @@ import { getSourceList } from '@heripo/research-radar';
203
209
  const groups = getSourceList(); // [{ id, name, sources: [{ id, name, url }] }]
204
210
  ```
205
211
 
206
- `createCrawlingTargetGroups(customFetch?)`, `getSourceList()`, `contentOptions`, `newsletterConfig`, and `llmConfig` are public exports. The package also exports the three provider classes, `DateService`, `TaskService`, and their public configuration/dependency types through [src/index.ts](./src/index.ts).
212
+ `createCrawlingTargetGroups(customFetch?)`, `getSourceList()`, `contentOptions`, `newsletterConfig`, `llmConfig`, and `researchRadarPromptProvider` are public exports. `ExcavationReport` and `ExcavationReportSource` are exported as types. The package also exports the three provider classes, `DateService`, `TaskService`, and their public configuration/dependency types through [src/index.ts](./src/index.ts).
207
213
 
208
214
  ## Email templates
209
215
 
@@ -332,7 +338,7 @@ Use repeatable `--skip-target=<id-or-name>` or `--skip-target <id-or-name>` to e
332
338
 
333
339
  **Output**: Console table summary + compact text summary for CI integrations.
334
340
 
335
- **CI**: [.github/workflows/parser-health-check.yml](./.github/workflows/parser-health-check.yml) runs daily at 08:00 UTC (17:00 KST), or manually, on an `org-linux` runner with a 30-minute job timeout. It skips the two KHS excavation report/site-open targets (57 targets checked with the current configuration). Slack notifications require the `SLACK_BOT_TOKEN` secret and `SLACK_ALERT_DEV_CHANNEL` repository variable. Forks need a matching runner and notification configuration to use this workflow unchanged. The CLI also writes GitHub Actions outputs and a job summary when their environment variables are present.
341
+ **CI**: [.github/workflows/parser-health-check.yml](./.github/workflows/parser-health-check.yml) runs daily at 08:00 UTC (17:00 KST), or manually, on an `org-linux` runner with a 30-minute job timeout. It skips the two KHS excavation report/site-open targets. The health-check composes the same fetch as production — robots.txt gate, then the KRAS detail adapter — so disallowed boards are reported as skipped rather than failed: 16 skipped and 55 checked with the current configuration. The detail check tries up to three list items, so one unreadable post at the top of a board does not fail the target. Slack notifications require the `SLACK_BOT_TOKEN` secret and `SLACK_ALERT_DEV_CHANNEL` repository variable. Forks need a matching runner and notification configuration to use this workflow unchanged. The CLI also writes GitHub Actions outputs and a job summary when their environment variables are present.
336
342
 
337
343
  ## 🤝 Contributing
338
344
 
@@ -390,7 +396,7 @@ const contentGeneration: ContentGenerationConfig = {
390
396
  };
391
397
  ```
392
398
 
393
- Default models: openai=`gpt-5.4`, anthropic=`claude-sonnet-4-6`, google=`gemini-3.1-pro-preview`
399
+ Default models: openai=`gpt-5.6-sol`, anthropic=`claude-sonnet-4-6`, google=`gemini-3.1-pro-preview`
394
400
 
395
401
  To change analysis providers, update both `src/providers/analysis.provider.ts` (provider type and models) and `src/newsletter-generator.ts` (provider construction), using a compatible AI SDK provider. Also adapt domain-specific minimum-score rules in the analysis provider, output language and expert fields in config, package metadata, and the GitHub Actions runner/Slack settings for your fork.
396
402
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { UrlString, ParsedTarget, CrawlingTargetGroup, CrawlingTarget, UnscoredArticle, ArticleForUpdateByAnalysis, ArticleForGenerateContent, Newsletter, AppLogger, EmailService, EmailMessage, DateService as DateService$1, IsoDateString, TaskService as TaskService$1, AnalysisProvider as AnalysisProvider$1, ContentGenerateProvider as ContentGenerateProvider$1, HtmlTemplate, CrawlingProvider as CrawlingProvider$1, GenerateNewsletterConfig } from '@llm-newsletter-kit/core';
1
+ import { UrlString, ParsedTarget, CrawlingTargetGroup, CrawlingTarget, UnscoredArticle, ArticleForUpdateByAnalysis, ArticleForGenerateContent, Newsletter, IsoDateString, AppLogger, EmailService, EmailMessage, PromptProvider, DateService as DateService$1, TaskService as TaskService$1, AnalysisProvider as AnalysisProvider$1, ContentGenerateProvider as ContentGenerateProvider$1, HtmlTemplate, CrawlingProvider as CrawlingProvider$1, GenerateNewsletterConfig } from '@llm-newsletter-kit/core';
2
2
  import { OpenAIProvider } from '@ai-sdk/openai';
3
3
  import { LanguageModel } from 'ai';
4
4
 
@@ -54,6 +54,53 @@ interface ArticleRepository {
54
54
  */
55
55
  findCandidatesForNewsletter(): Promise<ArticleForGenerateContent[]>;
56
56
  }
57
+ /**
58
+ * A single excavation report supplied by the application instead of crawled.
59
+ *
60
+ * Field names mirror what the 국가유산청 report board exposes, so an application
61
+ * that already stores these rows can hand them over without reshaping them.
62
+ */
63
+ interface ExcavationReport {
64
+ /**
65
+ * The board's own identifier (`ecexmRcno`). Used both as the article's unique
66
+ * id and to build its public detail URL, so it must match the value the board
67
+ * uses — otherwise previously crawled reports are re-saved as duplicates.
68
+ * @example "202609157717"
69
+ */
70
+ externalId: string;
71
+ /**
72
+ * Report title (보고서명).
73
+ * @example "태안 태안읍성 -남문지 및 연지-"
74
+ */
75
+ title: string;
76
+ /**
77
+ * Submission date (제출일) in ISO format (YYYY-MM-DD).
78
+ * @example "2026-09-15"
79
+ */
80
+ submittedDate: IsoDateString;
81
+ /**
82
+ * Report detail fields as label/value pairs, rendered into the article body in
83
+ * insertion order. Supply whatever the source holds — 허가번호, 유적명,
84
+ * 발간기관, 조사시도시군구, 조사기간, 유적성격/시대구분 and so on.
85
+ * Empty values are skipped.
86
+ */
87
+ fields: Record<string, string | null | undefined>;
88
+ /**
89
+ * Whether the report has a downloadable file. Defaults to true, matching the
90
+ * crawled parser.
91
+ */
92
+ hasAttachedFile?: boolean;
93
+ }
94
+ /**
95
+ * Supplies excavation reports from the application instead of crawling them.
96
+ *
97
+ * When provided, the 국가유산청 발굴조사 보고서 board is served from this
98
+ * function and never requested over the network; the rest of the crawl is
99
+ * unaffected. When omitted, the board is crawled as before.
100
+ *
101
+ * Called at most once per generation run.
102
+ */
103
+ type ExcavationReportSource = () => Promise<ExcavationReport[]>;
57
104
  /**
58
105
  * Repository interface for tag management
59
106
  */
@@ -178,7 +225,7 @@ interface PreviewNewsletterOptions {
178
225
  * Each provider uses a sensible default model that can be overridden.
179
226
  *
180
227
  * Default models:
181
- * - openai: `gpt-5.4`
228
+ * - openai: `gpt-5.6-sol`
182
229
  * - anthropic: `claude-sonnet-4-6`
183
230
  * - google: `gemini-3.1-pro-preview`
184
231
  */
@@ -226,6 +273,30 @@ interface NewsletterGeneratorDependencies {
226
273
  templateOptions?: NewsletterTemplateOptions;
227
274
  /** Custom fetch function for crawling (e.g., proxy-based fetch). Optional. */
228
275
  customFetch?: typeof fetch;
276
+ /**
277
+ * Supplies 국가유산청 발굴조사 보고서 entries from the application (optional).
278
+ *
279
+ * When provided, that board is read from this function and never crawled,
280
+ * which lets an application that already stores the reports reuse them. Omit
281
+ * it to keep crawling the board as before. No other target is affected.
282
+ */
283
+ excavationReportSource?: ExcavationReportSource;
284
+ /**
285
+ * data.go.kr service key for the 나라일터 and 알리오 job boards (optional).
286
+ *
287
+ * Pass the encoded key exactly as the portal supplies it. Both boards are read
288
+ * through open APIs rather than scraped; omit the key and they collect
289
+ * nothing while every other target is unaffected.
290
+ */
291
+ publicDataApiKey?: string;
292
+ /**
293
+ * LLM prompt overrides (optional).
294
+ *
295
+ * When provided, this replaces Research Radar's own prompt provider entirely
296
+ * rather than merging with it. Omit it to use the package's tuned prompts,
297
+ * which in turn fall back to core's defaults for any stage they do not define.
298
+ */
299
+ promptProvider?: PromptProvider;
229
300
  }
230
301
  /**
231
302
  * Newsletter generation execution function
@@ -419,6 +490,22 @@ declare class ContentGenerateProvider implements ContentGenerateProvider$1 {
419
490
  }>;
420
491
  }
421
492
 
493
+ type HeritageBidCandidate = {
494
+ /** Issuing and requesting institutions, joined. */
495
+ institution: string;
496
+ /** Notice title. */
497
+ title: string;
498
+ /** `pubPrcrmntMidClsfcNm`, when the notice carries one. */
499
+ classification?: string | null;
500
+ };
501
+
502
+ /**
503
+ * Decides which 나라장터 notices are worth scoring, one batch at a time.
504
+ *
505
+ * Returns one verdict per candidate, in the order they were given.
506
+ */
507
+ type HeritageBidTriage = (candidates: HeritageBidCandidate[], signal?: AbortSignal) => Promise<boolean[]>;
508
+
422
509
  /**
423
510
  * Crawling provider implementation
424
511
  * - Defines crawling targets
@@ -433,7 +520,7 @@ declare class CrawlingProvider implements CrawlingProvider$1 {
433
520
  customFetch?: typeof fetch;
434
521
  /** Crawling target groups configuration */
435
522
  crawlingTargetGroups: CrawlingTargetGroup[];
436
- constructor(articleRepository: ArticleRepository, customFetch?: typeof fetch);
523
+ constructor(articleRepository: ArticleRepository, customFetch?: typeof fetch, excavationReportSource?: ExcavationReportSource, logger?: AppLogger, publicDataApiKey?: string, bidTriage?: HeritageBidTriage);
437
524
  /**
438
525
  * Fetch existing articles by URLs to avoid duplicate crawling
439
526
  * @param articleUrls - URLs to check
@@ -489,6 +576,38 @@ declare const contentOptions: ContentOptions;
489
576
  * Newsletter brand configuration
490
577
  */
491
578
  declare const newsletterConfig: NewsletterConfig;
579
+ /**
580
+ * Maximum importance score per heritage domain (`tag1`).
581
+ *
582
+ * The newsletter is archaeology-first: archaeology and cultural heritage keep
583
+ * the full 1-10 range, while natural and intangible heritage are capped so they
584
+ * cannot crowd out archaeological coverage. `기타` — material that is not
585
+ * heritage at all — is capped lowest, because the scoring prompt's academic-value
586
+ * floor is domain-blind and would otherwise lift things like a general journal's
587
+ * call for papers into the top half of the scale.
588
+ *
589
+ * The cap is applied deterministically after scoring, in
590
+ * `AnalysisProvider.update()`, rather than asked for in the prompt, so the
591
+ * ceiling always holds. It never reaches 1: a score of 1 means "exclude from the
592
+ * newsletter" in the consuming application's candidate query, so capping to 1
593
+ * would delete these articles instead of demoting them.
594
+ *
595
+ * Domains absent from this map are not capped.
596
+ */
597
+ declare const maximumImportanceScoreByDomain: Record<string, number>;
598
+ /**
599
+ * Origins exempted from the robots.txt check.
600
+ *
601
+ * Each entry deliberately overrides what the site publishes, so it needs a
602
+ * reason and should be revisited when that site's robots.txt changes.
603
+ *
604
+ * - `http://www.yngogo.or.kr` (영남고고학회): its board is rendered from
605
+ * `/module/ntt/unity/selectNttListAjax.ink`, and robots.txt carries a blanket
606
+ * `Disallow: /module`. The rule reads as protecting an internal path rather
607
+ * than the public board it happens to serve, and there is no other route to
608
+ * the listing, so the society's boards are collected under this exemption.
609
+ */
610
+ declare const robotsExemptOrigins: readonly string[];
492
611
  /**
493
612
  * LLM configuration
494
613
  */
@@ -500,5 +619,35 @@ declare const llmConfig: {
500
619
  };
501
620
  };
502
621
 
503
- export { AnalysisProvider, ContentGenerateProvider, CrawlingProvider, DateService, TaskService, contentOptions, createCrawlingTargetGroups, generateNewsletter, generateWelcomeHTML, getSourceList, llmConfig, newsletterConfig };
504
- export type { ArticleRepository, ContentGenerationConfig, ContentOptions, NewsletterConfig, NewsletterGeneratorDependencies, NewsletterRepository, NewsletterTemplateOptions, PreviewNewsletterOptions, SourceGroup, SourceItem, TagRepository, TaskRepository, WelcomeTemplateOptions };
622
+ /**
623
+ * Research Radar's LLM prompt overrides.
624
+ *
625
+ * Each builder here **replaces** core's built-in prompt for that stage rather
626
+ * than extending it — a `PromptBuilder` returns the whole prompt string. That is
627
+ * deliberate: core's defaults instruct the model to use emoticons, render
628
+ * importance as star ratings, and add share-of-coverage statistics, all of which
629
+ * this newsletter removes. Appending contradicting rules to those defaults
630
+ * degrades the output, so a replacement is written from scratch instead.
631
+ *
632
+ * **Contract a replacement must still satisfy.** Core's output schema is fixed,
633
+ * and `generateNewsletter` regenerates the whole newsletter whenever the model
634
+ * reports a failure. A replacement prompt has to steer the model toward:
635
+ *
636
+ * - `title`: 20–70 characters
637
+ * - `isWrittenInOutputLanguage`, `copyrightVerified`, `factAccuracy`: true
638
+ * - `titleContext` (KRAS mode): the phrase must appear in the title
639
+ *
640
+ * Core caps that loop at 5 attempts, so a prompt that ignores the contract costs
641
+ * up to 5 full generations on the most expensive model in the pipeline.
642
+ *
643
+ * Tune these against real articles with core's playground
644
+ * (`npm run playground:generate-newsletter` in ../llm-newsletter-kit-core) and
645
+ * diff them against the defaults for free with `playground:verify-prompts`.
646
+ * The playground loads this module from `dist`, so run `npm run build` here
647
+ * after every edit — its source cannot be imported directly across repos
648
+ * because of the `~/*` path alias.
649
+ */
650
+ declare const researchRadarPromptProvider: PromptProvider;
651
+
652
+ export { AnalysisProvider, ContentGenerateProvider, CrawlingProvider, DateService, TaskService, contentOptions, createCrawlingTargetGroups, generateNewsletter, generateWelcomeHTML, getSourceList, llmConfig, maximumImportanceScoreByDomain, newsletterConfig, researchRadarPromptProvider, robotsExemptOrigins };
653
+ export type { ArticleRepository, ContentGenerationConfig, ContentOptions, ExcavationReport, ExcavationReportSource, NewsletterConfig, NewsletterGeneratorDependencies, NewsletterRepository, NewsletterTemplateOptions, PreviewNewsletterOptions, SourceGroup, SourceItem, TagRepository, TaskRepository, WelcomeTemplateOptions };