@heripo/research-radar 5.0.4 → 5.0.6
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 +210 -90
- package/dist/index.js +13 -31
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,15 +15,15 @@ English | [한국어](./README-ko.md)
|
|
|
15
15
|
|
|
16
16
|
An AI-powered newsletter service for Korean cultural heritage. Built on [`@llm-newsletter-kit/core`](https://github.com/heripo-lab/llm-newsletter-kit-core), it's both a production service ([live at heripo.app](https://heripo.app/research-radar/subscribe)) and a reference implementation showing how to build automated newsletters with LLMs.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **Engagement**: 15% CTR
|
|
18
|
+
This package provides crawling, analysis, content generation, and email templates. The hosted service supplies database repositories, scheduling, subscriber management, and email delivery separately.
|
|
19
|
+
|
|
20
|
+
Previously reported service metrics were $0.2–1 per issue and 15% CTR. These are historical figures, not benchmarks for the current models or deployment.
|
|
22
21
|
|
|
23
22
|
**Technical highlights**:
|
|
23
|
+
|
|
24
24
|
- Type-safe TypeScript with strict interfaces
|
|
25
25
|
- Provider pattern for swapping components (Crawling/Analysis/Content/Email)
|
|
26
|
-
-
|
|
26
|
+
- 59 active crawling targets across heritage agencies, museums, academic societies
|
|
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
|
|
|
@@ -33,7 +33,7 @@ An AI-powered newsletter service for Korean cultural heritage. Built on [`@llm-n
|
|
|
33
33
|
|
|
34
34
|
Created by archaeologist-turned-engineer Hongyeon Kim to answer: "Why must research rely on labor-intensive manual work?"
|
|
35
35
|
|
|
36
|
-
A personal script evolved into a production service after completing research on [Archaeological Informatization Using LLMs](https://poc.heripo.org). This repository open-sources the running service so developers can build
|
|
36
|
+
A personal script evolved into a production service after completing research on [Archaeological Informatization Using LLMs](https://poc.heripo.org). This repository open-sources the running service so developers can build
|
|
37
37
|
domain-specific newsletters without starting from scratch.
|
|
38
38
|
|
|
39
39
|
## License
|
|
@@ -67,63 +67,86 @@ For academic publications:
|
|
|
67
67
|
## Installation
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
npm install @heripo/research-radar @llm-newsletter-kit/core
|
|
70
|
+
npm install @heripo/research-radar '@llm-newsletter-kit/core@~3.0.0'
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
**Requirements**: Node.js >= 24
|
|
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`.
|
|
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
|
|
|
77
77
|
## Quick Start
|
|
78
78
|
|
|
79
|
+
Implement the four repository interfaces for your storage layer, then call this application-level wrapper. The repositories in this example are supplied by the caller; no database adapter is bundled.
|
|
80
|
+
|
|
79
81
|
```typescript
|
|
80
|
-
import {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
import {
|
|
83
|
+
type ArticleRepository,
|
|
84
|
+
type NewsletterRepository,
|
|
85
|
+
type TagRepository,
|
|
86
|
+
type TaskRepository,
|
|
87
|
+
generateNewsletter,
|
|
88
|
+
} from '@heripo/research-radar';
|
|
89
|
+
|
|
90
|
+
export async function runNewsletter(repositories: {
|
|
91
|
+
taskRepository: TaskRepository;
|
|
92
|
+
articleRepository: ArticleRepository;
|
|
93
|
+
tagRepository: TagRepository;
|
|
94
|
+
newsletterRepository: NewsletterRepository;
|
|
95
|
+
}) {
|
|
96
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
97
|
+
if (!apiKey) throw new Error('OPENAI_API_KEY is required');
|
|
98
|
+
|
|
99
|
+
const newsletterId = await generateNewsletter({
|
|
100
|
+
...repositories,
|
|
101
|
+
openAIApiKey: apiKey,
|
|
102
|
+
contentGeneration: { provider: 'openai', apiKey },
|
|
103
|
+
logger: console,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (newsletterId === null) {
|
|
107
|
+
console.log('No newsletter was created for this run.');
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log('Saved newsletter:', newsletterId);
|
|
112
|
+
return newsletterId;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
89
115
|
|
|
90
|
-
|
|
91
|
-
taskRepository: {
|
|
92
|
-
createTask: async () => db.tasks.create({ status: 'running' }),
|
|
93
|
-
completeTask: async (id) => db.tasks.update(id, { status: 'completed' }),
|
|
94
|
-
},
|
|
116
|
+
`generateNewsletter()` returns `Promise<string | number | null>`: the saved newsletter ID, or `null` when no issue is created (for example, when publication criteria are not met). Generation errors can reject the promise. The internal `createNewsletterGenerator()` factory is not exported.
|
|
95
117
|
|
|
96
|
-
|
|
97
|
-
findByUrls: async (urls) => db.articles.findByUrls(urls),
|
|
98
|
-
saveCrawledArticles: async (articles, ctx) => db.articles.save(articles, ctx),
|
|
99
|
-
findUnscoredArticles: async () => db.articles.findUnscored(),
|
|
100
|
-
updateAnalysis: async (article) => db.articles.updateAnalysis(article),
|
|
101
|
-
findCandidatesForNewsletter: async () => db.articles.findCandidates(),
|
|
102
|
-
},
|
|
118
|
+
### Repository contracts
|
|
103
119
|
|
|
104
|
-
|
|
105
|
-
findAllTags: async () => db.tags.findAll(),
|
|
106
|
-
},
|
|
120
|
+
All interfaces are exported from the package root and defined in [src/types/dependencies.ts](./src/types/dependencies.ts). Article and newsletter data types come from `@llm-newsletter-kit/core`.
|
|
107
121
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
| Repository | Method | Required result |
|
|
123
|
+
| ---------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
124
|
+
| `TaskRepository` | `createTask()` | `Promise<number>` — task ID, not a task record |
|
|
125
|
+
| | `completeTask(taskId)` | `Promise<void>` |
|
|
126
|
+
| `ArticleRepository` | `findByUrls(urls)` | `Promise<ParsedTarget[]>` for deduplication |
|
|
127
|
+
| | `saveCrawledArticles(articles, context)` | `Promise<number>` — saved count; preserve task, target group, and target context |
|
|
128
|
+
| | `findUnscoredArticles()` | `Promise<UnscoredArticle[]>` |
|
|
129
|
+
| | `updateAnalysis(article)` | `Promise<void>` |
|
|
130
|
+
| | `findCandidatesForNewsletter()` | `Promise<ArticleForGenerateContent[]>` |
|
|
131
|
+
| `TagRepository` | `findAllTags()` | `Promise<string[]>` |
|
|
132
|
+
| `NewsletterRepository` | `getNextIssueOrder()` | `Promise<number>` |
|
|
133
|
+
| | `saveNewsletter({ newsletter, usedArticles })` | `Promise<{ id: string \| number }>` |
|
|
112
134
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
```
|
|
135
|
+
Candidate selection belongs to your repository. Persist `usedArticles` associations as needed to exclude previously published content. Issue numbering is initialized before the pipeline starts; coordinate concurrent runs in your application or database. `TaskService` tracks an active task within one service instance, not across processes or separate `generateNewsletter()` calls.
|
|
136
|
+
|
|
137
|
+
### Optional generation settings
|
|
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` |
|
|
125
146
|
|
|
126
|
-
|
|
147
|
+
`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
|
+
|
|
149
|
+
Production integration must also supply a scheduler, subscriber storage, bulk delivery, and unsubscribe handling. The daily parser health-check workflow does not generate or distribute newsletters.
|
|
127
150
|
|
|
128
151
|
## Architecture
|
|
129
152
|
|
|
@@ -136,41 +159,134 @@ const newsletterId = await generateNewsletter({
|
|
|
136
159
|
|
|
137
160
|
Uses the **Provider-Service pattern** from `@llm-newsletter-kit/core`. See [core docs](https://github.com/heripo-lab/llm-newsletter-kit-core#architecture--flow) for flow diagrams.
|
|
138
161
|
|
|
139
|
-
##
|
|
162
|
+
## Configuration and models
|
|
163
|
+
|
|
164
|
+
Defaults below describe the checked-in code, not provider recommendations. This package uses AI SDK 7 and the OpenAI, Anthropic, and Google SDK adapters.
|
|
165
|
+
|
|
166
|
+
| Stage | Provider | Default model |
|
|
167
|
+
| ------------------ | --------- | ------------------------ |
|
|
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` |
|
|
172
|
+
| Content generation | Anthropic | `claude-sonnet-4-6` |
|
|
173
|
+
| Content generation | Google | `gemini-3.1-pro-preview` |
|
|
174
|
+
|
|
175
|
+
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
|
+
|
|
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.
|
|
178
|
+
|
|
179
|
+
## Crawling targets and parsers
|
|
180
|
+
|
|
181
|
+
[src/config/crawling-targets.ts](./src/config/crawling-targets.ts) currently defines these board targets (multiple boards may belong to one organization):
|
|
182
|
+
|
|
183
|
+
| Group | Active | Commented out |
|
|
184
|
+
| ---------- | -----: | ------------: |
|
|
185
|
+
| News | 48 | 10 |
|
|
186
|
+
| Business | 4 | 0 |
|
|
187
|
+
| Employment | 7 | 3 |
|
|
188
|
+
| **Total** | **59** | **13** |
|
|
189
|
+
|
|
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.
|
|
191
|
+
|
|
192
|
+
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
|
+
|
|
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.
|
|
195
|
+
|
|
196
|
+
`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
|
+
|
|
198
|
+
To display the active sources without crawling:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { getSourceList } from '@heripo/research-radar';
|
|
202
|
+
|
|
203
|
+
const groups = getSourceList(); // [{ id, name, sources: [{ id, name, url }] }]
|
|
204
|
+
```
|
|
205
|
+
|
|
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).
|
|
207
|
+
|
|
208
|
+
## Email templates
|
|
140
209
|
|
|
141
|
-
|
|
210
|
+
[src/templates/](./src/templates/) contains the responsive newsletter template, welcome email template, and shared logos, introduction, sanitization, and footer components. Templates include light/dark mode styles and heripo/KRAS variants. Markdown sections are converted with `safe-markdown2html`; welcome email names are sanitized with DOMPurify and CSS is inlined with `juice`.
|
|
142
211
|
|
|
143
|
-
|
|
212
|
+
`NewsletterTemplateOptions` supports:
|
|
144
213
|
|
|
145
|
-
|
|
214
|
+
| Option | Purpose |
|
|
215
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
216
|
+
| `isKrasNewsletter` | Enable KRAS branding, the 50th-anniversary header, archaeology-first expertise, free-form introduction, and the brand name `한국고고학회 뉴스레터` |
|
|
217
|
+
| `krasNewsMarkdown` | Society news before generated content |
|
|
218
|
+
| `heripolabNewsMarkdown` | heripo lab news before generated content |
|
|
219
|
+
| `krasNoticeMarkdown` | Society notices after generated content |
|
|
220
|
+
| `titleContext` | Title-generation context, only when `isKrasNewsletter: true`; an empty string is ignored |
|
|
221
|
+
| `displayDate` | Header date; `generateNewsletter()` replaces it with the date resolved by `DateService` when template options are supplied |
|
|
146
222
|
|
|
147
|
-
|
|
223
|
+
The three Markdown sections are available in either branding mode.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import {
|
|
227
|
+
type NewsletterTemplateOptions,
|
|
228
|
+
generateWelcomeHTML,
|
|
229
|
+
} from '@heripo/research-radar';
|
|
230
|
+
|
|
231
|
+
const templateOptions: NewsletterTemplateOptions = {
|
|
232
|
+
isKrasNewsletter: true,
|
|
233
|
+
titleContext: '한국고고학전국대회',
|
|
234
|
+
krasNewsMarkdown: '학회 소식을 입력하세요.',
|
|
235
|
+
krasNoticeMarkdown: '학회 공지사항을 입력하세요.',
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const welcomeHtml = await generateWelcomeHTML('subscriber-id', '홍길동', {
|
|
239
|
+
isKrasNewsletter: true,
|
|
240
|
+
siteUrl: 'https://heripo.app',
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`generateWelcomeHTML(id, name, options?)` returns `Promise<string>` and only renders HTML. `siteUrl` defaults to `https://heripo.app`; unsubscribe links use `/research-radar/unsubscribe?id=...`. Newsletter templates retain Resend's `{{{RESEND_UNSUBSCRIBE_URL}}}` marker, which your delivery integration must resolve or replace. The raw newsletter template factory and shared HTML helpers are internal, not package-root exports.
|
|
148
245
|
|
|
149
246
|
## Development commands
|
|
150
247
|
|
|
151
248
|
```bash
|
|
249
|
+
# Install development dependencies from the lockfile
|
|
250
|
+
npm ci
|
|
251
|
+
|
|
152
252
|
# build
|
|
253
|
+
npm run clean # remove dist/
|
|
153
254
|
npm run build # clean dist/ and build with Rollup (ESM + types)
|
|
154
255
|
|
|
155
256
|
# type-check & lint
|
|
156
257
|
npm run lint # lint source files
|
|
157
258
|
npm run lint:fix # lint with autofix
|
|
259
|
+
npm run lint:ci # quiet CI lint
|
|
158
260
|
npm run typecheck # TypeScript type-check
|
|
159
261
|
|
|
160
262
|
# formatting
|
|
161
|
-
npm run format # Prettier
|
|
263
|
+
npm run format # format src/ with Prettier
|
|
264
|
+
npm run format:check # check src/ formatting
|
|
162
265
|
```
|
|
163
266
|
|
|
267
|
+
The build emits ESM, declarations, and a JavaScript sourcemap; runtime dependencies remain external. CI runs `npm ci`, `format:check`, `lint:ci`, `typecheck`, and `build` on Node.js 24.x for pull requests and manual dispatch. Formatting scripts cover `src/`; to check the READMEs explicitly, run `npx prettier --check README.md README-ko.md`. There is no `npm test` script.
|
|
268
|
+
|
|
269
|
+
For maintainers, `release` publishes to npm, while `release:patch`, `release:minor`, and `release:major` bump the version and publish. The version hooks build first and push commits/tags; `prepublishOnly` builds before publishing.
|
|
270
|
+
|
|
164
271
|
### Crawler Debugger
|
|
165
272
|
|
|
166
273
|
A web-based tool for testing crawling parsers during development. Built with Express.js and vanilla HTML/CSS/JS to minimize dependencies.
|
|
167
274
|
|
|
168
275
|
```bash
|
|
169
276
|
npm run dev:crawler # Start at http://localhost:3333
|
|
170
|
-
npm run dev:crawler:proxy #
|
|
277
|
+
npm run dev:crawler:proxy # Load dev-tools/crawler-debugger/.env
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
For proxy use, create the gitignored `dev-tools/crawler-debugger/.env` file with your proxy endpoint:
|
|
281
|
+
|
|
282
|
+
```dotenv
|
|
283
|
+
PROXY_URL=http://127.0.0.1:8080
|
|
171
284
|
```
|
|
172
285
|
|
|
286
|
+
The library itself receives a `customFetch` function; it does not read `PROXY_URL`.
|
|
287
|
+
|
|
173
288
|
**Features**:
|
|
289
|
+
|
|
174
290
|
- Test `parseList()` and `parseDetail()` parsers via web UI
|
|
175
291
|
- View raw HTML source for debugging
|
|
176
292
|
- Copy parsed results as JSON
|
|
@@ -185,7 +301,7 @@ Preview rendered newsletter HTML with sample content.
|
|
|
185
301
|
npm run dev:newsletter-preview # Start at http://localhost:3334
|
|
186
302
|
```
|
|
187
303
|
|
|
188
|
-
|
|
304
|
+
Use the UI controls, or open `/api/preview?kras=true&krasNews=true&krasNotice=true&heripolabNews=true` on port 3334. These query parameters belong to `/api/preview`, not the root UI URL. Each section switch includes sample Markdown; this preview does not call an LLM.
|
|
189
305
|
|
|
190
306
|
### Welcome Email Preview
|
|
191
307
|
|
|
@@ -195,7 +311,7 @@ Preview rendered welcome email HTML.
|
|
|
195
311
|
npm run dev:welcome-preview # Start at http://localhost:3335
|
|
196
312
|
```
|
|
197
313
|
|
|
198
|
-
|
|
314
|
+
Use the UI controls, or open `/api/preview?kras=true&name=홍길동` on port 3335. No API key is needed for either HTML preview tool.
|
|
199
315
|
|
|
200
316
|
### Parser Health-Check
|
|
201
317
|
|
|
@@ -203,19 +319,20 @@ CLI tool that validates all active crawling parsers against live websites. Detec
|
|
|
203
319
|
|
|
204
320
|
```bash
|
|
205
321
|
npm run health-check # Run health-check
|
|
206
|
-
npm run health-check:proxy #
|
|
322
|
+
npm run health-check:proxy # Load the same .env and pass --proxy
|
|
207
323
|
npm run health-check -- --skip-khs-excavation # Skip KHS excavation report/site-open targets
|
|
208
324
|
```
|
|
209
325
|
|
|
210
326
|
**What it checks per target**:
|
|
211
|
-
- `parseList()`: Returns non-empty array with valid title, date, and detailUrl
|
|
212
|
-
- `parseDetail()`: Returns non-empty detailContent (20+ chars)
|
|
213
327
|
|
|
214
|
-
|
|
328
|
+
- `parseList()`: Non-empty array; checks the first item for non-empty title/date and a detailUrl starting with `http`
|
|
329
|
+
- `parseDetail()`: Fetches the first detail item and checks trimmed `detailContent` length >= 20
|
|
330
|
+
|
|
331
|
+
Use repeatable `--skip-target=<id-or-name>` or `--skip-target <id-or-name>` to exclude targets by exact ID or name. `npm run health-check -- --help` lists options without crawling. This is a live-site smoke check of the first item per target, not full article validation; it does not invoke LLM analysis. The script exits with code 1 if any checked target fails.
|
|
215
332
|
|
|
216
333
|
**Output**: Console table summary + compact text summary for CI integrations.
|
|
217
334
|
|
|
218
|
-
**CI**:
|
|
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.
|
|
219
336
|
|
|
220
337
|
## 🤝 Contributing
|
|
221
338
|
|
|
@@ -230,55 +347,58 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for contribution workflow, dev setup, a
|
|
|
230
347
|
|
|
231
348
|
To build your own newsletter, update these files:
|
|
232
349
|
|
|
233
|
-
**1.
|
|
350
|
+
**1. Templates** (`src/templates/newsletter-html.ts`, `welcome-html.ts`, `shared.ts`):
|
|
351
|
+
|
|
234
352
|
- Logo URLs, brand colors (#D2691E, #E59866), contact info
|
|
235
353
|
- Platform intro and footer text
|
|
236
354
|
- Unsubscribe link format (currently Resend's `{{{RESEND_UNSUBSCRIBE_URL}}}`)
|
|
237
355
|
|
|
238
356
|
**2. Config** (`src/config/index.ts`):
|
|
357
|
+
|
|
239
358
|
```typescript
|
|
240
|
-
|
|
241
|
-
|
|
359
|
+
import type { NewsletterConfig } from '@heripo/research-radar';
|
|
360
|
+
|
|
361
|
+
export const newsletterConfig: NewsletterConfig = {
|
|
362
|
+
brandName: 'Your Newsletter Name',
|
|
363
|
+
subscribePageUrl: 'https://yourdomain.com/subscribe',
|
|
364
|
+
publicationCriteria: {
|
|
365
|
+
minimumArticleCountForIssue: 5,
|
|
366
|
+
priorityArticleScoreThreshold: 8,
|
|
367
|
+
},
|
|
368
|
+
};
|
|
242
369
|
```
|
|
243
370
|
|
|
244
371
|
**3. Crawling targets** (`src/config/crawling-targets.ts`):
|
|
372
|
+
|
|
245
373
|
- Replace Korean heritage sites with your domain sources
|
|
246
374
|
- Implement parsers in `src/parsers/`
|
|
247
375
|
|
|
248
376
|
**4. Switch content generation LLM provider** (optional):
|
|
249
377
|
|
|
250
378
|
Content generation supports **3 built-in providers** — just change `contentGeneration.provider`:
|
|
379
|
+
|
|
251
380
|
```typescript
|
|
252
|
-
|
|
253
|
-
provider: 'google', // 'openai' | 'anthropic' | 'google'
|
|
254
|
-
apiKey: process.env.GOOGLE_API_KEY,
|
|
255
|
-
model: 'gemini-3.1-pro-preview', // optional, each provider has a default
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
Default models: openai=`gpt-5.1`, anthropic=`claude-sonnet-4-6`, google=`gemini-3.1-pro-preview`
|
|
381
|
+
import type { ContentGenerationConfig } from '@heripo/research-radar';
|
|
259
382
|
|
|
260
|
-
|
|
383
|
+
const apiKey = process.env.GOOGLE_API_KEY;
|
|
384
|
+
if (!apiKey) throw new Error('GOOGLE_API_KEY is required');
|
|
261
385
|
|
|
262
|
-
|
|
386
|
+
const contentGeneration: ContentGenerationConfig = {
|
|
387
|
+
provider: 'google',
|
|
388
|
+
apiKey,
|
|
389
|
+
model: 'gemini-3.1-pro-preview',
|
|
390
|
+
};
|
|
391
|
+
```
|
|
263
392
|
|
|
264
|
-
|
|
393
|
+
Default models: openai=`gpt-5.4`, anthropic=`claude-sonnet-4-6`, google=`gemini-3.1-pro-preview`
|
|
265
394
|
|
|
266
|
-
|
|
395
|
+
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.
|
|
267
396
|
|
|
268
|
-
**
|
|
269
|
-
**This kit**: Self-reflection, chain-of-thought, multi-step verification workflows
|
|
397
|
+
**Search keywords**: `heripo`, `kimhongyeon`, `#D2691E`, `openai`, `gpt-5`, `contentGeneration`
|
|
270
398
|
|
|
271
|
-
|
|
272
|
-
- **Quality**: Sophisticated prompting strategies, custom validation pipelines
|
|
273
|
-
- **Cost control**: Different models per step, token limits, retry logic
|
|
274
|
-
- **Flexibility**: Swap any component (Crawling/Analysis/Content/Email) via Provider interfaces
|
|
275
|
-
- **Operations**: Built-in retries, preview emails, integrates with CI/CD
|
|
276
|
-
- **No lock-in**: OSS, self-hostable, any LLM provider
|
|
399
|
+
## Why Code-Based?
|
|
277
400
|
|
|
278
|
-
|
|
279
|
-
- Logic in code (orchestration, deduplication)
|
|
280
|
-
- Reasoning in AI (analysis, scoring, content generation)
|
|
281
|
-
- Connections in architecture (swappable Providers)
|
|
401
|
+
The domain logic is inspectable in source: parser behavior, model selection, score rules, publication settings, and HTML templates can be versioned together. Repository and provider interfaces let applications integrate existing storage and delivery systems, while the core engine supplies orchestration and retry handling.
|
|
282
402
|
|
|
283
403
|
## Related Projects
|
|
284
404
|
|
package/dist/index.js
CHANGED
|
@@ -2013,18 +2013,14 @@ const heripoLogoHtml = (imgMarginBottom) => `
|
|
|
2013
2013
|
<!--<![endif]-->
|
|
2014
2014
|
</div>`;
|
|
2015
2015
|
/**
|
|
2016
|
-
* Heripo
|
|
2016
|
+
* Heripo project introduction section.
|
|
2017
2017
|
* Shared between newsletter and welcome email templates.
|
|
2018
2018
|
*
|
|
2019
|
-
* Note: Each template may append its own additional paragraph after this block
|
|
2020
|
-
* (e.g., newsletter adds a line about source requests via GitHub Issues).
|
|
2021
2019
|
*/
|
|
2022
2020
|
const platformIntroHtml = () => `
|
|
2023
|
-
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">heripo는
|
|
2024
|
-
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;"
|
|
2025
|
-
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7;
|
|
2026
|
-
color: #444444; margin: 0 0 18px 0;">현재는 소프트웨어 엔지니어와 고고학 연구자가 함께하는 <strong><a href="https://github.com/heripo-lab" target="_blank">heripo lab</a></strong>으로 운영 중이며, 2026년 1월 28일 핵심 엔진을 <strong><a href="https://github.com/heripo-lab/heripo-engine" target="_blank">오픈소스로 공개</a></strong>했습니다.</p>
|
|
2027
|
-
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">오픈소스로 공개된 핵심 기능은 <strong><a href="https://engine-demo.heripo.org" target="_blank">데모 사이트</a></strong>에서 직접 체험해 보실 수 있으며, 플랫폼 프로토타입 출시 시 구독자분들께 우선 안내해 드리겠습니다.</p>`;
|
|
2021
|
+
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">heripo는 발굴조사보고서의 기록을 데이터로 전환하고 출처와 변환 경로를 함께 보존하기 위한 연구 인프라 프로젝트입니다. 지금 받아보시는 뉴스레터는 그 출발점이며, 관련 오픈소스 프로젝트는 <strong><a href="https://github.com/heripo-lab" target="_blank">heripo lab</a></strong>에서 공개하고 있습니다.</p>
|
|
2022
|
+
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">뉴스레터에 이어 오픈소스 <strong><a href="https://github.com/heripo-lab/heripo-engine" target="_blank">heripo engine</a></strong>을 바탕으로 발굴조사보고서의 기록을 데이터로 전환하고 이를 탐색하고 활용할 수 있는 연구 도구 <strong>heripo 베이스캠프</strong>를 준비 중입니다.</p>
|
|
2023
|
+
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">heripo 베이스캠프는 현재 초대 기반 비공개 알파 테스트 중입니다. 공개 테스터 모집 및 정식 출시 소식은 이 뉴스레터로 전해드리겠습니다.</p>`;
|
|
2028
2024
|
/**
|
|
2029
2025
|
* "Powered by LLM Newsletter Kit · View Source" footer line.
|
|
2030
2026
|
*/
|
|
@@ -2531,7 +2527,7 @@ ${options.heripolabNewsMarkdown}
|
|
|
2531
2527
|
: ''}
|
|
2532
2528
|
|
|
2533
2529
|
<h2 style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; font-weight: bold; line-height: 1.3; color: #D2691E; margin: 0 0 16px 0; letter-spacing: -0.2px; border-left: 5px solid #D2691E; padding-left: 12px; background: none;">🔍 뉴스레터 출처</h2>
|
|
2534
|
-
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;"
|
|
2530
|
+
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">이 뉴스레터가 수집하는 모든 소식의 출처는 <a href="https://heripo.app/research-radar/sources" target="_blank">이곳에서</a> 확인할 수 있습니다. 새롭게 포함했으면 하는 출처나 오류가 있다면 <a href="https://github.com/heripo-lab/heripo-research-radar/issues" target="_blank">GitHub 이슈</a>로 알려주세요.</p>
|
|
2535
2531
|
<hr style="border: 0; border-top: 2px solid #D2691E; margin: 32px 0;">
|
|
2536
2532
|
<h2 style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; font-weight: bold; line-height: 1.3; color: #D2691E; margin: 0 0 16px 0; letter-spacing: -0.2px; border-left: 5px solid #D2691E; padding-left: 12px; background: none;">📅 발행 정책</h2>
|
|
2537
2533
|
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;"><strong>${options?.isKrasNewsletter ? '한국고고학회 뉴스레터' : 'heripo 리서치 레이더'}</strong>는 매일 발행을 원칙으로 하되, 독자분들께 의미 있는 정보를 제공하기 위해 다음과 같은 발행 기준을 적용합니다:</p>
|
|
@@ -2542,10 +2538,8 @@ ${options.heripolabNewsMarkdown}
|
|
|
2542
2538
|
</ul>
|
|
2543
2539
|
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">이러한 정책을 통해 매일 의미 없는 소식으로 독자분들의 시간을 낭비하지 않고, 정말 중요한 정보를 적절한 타이밍에 제공하고자 합니다.</p>
|
|
2544
2540
|
<hr style="border: 0; border-top: 2px solid #D2691E; margin: 32px 0;">
|
|
2545
|
-
<h2 style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; font-weight: bold; line-height: 1.3; color: #D2691E; margin: 0 0 16px 0; letter-spacing: -0.2px; border-left: 5px solid #D2691E; padding-left: 12px; background: none;">🔍 heripo
|
|
2541
|
+
<h2 style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; font-weight: bold; line-height: 1.3; color: #D2691E; margin: 0 0 16px 0; letter-spacing: -0.2px; border-left: 5px solid #D2691E; padding-left: 12px; background: none;">🔍 heripo 프로젝트 소개</h2>
|
|
2546
2542
|
${platformIntroHtml()}
|
|
2547
|
-
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7;
|
|
2548
|
-
color: #444444; margin: 0 0 18px 0;">보고 계신 뉴스레터(리서치 레이더)는 heripo의 초기 선행 기능 중 하나입니다. 뉴스레터 소스 추가 요청은 <a href="https://github.com/heripo-lab/heripo-research-radar/issues" target="_blank">GitHub 이슈</a>를 통해 언제든 환영합니다.</p>
|
|
2549
2543
|
<hr style="border: 0; border-top: 2px solid #D2691E; margin: 32px 0;">
|
|
2550
2544
|
<h2 style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; font-weight: bold; line-height: 1.3; color: #D2691E; margin: 0 0 16px 0; letter-spacing: -0.2px; border-left: 5px solid #D2691E; padding-left: 12px; background: none;">⚠️ 중요 안내</h2>
|
|
2551
2545
|
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">본 뉴스레터는 국가유산청 공지사항, 관련 기관 입찰 정보 등 특정 웹 게시판의 모든 신규 소식을 빠짐없이 수집하여 제공합니다. 수집된 모든 정보는 정확한 크롤링 로직에 기반하므로 원본과 일치하여 신뢰할 수 있습니다.</p>
|
|
@@ -2936,7 +2930,7 @@ async function generateWelcomeHTML(id, name, options) {
|
|
|
2936
2930
|
function createWelcomeHtmlRaw(name, isKras, siteUrl, unsubscribeUrl) {
|
|
2937
2931
|
const title = isKras
|
|
2938
2932
|
? '한국고고학회 뉴스레터 구독 완료'
|
|
2939
|
-
: 'heripo
|
|
2933
|
+
: 'heripo 뉴스레터 구독 완료';
|
|
2940
2934
|
const headerHtml = isKras
|
|
2941
2935
|
? `<!-- KRAS 50주년 헤더 -->
|
|
2942
2936
|
<div style="text-align: center; margin-bottom: 36px;">
|
|
@@ -2947,12 +2941,7 @@ function createWelcomeHtmlRaw(name, isKras, siteUrl, unsubscribeUrl) {
|
|
|
2947
2941
|
: `${heripoLogoHtml('8px')}
|
|
2948
2942
|
|
|
2949
2943
|
<h1 style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; line-height: 1.2; margin: 0 0
|
|
2950
|
-
18px 0; letter-spacing: -0.5px; margin-top: 0; font-size: 32px; font-weight: bold; color: #111111; border-bottom: 3px solid #D2691E; padding-bottom: 8px;">${name}님,
|
|
2951
|
-
const feedbackHeading = `${name}님의 목소리가 heripo의 미래를 만듭니다`;
|
|
2952
|
-
const feedbackText = 'heripo';
|
|
2953
|
-
const newsletterLine = isKras
|
|
2954
|
-
? `<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">뉴스레터(리서치 레이더)는 heripo의 초기 선행 기능 중 하나입니다. 뉴스레터 소스 추가 요청은 <a href="https://github.com/heripo-lab/heripo-research-radar/issues" target="_blank">GitHub 이슈</a>를 통해 언제든 환영합니다.</p>`
|
|
2955
|
-
: `<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">뉴스레터(리서치 레이더)는 heripo의 초기 선행 기능 중 하나입니다. 뉴스레터 소스 추가 요청은 <a href="https://github.com/heripo-lab/heripo-research-radar/issues" target="_blank">GitHub 이슈</a>를 통해 언제든 환영합니다.</p>`;
|
|
2944
|
+
18px 0; letter-spacing: -0.5px; margin-top: 0; font-size: 32px; font-weight: bold; color: #111111; border-bottom: 3px solid #D2691E; padding-bottom: 8px;">${name}님, 환영합니다.</h1>`;
|
|
2956
2945
|
const warningHtml = isKras
|
|
2957
2946
|
? `
|
|
2958
2947
|
<blockquote style="background-color: #fef2f2; border-left: 5px solid #dc2626; margin: 24px 0; padding: 20px; border-radius: 4px;">
|
|
@@ -2968,7 +2957,7 @@ function createWelcomeHtmlRaw(name, isKras, siteUrl, unsubscribeUrl) {
|
|
|
2968
2957
|
</blockquote>`;
|
|
2969
2958
|
const footerDisclaimerText = isKras
|
|
2970
2959
|
? '이 이메일은 heripo.app에서 한국고고학회 뉴스레터를 구독하신 분들에게 발송됩니다.'
|
|
2971
|
-
: '이 이메일은 heripo.app에서
|
|
2960
|
+
: '이 이메일은 heripo.app에서 heripo 뉴스레터를 구독하신 분들에게 발송됩니다.';
|
|
2972
2961
|
const footerUnsubscribeHtml = isKras
|
|
2973
2962
|
? `<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.7; color: #6b7280; margin: 0 0 18px 0; margin-bottom: 8px;">📱 구독 관리: <a href="${unsubscribeUrl}" class="footer-link">구독 해지</a></p>`
|
|
2974
2963
|
: `<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.7; color: #6b7280; margin: 0 0 18px 0; margin-bottom: 8px;">📱 구독 관리: <a href="${unsubscribeUrl}" class="footer-link">구독 해지</a></p>`;
|
|
@@ -3124,23 +3113,16 @@ function createWelcomeHtmlRaw(name, isKras, siteUrl, unsubscribeUrl) {
|
|
|
3124
3113
|
<td bgcolor="#ffffff" align="left" class="content-cell dark-mode-content-bg${isKras ? ' kras-newsletter' : ''}" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding: 48px 44px 44px 44px; border-radius: 12px; box-shadow: 0 4px 18px rgba(0,0,0,0.07);">
|
|
3125
3114
|
${headerHtml}
|
|
3126
3115
|
|
|
3127
|
-
<h2 style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; font-weight: bold; line-height: 1.3; color: #D2691E; margin: 0 0 15px 0; letter-spacing: -0.2px; border-left: 5px solid #D2691E; padding-left: 12px; background: #fff7f2;"
|
|
3116
|
+
<h2 style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; font-weight: bold; line-height: 1.3; color: #D2691E; margin: 0 0 15px 0; letter-spacing: -0.2px; border-left: 5px solid #D2691E; padding-left: 12px; background: #fff7f2;">📬 뉴스레터 구독이 완료되었습니다</h2>
|
|
3128
3117
|
|
|
3129
|
-
|
|
3118
|
+
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">${name}님, 구독해주셔서 감사합니다. 고고학과 문화유산의 중요한 소식을 뉴스레터로 전해드리겠습니다.</p>
|
|
3130
3119
|
|
|
3131
|
-
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;"
|
|
3120
|
+
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">놓치고 싶지 않은 소식이나 개선 의견이 있다면 언제든 <a href="${siteUrl}/contact">문의하기</a>로 알려주세요.</p>
|
|
3132
3121
|
|
|
3133
|
-
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;">여러분의 피드백 하나하나가 ${feedbackText}의 다음 발걸음을 결정합니다.</p>
|
|
3134
|
-
${isKras
|
|
3135
|
-
? `
|
|
3136
|
-
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #444444; margin: 0 0 18px 0;"><strong><a href="https://github.com/heripo-lab" target="_blank">heripo lab</a></strong>은 한국고고학회와 함께 뉴스레터 발행 및 고고학의 디지털 전환을 추진하고 있습니다. 앞으로도 연구 현장에 실질적으로 도움이 되는 정보와 기술을 제공해 드리겠습니다.</p>
|
|
3137
|
-
`
|
|
3138
|
-
: ''}
|
|
3139
3122
|
<hr style="border: 0; border-top: 1px solid #e5e7eb; margin: 28px 0 20px;">
|
|
3140
3123
|
|
|
3141
|
-
<h2 style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; font-weight: bold; line-height: 1.3; color: #D2691E; margin: 0 0 15px 0; letter-spacing: -0.2px; border-left: 5px solid #D2691E; padding-left: 12px; background: #fff7f2;">🔍 heripo
|
|
3124
|
+
<h2 style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; font-weight: bold; line-height: 1.3; color: #D2691E; margin: 0 0 15px 0; letter-spacing: -0.2px; border-left: 5px solid #D2691E; padding-left: 12px; background: #fff7f2;">🔍 heripo 프로젝트 소개</h2>
|
|
3142
3125
|
${platformIntroHtml()}
|
|
3143
|
-
${newsletterLine}
|
|
3144
3126
|
${warningHtml}
|
|
3145
3127
|
|
|
3146
3128
|
<hr style="border: 0; border-top: 1px solid #e5e7eb; margin: 32px 0;">
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@heripo/research-radar",
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "5.0.
|
|
5
|
+
"version": "5.0.6",
|
|
6
6
|
"description": "AI-driven intelligence for Korean cultural heritage. This package serves as both a ready-to-use newsletter service and a practical implementation example for the LLM-Newsletter-Kit.",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@eslint/js": "^10.0.1",
|
|
65
|
-
"@llm-newsletter-kit/core": "^3.0.
|
|
65
|
+
"@llm-newsletter-kit/core": "^3.0.4",
|
|
66
66
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
67
67
|
"@types/express": "^5.0.6",
|
|
68
68
|
"@types/jsdom": "^30.0.0",
|