alif-digest 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/.github/workflows/publish.yml +33 -0
  2. package/.husky/pre-commit +1 -0
  3. package/.prettierrc +7 -0
  4. package/LICENSE +21 -0
  5. package/README.md +131 -0
  6. package/dist/cli/commands/init.d.ts +1 -0
  7. package/dist/cli/commands/init.js +88 -0
  8. package/dist/cli/commands/init.js.map +1 -0
  9. package/dist/cli/commands/run.d.ts +4 -0
  10. package/dist/cli/commands/run.js +46 -0
  11. package/dist/cli/commands/run.js.map +1 -0
  12. package/dist/cli/commands/schedule.d.ts +1 -0
  13. package/dist/cli/commands/schedule.js +94 -0
  14. package/dist/cli/commands/schedule.js.map +1 -0
  15. package/dist/cli/index.d.ts +2 -0
  16. package/dist/cli/index.js +29 -0
  17. package/dist/cli/index.js.map +1 -0
  18. package/dist/core/config-manager.d.ts +14 -0
  19. package/dist/core/config-manager.js +65 -0
  20. package/dist/core/config-manager.js.map +1 -0
  21. package/dist/core/config-schema.d.ts +40 -0
  22. package/dist/core/config-schema.js +24 -0
  23. package/dist/core/config-schema.js.map +1 -0
  24. package/dist/core/default-keywords.d.ts +1 -0
  25. package/dist/core/default-keywords.js +10 -0
  26. package/dist/core/default-keywords.js.map +1 -0
  27. package/dist/core/filters/deduplicator.d.ts +10 -0
  28. package/dist/core/filters/deduplicator.js +34 -0
  29. package/dist/core/filters/deduplicator.js.map +1 -0
  30. package/dist/core/filters/keywords.d.ts +6 -0
  31. package/dist/core/filters/keywords.js +17 -0
  32. package/dist/core/filters/keywords.js.map +1 -0
  33. package/dist/core/orchestrator.d.ts +6 -0
  34. package/dist/core/orchestrator.js +44 -0
  35. package/dist/core/orchestrator.js.map +1 -0
  36. package/dist/core/pipeline.d.ts +15 -0
  37. package/dist/core/pipeline.js +140 -0
  38. package/dist/core/pipeline.js.map +1 -0
  39. package/dist/core/scheduler.d.ts +9 -0
  40. package/dist/core/scheduler.js +64 -0
  41. package/dist/core/scheduler.js.map +1 -0
  42. package/dist/core/scraper-types.d.ts +27 -0
  43. package/dist/core/scraper-types.js +3 -0
  44. package/dist/core/scraper-types.js.map +1 -0
  45. package/dist/core/scrapers/api-scraper.d.ts +4 -0
  46. package/dist/core/scrapers/api-scraper.js +46 -0
  47. package/dist/core/scrapers/api-scraper.js.map +1 -0
  48. package/dist/core/scrapers/arxiv-scraper.d.ts +4 -0
  49. package/dist/core/scrapers/arxiv-scraper.js +34 -0
  50. package/dist/core/scrapers/arxiv-scraper.js.map +1 -0
  51. package/dist/core/scrapers/json-scraper.d.ts +4 -0
  52. package/dist/core/scrapers/json-scraper.js +56 -0
  53. package/dist/core/scrapers/json-scraper.js.map +1 -0
  54. package/dist/core/scrapers/rss-scraper.d.ts +6 -0
  55. package/dist/core/scrapers/rss-scraper.js +32 -0
  56. package/dist/core/scrapers/rss-scraper.js.map +1 -0
  57. package/dist/core/scrapers/scrape-scraper.d.ts +4 -0
  58. package/dist/core/scrapers/scrape-scraper.js +49 -0
  59. package/dist/core/scrapers/scrape-scraper.js.map +1 -0
  60. package/dist/db/article-store.d.ts +22 -0
  61. package/dist/db/article-store.js +43 -0
  62. package/dist/db/article-store.js.map +1 -0
  63. package/dist/db/connection.d.ts +2 -0
  64. package/dist/db/connection.js +15 -0
  65. package/dist/db/connection.js.map +1 -0
  66. package/dist/db/migrate.d.ts +2 -0
  67. package/dist/db/migrate.js +60 -0
  68. package/dist/db/migrate.js.map +1 -0
  69. package/dist/db/schedule-store.d.ts +17 -0
  70. package/dist/db/schedule-store.js +23 -0
  71. package/dist/db/schedule-store.js.map +1 -0
  72. package/dist/db/source-health-store.d.ts +16 -0
  73. package/dist/db/source-health-store.js +31 -0
  74. package/dist/db/source-health-store.js.map +1 -0
  75. package/dist/providers/delivery/index.d.ts +18 -0
  76. package/dist/providers/delivery/index.js +2 -0
  77. package/dist/providers/delivery/index.js.map +1 -0
  78. package/dist/providers/delivery/slack.d.ts +6 -0
  79. package/dist/providers/delivery/slack.js +52 -0
  80. package/dist/providers/delivery/slack.js.map +1 -0
  81. package/dist/providers/delivery/webhook.d.ts +6 -0
  82. package/dist/providers/delivery/webhook.js +16 -0
  83. package/dist/providers/delivery/webhook.js.map +1 -0
  84. package/dist/providers/factory.d.ts +7 -0
  85. package/dist/providers/factory.js +33 -0
  86. package/dist/providers/factory.js.map +1 -0
  87. package/dist/providers/llm/anthropic.d.ts +12 -0
  88. package/dist/providers/llm/anthropic.js +43 -0
  89. package/dist/providers/llm/anthropic.js.map +1 -0
  90. package/dist/providers/llm/index.d.ts +10 -0
  91. package/dist/providers/llm/index.js +2 -0
  92. package/dist/providers/llm/index.js.map +1 -0
  93. package/dist/providers/llm/ollama.d.ts +12 -0
  94. package/dist/providers/llm/ollama.js +42 -0
  95. package/dist/providers/llm/ollama.js.map +1 -0
  96. package/dist/providers/llm/openrouter.d.ts +13 -0
  97. package/dist/providers/llm/openrouter.js +53 -0
  98. package/dist/providers/llm/openrouter.js.map +1 -0
  99. package/dist/providers/llm/utils.d.ts +6 -0
  100. package/dist/providers/llm/utils.js +45 -0
  101. package/dist/providers/llm/utils.js.map +1 -0
  102. package/dist/resources/default-feeds.json +650 -0
  103. package/dist/resources/index.d.ts +2 -0
  104. package/dist/resources/index.js +3 -0
  105. package/dist/resources/index.js.map +1 -0
  106. package/eslint.config.mjs +29 -0
  107. package/package.json +66 -0
  108. package/src/cli/commands/init.ts +94 -0
  109. package/src/cli/commands/run.ts +52 -0
  110. package/src/cli/commands/schedule.ts +99 -0
  111. package/src/cli/index.ts +34 -0
  112. package/src/core/config-manager.ts +72 -0
  113. package/src/core/config-schema.ts +31 -0
  114. package/src/core/default-keywords.ts +9 -0
  115. package/src/core/filters/deduplicator.ts +39 -0
  116. package/src/core/filters/keywords.ts +18 -0
  117. package/src/core/orchestrator.ts +47 -0
  118. package/src/core/pipeline.ts +171 -0
  119. package/src/core/scheduler.ts +74 -0
  120. package/src/core/scraper-types.ts +30 -0
  121. package/src/core/scrapers/api-scraper.ts +45 -0
  122. package/src/core/scrapers/arxiv-scraper.ts +35 -0
  123. package/src/core/scrapers/json-scraper.ts +54 -0
  124. package/src/core/scrapers/rss-scraper.ts +34 -0
  125. package/src/core/scrapers/scrape-scraper.ts +50 -0
  126. package/src/db/article-store.ts +75 -0
  127. package/src/db/connection.ts +17 -0
  128. package/src/db/migrate.ts +68 -0
  129. package/src/db/schedule-store.ts +41 -0
  130. package/src/db/source-health-store.ts +42 -0
  131. package/src/providers/delivery/index.ts +19 -0
  132. package/src/providers/delivery/slack.ts +55 -0
  133. package/src/providers/delivery/webhook.ts +16 -0
  134. package/src/providers/factory.ts +37 -0
  135. package/src/providers/llm/anthropic.ts +48 -0
  136. package/src/providers/llm/index.ts +8 -0
  137. package/src/providers/llm/ollama.ts +44 -0
  138. package/src/providers/llm/openrouter.ts +56 -0
  139. package/src/providers/llm/utils.ts +54 -0
  140. package/src/resources/default-feeds.json +650 -0
  141. package/src/resources/index.ts +3 -0
  142. package/tests/config-manager.test.ts +70 -0
  143. package/tests/db-integration.test.ts +72 -0
  144. package/tests/filters.test.ts +53 -0
  145. package/tests/llm-provider.test.ts +115 -0
  146. package/tsconfig.json +18 -0
  147. package/vitest.config.ts +13 -0
@@ -0,0 +1,32 @@
1
+ import Parser from 'rss-parser';
2
+ import { BaseScraper } from '../scraper-types.js';
3
+ export class RssScraper extends BaseScraper {
4
+ parser;
5
+ constructor() {
6
+ super();
7
+ this.parser = new Parser();
8
+ }
9
+ async scrape(source) {
10
+ try {
11
+ const feed = await this.parser.parseURL(source.url);
12
+ const items = feed.items.map((item) => ({
13
+ id: item.guid || item.link || `${source.id}-${item.title}`,
14
+ title: item.title || 'No Title',
15
+ url: item.link || '',
16
+ content: item.contentSnippet || item.content || '',
17
+ published_at: item.isoDate || item.pubDate,
18
+ source: source.id,
19
+ }));
20
+ return { source: source.id, status: 'ok', items };
21
+ }
22
+ catch (error) {
23
+ return {
24
+ source: source.id,
25
+ status: 'error',
26
+ items: [],
27
+ error: error instanceof Error ? error.message : String(error),
28
+ };
29
+ }
30
+ }
31
+ }
32
+ //# sourceMappingURL=rss-scraper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rss-scraper.js","sourceRoot":"","sources":["../../../src/core/scrapers/rss-scraper.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,YAAY,CAAC;AAChC,OAAO,EAAE,WAAW,EAAgD,MAAM,qBAAqB,CAAC;AAEhG,MAAM,OAAO,UAAW,SAAQ,WAAW;IACjC,MAAM,CAAS;IAEvB;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAqB;QAChC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACpD,MAAM,KAAK,GAAqB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACxD,EAAE,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE;gBAC1D,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,UAAU;gBAC/B,GAAG,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;gBACpB,OAAO,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE;gBAClD,YAAY,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO;gBAC1C,MAAM,EAAE,MAAM,CAAC,EAAE;aAClB,CAAC,CAAC,CAAC;YAEJ,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QACpD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,EAAE;gBACjB,MAAM,EAAE,OAAO;gBACf,KAAK,EAAE,EAAE;gBACT,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ import { BaseScraper, ScraperSource, ScraperResult } from '../scraper-types.js';
2
+ export declare class ScrapeScraper extends BaseScraper {
3
+ scrape(source: ScraperSource): Promise<ScraperResult>;
4
+ }
@@ -0,0 +1,49 @@
1
+ import axios from 'axios';
2
+ import * as cheerio from 'cheerio';
3
+ import { BaseScraper } from '../scraper-types.js';
4
+ export class ScrapeScraper extends BaseScraper {
5
+ async scrape(source) {
6
+ try {
7
+ const response = await axios.get(source.url, {
8
+ headers: {
9
+ 'User-Agent': 'Mozilla/5.0 (compatible; AlifBot/1.0)',
10
+ },
11
+ });
12
+ const $ = cheerio.load(response.data);
13
+ const items = [];
14
+ if (source.id === 'github_trending') {
15
+ $('.Box-row').each((_, el) => {
16
+ const $el = $(el);
17
+ const title = $el.find('h2 a').text().trim().replace(/\s+/g, ' ');
18
+ const url = 'https://github.com' + $el.find('h2 a').attr('href');
19
+ const content = $el.find('p').text().trim();
20
+ items.push({
21
+ id: url,
22
+ title,
23
+ url,
24
+ content,
25
+ source: source.id,
26
+ });
27
+ });
28
+ }
29
+ else {
30
+ return {
31
+ source: source.id,
32
+ status: 'error',
33
+ items: [],
34
+ error: 'Unsupported scraping source',
35
+ };
36
+ }
37
+ return { source: source.id, status: 'ok', items };
38
+ }
39
+ catch (error) {
40
+ return {
41
+ source: source.id,
42
+ status: 'error',
43
+ items: [],
44
+ error: error instanceof Error ? error.message : String(error),
45
+ };
46
+ }
47
+ }
48
+ }
49
+ //# sourceMappingURL=scrape-scraper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrape-scraper.js","sourceRoot":"","sources":["../../../src/core/scrapers/scrape-scraper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,WAAW,EAAgD,MAAM,qBAAqB,CAAC;AAEhG,MAAM,OAAO,aAAc,SAAQ,WAAW;IAC5C,KAAK,CAAC,MAAM,CAAC,MAAqB;QAChC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE;gBAC3C,OAAO,EAAE;oBACP,YAAY,EAAE,uCAAuC;iBACtD;aACF,CAAC,CAAC;YACH,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACtC,MAAM,KAAK,GAAqB,EAAE,CAAC;YAEnC,IAAI,MAAM,CAAC,EAAE,KAAK,iBAAiB,EAAE,CAAC;gBACpC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE;oBAC3B,MAAM,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;oBAClB,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;oBAClE,MAAM,GAAG,GAAG,oBAAoB,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;oBACjE,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;oBAE5C,KAAK,CAAC,IAAI,CAAC;wBACT,EAAE,EAAE,GAAG;wBACP,KAAK;wBACL,GAAG;wBACH,OAAO;wBACP,MAAM,EAAE,MAAM,CAAC,EAAE;qBAClB,CAAC,CAAC;gBACL,CAAC,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,OAAO;oBACL,MAAM,EAAE,MAAM,CAAC,EAAE;oBACjB,MAAM,EAAE,OAAO;oBACf,KAAK,EAAE,EAAE;oBACT,KAAK,EAAE,6BAA6B;iBACrC,CAAC;YACJ,CAAC;YAED,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QACpD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,EAAE;gBACjB,MAAM,EAAE,OAAO;gBACf,KAAK,EAAE,EAAE;gBACT,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,22 @@
1
+ import { Database } from 'better-sqlite3';
2
+ export interface Article {
3
+ id: string;
4
+ title: string;
5
+ url: string;
6
+ source: string;
7
+ content?: string | null;
8
+ summary?: string | null;
9
+ category?: string;
10
+ published_at?: string;
11
+ score?: number;
12
+ digest_date: string;
13
+ delivered?: number;
14
+ }
15
+ export declare class ArticleStore {
16
+ private db;
17
+ constructor(db: Database);
18
+ upsert(article: Article): void;
19
+ getPendingHighSignal(threshold: number, hours?: number): Article[];
20
+ markAsDelivered(articleIds: string[]): void;
21
+ getLatestTimestamp(): string | null;
22
+ }
@@ -0,0 +1,43 @@
1
+ export class ArticleStore {
2
+ db;
3
+ constructor(db) {
4
+ this.db = db;
5
+ }
6
+ upsert(article) {
7
+ const stmt = this.db.prepare(`
8
+ INSERT INTO articles (id, title, url, source, content, summary, category, published_at, score, digest_date, delivered)
9
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
10
+ ON CONFLICT(id) DO UPDATE SET
11
+ title = excluded.title,
12
+ summary = excluded.summary,
13
+ category = excluded.category,
14
+ score = excluded.score
15
+ `);
16
+ stmt.run(article.id, article.title, article.url, article.source, article.content || null, article.summary || null, article.category || 'Uncategorized', article.published_at || null, article.score || 0, article.digest_date, article.delivered || 0);
17
+ }
18
+ getPendingHighSignal(threshold, hours = 24) {
19
+ const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString();
20
+ return this.db
21
+ .prepare(`
22
+ SELECT * FROM articles
23
+ WHERE score >= ?
24
+ AND delivered = 0
25
+ AND summary IS NOT NULL
26
+ AND (published_at > ? OR digest_date > ?)
27
+ `)
28
+ .all(threshold, cutoff, cutoff.split('T')[0]);
29
+ }
30
+ markAsDelivered(articleIds) {
31
+ const stmt = this.db.prepare('UPDATE articles SET delivered = 1 WHERE id = ?');
32
+ const transaction = this.db.transaction((ids) => {
33
+ for (const id of ids)
34
+ stmt.run(id);
35
+ });
36
+ transaction(articleIds);
37
+ }
38
+ getLatestTimestamp() {
39
+ const result = this.db.prepare('SELECT MAX(published_at) as latest FROM articles').get();
40
+ return result?.latest || null;
41
+ }
42
+ }
43
+ //# sourceMappingURL=article-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"article-store.js","sourceRoot":"","sources":["../../src/db/article-store.ts"],"names":[],"mappings":"AAgBA,MAAM,OAAO,YAAY;IACH;IAApB,YAAoB,EAAY;QAAZ,OAAE,GAAF,EAAE,CAAU;IAAG,CAAC;IAEpC,MAAM,CAAC,OAAgB;QACrB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;;;KAQ5B,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CACN,OAAO,CAAC,EAAE,EACV,OAAO,CAAC,KAAK,EACb,OAAO,CAAC,GAAG,EACX,OAAO,CAAC,MAAM,EACd,OAAO,CAAC,OAAO,IAAI,IAAI,EACvB,OAAO,CAAC,OAAO,IAAI,IAAI,EACvB,OAAO,CAAC,QAAQ,IAAI,eAAe,EACnC,OAAO,CAAC,YAAY,IAAI,IAAI,EAC5B,OAAO,CAAC,KAAK,IAAI,CAAC,EAClB,OAAO,CAAC,WAAW,EACnB,OAAO,CAAC,SAAS,IAAI,CAAC,CACvB,CAAC;IACJ,CAAC;IAED,oBAAoB,CAAC,SAAiB,EAAE,QAAgB,EAAE;QACxD,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QAC3E,OAAO,IAAI,CAAC,EAAE;aACX,OAAO,CACN;;;;;;KAMH,CACE;aACA,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAc,CAAC;IAC/D,CAAC;IAED,eAAe,CAAC,UAAoB;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,gDAAgD,CAAC,CAAC;QAC/E,MAAM,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,GAAa,EAAE,EAAE;YACxD,KAAK,MAAM,EAAE,IAAI,GAAG;gBAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QACH,WAAW,CAAC,UAAU,CAAC,CAAC;IAC1B,CAAC;IAED,kBAAkB;QAChB,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,kDAAkD,CAAC,CAAC,GAAG,EAErF,CAAC;QACF,OAAO,MAAM,EAAE,MAAM,IAAI,IAAI,CAAC;IAChC,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ import { Database as DatabaseType } from 'better-sqlite3';
2
+ export declare function createDatabase(dbPath: string): DatabaseType;
@@ -0,0 +1,15 @@
1
+ import Database from 'better-sqlite3';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ export function createDatabase(dbPath) {
5
+ const dbDir = path.dirname(dbPath);
6
+ if (!fs.existsSync(dbDir)) {
7
+ fs.mkdirSync(dbDir, { recursive: true });
8
+ }
9
+ const db = new Database(dbPath);
10
+ db.pragma('journal_mode = WAL');
11
+ db.pragma('synchronous = NORMAL');
12
+ db.pragma('foreign_keys = ON');
13
+ return db;
14
+ }
15
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/db/connection.ts"],"names":[],"mappings":"AAAA,OAAO,QAAsC,MAAM,gBAAgB,CAAC;AACpE,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;IAClC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAE/B,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { Database } from 'better-sqlite3';
2
+ export declare function runMigrations(db: Database): void;
@@ -0,0 +1,60 @@
1
+ const MIGRATIONS = [
2
+ `
3
+ CREATE TABLE IF NOT EXISTS articles (
4
+ id TEXT PRIMARY KEY,
5
+ title TEXT NOT NULL,
6
+ url TEXT NOT NULL,
7
+ source TEXT NOT NULL,
8
+ content TEXT,
9
+ summary TEXT,
10
+ category TEXT,
11
+ published_at TEXT,
12
+ score INTEGER,
13
+ digest_date TEXT NOT NULL
14
+ );
15
+ `,
16
+ `
17
+ CREATE TABLE IF NOT EXISTS source_health (
18
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
19
+ source TEXT NOT NULL,
20
+ status TEXT NOT NULL,
21
+ items_found INTEGER DEFAULT 0,
22
+ error_message TEXT,
23
+ last_check TEXT DEFAULT CURRENT_TIMESTAMP
24
+ );
25
+ `,
26
+ `
27
+ CREATE TABLE IF NOT EXISTS schedules (
28
+ id TEXT PRIMARY KEY,
29
+ name TEXT NOT NULL,
30
+ cron TEXT NOT NULL,
31
+ active INTEGER DEFAULT 1,
32
+ last_run TEXT
33
+ );
34
+ `,
35
+ `
36
+ ALTER TABLE schedules ADD COLUMN scheduled_time TEXT;
37
+ `,
38
+ `
39
+ ALTER TABLE articles ADD COLUMN delivered INTEGER DEFAULT 0;
40
+ `,
41
+ ];
42
+ export function runMigrations(db) {
43
+ db.transaction(() => {
44
+ // Basic migration tracking
45
+ db.prepare(`
46
+ CREATE TABLE IF NOT EXISTS migrations (
47
+ id INTEGER PRIMARY KEY,
48
+ executed_at TEXT DEFAULT CURRENT_TIMESTAMP
49
+ )
50
+ `).run();
51
+ const result = db.prepare('SELECT MAX(id) as lastId FROM migrations').get();
52
+ const lastId = result?.lastId ?? -1;
53
+ for (let i = lastId + 1; i < MIGRATIONS.length; i++) {
54
+ console.log(`[Database] Running migration ${i}...`);
55
+ db.prepare(MIGRATIONS[i]).run();
56
+ db.prepare('INSERT INTO migrations (id) VALUES (?)').run(i);
57
+ }
58
+ })();
59
+ }
60
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,GAAG;IACjB;;;;;;;;;;;;;GAaC;IACD;;;;;;;;;GASC;IACD;;;;;;;;GAQC;IACD;;GAEC;IACD;;GAEC;CACF,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,EAAY;IACxC,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QAClB,2BAA2B;QAC3B,EAAE,CAAC,OAAO,CACR;;;;;KAKD,CACA,CAAC,GAAG,EAAE,CAAC;QAER,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,0CAA0C,CAAC,CAAC,GAAG,EAExE,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC;QAEpC,KAAK,IAAI,CAAC,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACpD,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,KAAK,CAAC,CAAC;YACpD,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YAChC,EAAE,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;AACP,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { Database } from 'better-sqlite3';
2
+ export interface Schedule {
3
+ id: string;
4
+ name: string;
5
+ cron: string;
6
+ scheduled_time?: string | null;
7
+ active: number;
8
+ last_run?: string | null;
9
+ }
10
+ export declare class ScheduleStore {
11
+ private db;
12
+ constructor(db: Database);
13
+ add(schedule: Schedule): void;
14
+ getAll(): Schedule[];
15
+ delete(id: string): void;
16
+ updateLastRun(id: string, timestamp: string): void;
17
+ }
@@ -0,0 +1,23 @@
1
+ export class ScheduleStore {
2
+ db;
3
+ constructor(db) {
4
+ this.db = db;
5
+ }
6
+ add(schedule) {
7
+ const stmt = this.db.prepare(`
8
+ INSERT INTO schedules (id, name, cron, scheduled_time, active, last_run)
9
+ VALUES (?, ?, ?, ?, ?, ?)
10
+ `);
11
+ stmt.run(schedule.id, schedule.name, schedule.cron, schedule.scheduled_time || null, schedule.active, schedule.last_run || null);
12
+ }
13
+ getAll() {
14
+ return this.db.prepare('SELECT * FROM schedules').all();
15
+ }
16
+ delete(id) {
17
+ this.db.prepare('DELETE FROM schedules WHERE id = ?').run(id);
18
+ }
19
+ updateLastRun(id, timestamp) {
20
+ this.db.prepare('UPDATE schedules SET last_run = ? WHERE id = ?').run(timestamp, id);
21
+ }
22
+ }
23
+ //# sourceMappingURL=schedule-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule-store.js","sourceRoot":"","sources":["../../src/db/schedule-store.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,aAAa;IACJ;IAApB,YAAoB,EAAY;QAAZ,OAAE,GAAF,EAAE,CAAU;IAAI,CAAC;IAErC,GAAG,CAAC,QAAkB;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;KAG5B,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CACN,QAAQ,CAAC,EAAE,EACX,QAAQ,CAAC,IAAI,EACb,QAAQ,CAAC,IAAI,EACb,QAAQ,CAAC,cAAc,IAAI,IAAI,EAC/B,QAAQ,CAAC,MAAM,EACf,QAAQ,CAAC,QAAQ,IAAI,IAAI,CAC1B,CAAC;IACJ,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC,GAAG,EAAgB,CAAC;IACxE,CAAC;IAED,MAAM,CAAC,EAAU;QACf,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC;IAED,aAAa,CAAC,EAAU,EAAE,SAAiB;QACzC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,gDAAgD,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACvF,CAAC;CACF"}
@@ -0,0 +1,16 @@
1
+ import { Database } from 'better-sqlite3';
2
+ export interface SourceHealth {
3
+ source: string;
4
+ status: 'ok' | 'error';
5
+ items_found: number;
6
+ error_message?: string | null;
7
+ }
8
+ export declare class SourceHealthStore {
9
+ private db;
10
+ constructor(db: Database);
11
+ record(health: SourceHealth): void;
12
+ getLatest(source: string): {
13
+ last_check: string;
14
+ } | undefined;
15
+ isThrottled(source: string, minutes: number): boolean;
16
+ }
@@ -0,0 +1,31 @@
1
+ export class SourceHealthStore {
2
+ db;
3
+ constructor(db) {
4
+ this.db = db;
5
+ }
6
+ record(health) {
7
+ const stmt = this.db.prepare(`
8
+ INSERT INTO source_health (source, status, items_found, error_message)
9
+ VALUES (?, ?, ?, ?)
10
+ `);
11
+ stmt.run(health.source, health.status, health.items_found, health.error_message || null);
12
+ }
13
+ getLatest(source) {
14
+ return this.db
15
+ .prepare(`
16
+ SELECT * FROM source_health WHERE source = ? ORDER BY last_check DESC LIMIT 1
17
+ `)
18
+ .get(source);
19
+ }
20
+ isThrottled(source, minutes) {
21
+ const latest = this.getLatest(source);
22
+ if (!latest)
23
+ return false;
24
+ // SQLite CURRENT_TIMESTAMP is UTC
25
+ const lastCheck = new Date(latest.last_check + 'Z').getTime();
26
+ const now = Date.now();
27
+ const diffMinutes = (now - lastCheck) / (1000 * 60);
28
+ return diffMinutes < minutes;
29
+ }
30
+ }
31
+ //# sourceMappingURL=source-health-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-health-store.js","sourceRoot":"","sources":["../../src/db/source-health-store.ts"],"names":[],"mappings":"AASA,MAAM,OAAO,iBAAiB;IACR;IAApB,YAAoB,EAAY;QAAZ,OAAE,GAAF,EAAE,CAAU;IAAG,CAAC;IAEpC,MAAM,CAAC,MAAoB;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;KAG5B,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,aAAa,IAAI,IAAI,CAAC,CAAC;IAC3F,CAAC;IAED,SAAS,CAAC,MAAc;QACtB,OAAO,IAAI,CAAC,EAAE;aACX,OAAO,CACN;;KAEH,CACE;aACA,GAAG,CAAC,MAAM,CAAuC,CAAC;IACvD,CAAC;IAED,WAAW,CAAC,MAAc,EAAE,OAAe;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAE1B,kCAAkC;QAClC,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,GAAG,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;QAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,WAAW,GAAG,CAAC,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;QAEpD,OAAO,WAAW,GAAG,OAAO,CAAC;IAC/B,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ export interface Digest {
2
+ items: {
3
+ title: string;
4
+ url: string;
5
+ summary: string | null;
6
+ category: string;
7
+ source: string;
8
+ score: number;
9
+ }[];
10
+ metadata: {
11
+ total_new_items: number;
12
+ total_selected: number;
13
+ date: string;
14
+ };
15
+ }
16
+ export interface DeliveryProvider {
17
+ send(digest: Digest): Promise<void>;
18
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/providers/delivery/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import { DeliveryProvider, Digest } from './index.js';
2
+ export declare class SlackDelivery implements DeliveryProvider {
3
+ private webhookUrl;
4
+ constructor(webhookUrl: string);
5
+ send(digest: Digest): Promise<void>;
6
+ }
@@ -0,0 +1,52 @@
1
+ import axios from 'axios';
2
+ export class SlackDelivery {
3
+ webhookUrl;
4
+ constructor(webhookUrl) {
5
+ this.webhookUrl = webhookUrl;
6
+ }
7
+ async send(digest) {
8
+ const blocks = [
9
+ {
10
+ type: 'header',
11
+ text: {
12
+ type: 'plain_text',
13
+ text: `🚀 Alif AI Signal Digest - ${digest.metadata.date}`,
14
+ emoji: true,
15
+ },
16
+ },
17
+ {
18
+ type: 'section',
19
+ text: {
20
+ type: 'mrkdwn',
21
+ text: `Found *${digest.metadata.total_selected}* high-signal items out of *${digest.metadata.total_new_items}* new articles analyzed today.`,
22
+ },
23
+ },
24
+ { type: 'divider' },
25
+ ];
26
+ for (const item of digest.items) {
27
+ blocks.push({
28
+ type: 'section',
29
+ text: {
30
+ type: 'mrkdwn',
31
+ text: `*<${item.url}|${item.title}>*\n_${item.category}_ • ${item.source} • Score: ${item.score}\n${item.summary || 'No summary available.'}`,
32
+ },
33
+ });
34
+ }
35
+ blocks.push({
36
+ type: 'context',
37
+ elements: [
38
+ {
39
+ type: 'mrkdwn',
40
+ text: 'Generated by *Alif CLI* — Built by qarib.',
41
+ },
42
+ ],
43
+ });
44
+ try {
45
+ await axios.post(this.webhookUrl, { blocks });
46
+ }
47
+ catch (error) {
48
+ console.error(`[Slack Delivery] Error: ${error instanceof Error ? error.message : String(error)}`);
49
+ }
50
+ }
51
+ }
52
+ //# sourceMappingURL=slack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack.js","sourceRoot":"","sources":["../../../src/providers/delivery/slack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,OAAO,aAAa;IACJ;IAApB,YAAoB,UAAkB;QAAlB,eAAU,GAAV,UAAU,CAAQ;IAAG,CAAC;IAE1C,KAAK,CAAC,IAAI,CAAC,MAAc;QACvB,MAAM,MAAM,GAAG;YACb;gBACE,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE;oBACJ,IAAI,EAAE,YAAY;oBAClB,IAAI,EAAE,8BAA8B,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;oBAC1D,KAAK,EAAE,IAAI;iBACZ;aACF;YACD;gBACE,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,UAAU,MAAM,CAAC,QAAQ,CAAC,cAAc,+BAA+B,MAAM,CAAC,QAAQ,CAAC,eAAe,gCAAgC;iBAC7I;aACF;YACD,EAAE,IAAI,EAAE,SAAS,EAAE;SACpB,CAAC;QAEF,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAChC,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,KAAK,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,MAAM,aAAa,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,OAAO,IAAI,uBAAuB,EAAE;iBAC9I;aACK,CAAC,CAAC;QACZ,CAAC;QAED,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,2CAA2C;iBAClD;aACF;SACK,CAAC,CAAC;QAEV,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CACX,2BAA2B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACpF,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,6 @@
1
+ import { DeliveryProvider, Digest } from './index.js';
2
+ export declare class WebhookDelivery implements DeliveryProvider {
3
+ private webhookUrl;
4
+ constructor(webhookUrl: string);
5
+ send(digest: Digest): Promise<void>;
6
+ }
@@ -0,0 +1,16 @@
1
+ import axios from 'axios';
2
+ export class WebhookDelivery {
3
+ webhookUrl;
4
+ constructor(webhookUrl) {
5
+ this.webhookUrl = webhookUrl;
6
+ }
7
+ async send(digest) {
8
+ try {
9
+ await axios.post(this.webhookUrl, digest);
10
+ }
11
+ catch (error) {
12
+ console.error(`[Webhook Delivery] Error: ${error instanceof Error ? error.message : String(error)}`);
13
+ }
14
+ }
15
+ }
16
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../../src/providers/delivery/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,OAAO,eAAe;IACN;IAApB,YAAoB,UAAkB;QAAlB,eAAU,GAAV,UAAU,CAAQ;IAAG,CAAC;IAE1C,KAAK,CAAC,IAAI,CAAC,MAAc;QACvB,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CACX,6BAA6B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACtF,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ import { Config } from '../core/config-schema.js';
2
+ import { LLMProvider } from './llm/index.js';
3
+ import { DeliveryProvider } from './delivery/index.js';
4
+ export declare class ProviderFactory {
5
+ static createLLM(config: Config): LLMProvider;
6
+ static createDelivery(config: Config): DeliveryProvider[];
7
+ }
@@ -0,0 +1,33 @@
1
+ import { OllamaProvider } from './llm/ollama.js';
2
+ import { AnthropicProvider } from './llm/anthropic.js';
3
+ import { OpenRouterProvider } from './llm/openrouter.js';
4
+ import { SlackDelivery } from './delivery/slack.js';
5
+ import { WebhookDelivery } from './delivery/webhook.js';
6
+ export class ProviderFactory {
7
+ static createLLM(config) {
8
+ const { provider, apiKey, model, baseUrl } = config.llm;
9
+ switch (provider) {
10
+ case 'ollama':
11
+ return new OllamaProvider({ baseUrl: baseUrl || 'http://localhost:11434', model });
12
+ case 'anthropic':
13
+ return new AnthropicProvider({ apiKey: apiKey || '', model });
14
+ case 'openrouter':
15
+ return new OpenRouterProvider({ apiKey: apiKey || '', model });
16
+ default:
17
+ throw new Error(`Unsupported LLM provider: ${provider}`);
18
+ }
19
+ }
20
+ static createDelivery(config) {
21
+ return config.delivery.map((d) => {
22
+ switch (d.type) {
23
+ case 'slack':
24
+ return new SlackDelivery(d.webhookUrl);
25
+ case 'webhook':
26
+ return new WebhookDelivery(d.webhookUrl);
27
+ default:
28
+ throw new Error(`Unsupported delivery type: ${d.type}`);
29
+ }
30
+ });
31
+ }
32
+ }
33
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/providers/factory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,MAAM,OAAO,eAAe;IAC1B,MAAM,CAAC,SAAS,CAAC,MAAc;QAC7B,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC;QACxD,QAAQ,QAAQ,EAAE,CAAC;YACjB,KAAK,QAAQ;gBACX,OAAO,IAAI,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,KAAK,EAAE,CAAC,CAAC;YACrF,KAAK,WAAW;gBACd,OAAO,IAAI,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAChE,KAAK,YAAY;gBACf,OAAO,IAAI,kBAAkB,CAAC,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YACjE;gBACE,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,MAAc;QAClC,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC/B,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;gBACf,KAAK,OAAO;oBACV,OAAO,IAAI,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;gBACzC,KAAK,SAAS;oBACZ,OAAO,IAAI,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;gBAC3C;oBACE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ import { LLMProvider, AnalysisResult } from './index.js';
2
+ export declare class AnthropicProvider implements LLMProvider {
3
+ private options;
4
+ constructor(options: {
5
+ apiKey: string;
6
+ model: string;
7
+ });
8
+ analyze(articles: {
9
+ title: string;
10
+ content?: string;
11
+ }[]): Promise<AnalysisResult[]>;
12
+ }
@@ -0,0 +1,43 @@
1
+ import axios from 'axios';
2
+ import { parseLLMJson } from './utils.js';
3
+ export class AnthropicProvider {
4
+ options;
5
+ constructor(options) {
6
+ this.options = options;
7
+ }
8
+ async analyze(articles) {
9
+ if (articles.length === 0)
10
+ return [];
11
+ const prompt = `
12
+ Analyze the following AI-related news items. For each item, provide:
13
+ 1. A concise, one-sentence summary (max 30 words).
14
+ 2. A category (e.g., "Model Release", "Research", "Tool/SDK", "Policy", "Industry News", "Tutorial").
15
+
16
+ Return ONLY a JSON array of objects with keys "summary" and "category". Match the order of the input items.
17
+
18
+ Items:
19
+ ${articles.map((a, idx) => `${idx + 1}. TITLE: ${a.title}\nCONTENT: ${a.content || 'None'}`).join('\n\n')}
20
+ `;
21
+ try {
22
+ const response = await axios.post('https://api.anthropic.com/v1/messages', {
23
+ model: this.options.model,
24
+ max_tokens: 4096,
25
+ messages: [{ role: 'user', content: prompt }],
26
+ system: 'You are an AI signal analyst. Be precise and objective. You respond ONLY with valid JSON array.',
27
+ }, {
28
+ headers: {
29
+ 'x-api-key': this.options.apiKey,
30
+ 'anthropic-version': '2023-06-01',
31
+ 'content-type': 'application/json',
32
+ },
33
+ });
34
+ const content = response.data.content[0].text;
35
+ return parseLLMJson(content);
36
+ }
37
+ catch (error) {
38
+ console.error(`[Anthropic] Error: ${error instanceof Error ? error.message : String(error)}`);
39
+ return articles.map(() => ({ summary: null, category: 'Uncategorized' }));
40
+ }
41
+ }
42
+ }
43
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/providers/llm/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,OAAO,iBAAiB;IACR;IAApB,YAAoB,OAA0C;QAA1C,YAAO,GAAP,OAAO,CAAmC;IAAI,CAAC;IAEnE,KAAK,CAAC,OAAO,CAAC,QAA+C;QAC3D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAErC,MAAM,MAAM,GAAG;;;;;;;;EAQjB,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,KAAK,cAAc,CAAC,CAAC,OAAO,IAAI,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;CACxG,CAAC;QAEE,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAC/B,uCAAuC,EACvC;gBACE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK;gBACzB,UAAU,EAAE,IAAI;gBAChB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;gBAC7C,MAAM,EACJ,iGAAiG;aACpG,EACD;gBACE,OAAO,EAAE;oBACP,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;oBAChC,mBAAmB,EAAE,YAAY;oBACjC,cAAc,EAAE,kBAAkB;iBACnC;aACF,CACF,CAAC;YAEF,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC9C,OAAO,YAAY,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,sBAAsB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC9F,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ export interface AnalysisResult {
2
+ summary: string | null;
3
+ category: string;
4
+ }
5
+ export interface LLMProvider {
6
+ analyze(articles: {
7
+ title: string;
8
+ content?: string;
9
+ }[]): Promise<AnalysisResult[]>;
10
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/providers/llm/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import { LLMProvider, AnalysisResult } from './index.js';
2
+ export declare class OllamaProvider implements LLMProvider {
3
+ private options;
4
+ constructor(options: {
5
+ baseUrl: string;
6
+ model: string;
7
+ });
8
+ analyze(articles: {
9
+ title: string;
10
+ content?: string;
11
+ }[]): Promise<AnalysisResult[]>;
12
+ }