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,33 @@
1
+ name: Publish to NPM
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Checkout repository
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Setup Node.js
16
+ uses: actions/setup-node@v4
17
+ with:
18
+ node-version: '20'
19
+ registry-url: 'https://registry.npmjs.org'
20
+
21
+ - name: Install dependencies
22
+ run: npm ci
23
+
24
+ - name: Run tests
25
+ run: npm test
26
+
27
+ - name: Build project
28
+ run: npm run build
29
+
30
+ - name: Publish to NPM
31
+ run: npm publish --access public
32
+ env:
33
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1 @@
1
+ npx lint-staged
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "trailingComma": "all",
4
+ "singleQuote": true,
5
+ "printWidth": 100,
6
+ "tabWidth": 2
7
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 qarib
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # 🚀 Alif-Digest
2
+
3
+ **The Autonomous AI Signal Digest CLI.**
4
+
5
+ Alif (ألف) scours the web for high-signal AI developments, selects the most relevant breakthroughs using LLMs, and delivers a curated digest directly to your workspace.
6
+
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/)
9
+
10
+ ---
11
+
12
+ ## ⚡️ Quick Start
13
+
14
+ Alif helps you track AI breakthroughs by aggregating and analyzing high-signal sources. Follow these steps to get started:
15
+
16
+ ### 1. Install
17
+
18
+ ```bash
19
+ npm install -g alif-digest
20
+ ```
21
+
22
+ ### 2. Setup
23
+
24
+ Initialize your environment. Alif will guide you through connecting an LLM (Local Ollama, Anthropic, or OpenRouter) and setting up your delivery channels.
25
+
26
+ ```bash
27
+ alif init
28
+ ```
29
+
30
+ ### 3. Run
31
+
32
+ Generate your daily digest. Alif will scrape all sources, filter the noise, analyze the breakthroughs, and deliver the results.
33
+
34
+ ```bash
35
+ alif run
36
+ ```
37
+
38
+ To bypass the source rate limiting (default 5m cooldown), use the force flag:
39
+
40
+ ```bash
41
+ alif run --force
42
+ ```
43
+
44
+ ### 4. Schedule
45
+
46
+ Keep the signals flowing by checking for scheduled runs.
47
+
48
+ ```bash
49
+ alif schedule add
50
+ alif schedule check
51
+ ```
52
+
53
+ ---
54
+
55
+ ## 🛠 For Tinkers (Customize & Contribute)
56
+
57
+ Alif is built with extreme modularity. Everything from scrapers to LLM providers follows a strict factory pattern.
58
+
59
+ ### Architecture
60
+
61
+ - **Inspiration**: Built to solve the "noise" problem in AI news.
62
+ - **Persistence**: Local SQLite database handles article deduplication and history.
63
+ - **Workflow**: `Scraper` → `Deduplicator` → `Keyword Scorer` → `LLM Analyzer` → `Delivery`.
64
+
65
+ ### Customizing Sources & Keywords
66
+
67
+ Your feeds are stored in `~/.config/alif/feeds.json`. You can add any source using the supported types: `rss`, `api`, `json`, or `scrape`.
68
+
69
+ **Keyword Signal Overrides**
70
+ Alif ships with a set of default base keywords. If you want to change what Alif considers "high-signal" (or silence certain topics), add a `customKeywords` object to the `preferences` section in `~/.config/alif/config.json`:
71
+
72
+ ```json
73
+ "preferences": {
74
+ "signalThreshold": 60,
75
+ "customKeywords": {
76
+ "my-favorite-framework": 100,
77
+ "topic-i-want-to-ignore": 0,
78
+ "gpt-5": 50
79
+ }
80
+ }
81
+ ```
82
+
83
+ _Note: Your custom keywords will be merged with the base keywords. If you define a key that already exists, your weight will override the default._
84
+
85
+ ### Project Structure
86
+
87
+ - `src/core/scrapers/`: Logic for data ingestion.
88
+ - `src/providers/llm/`: Support for Ollama, Anthropic, and OpenRouter.
89
+ - `src/providers/delivery/`: Slack Block Kit and generic Webhook support.
90
+
91
+ ### Local Development
92
+
93
+ ```bash
94
+ # Install dependencies
95
+ npm install
96
+
97
+ # Run the CLI from source (requires one-time initialization)
98
+ npm run dev -- init
99
+ npm run dev -- run
100
+
101
+ # Execute tests
102
+ npm run test
103
+ ```
104
+
105
+ > [!TIP]
106
+ > **Developer Hint**: The CLI looks for configuration in `~/.config/alif/config.json`. If you are running in development, you still need to run `npm run dev -- init` once to generate your local config and database path.
107
+
108
+ ### Troubleshooting Husky (GUI / GitHub Desktop)
109
+
110
+ If you're using a GUI client like GitHub Desktop on macOS and the pre-commit hooks fail because `npm` or `node` is not found, you need to ensure Husky can find your PATH.
111
+
112
+ Run the following in your terminal:
113
+
114
+ ```bash
115
+ mkdir -p ~/.config/husky
116
+ echo 'export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"' > ~/.config/husky/init.sh
117
+ ```
118
+
119
+ ### Contributing
120
+
121
+ 1. Fork the repo.
122
+ 2. Create your feature branch (`git checkout -b feature/amazing-scraper`).
123
+ 3. Commit your changes (`git commit -m 'Add support for NewsAPI'`).
124
+ 4. Push to the branch (`git push origin feature/amazing-scraper`).
125
+ 5. Open a Pull Request.
126
+
127
+ ---
128
+
129
+ ## 📄 License
130
+
131
+ Distributed under the MIT License. See `LICENSE` for more information.
@@ -0,0 +1 @@
1
+ export declare function initCommand(): Promise<void>;
@@ -0,0 +1,88 @@
1
+ import prompts from 'prompts';
2
+ import path from 'path';
3
+ import { ConfigManager } from '../../core/config-manager.js';
4
+ export async function initCommand() {
5
+ const configManager = ConfigManager.getInstance();
6
+ const configDir = configManager.getConfigDir();
7
+ console.log('--- Alif Initialization ---');
8
+ const response = await prompts([
9
+ {
10
+ type: 'select',
11
+ name: 'llmProvider',
12
+ message: 'Which LLM provider would you like to use?',
13
+ choices: [
14
+ { title: 'Ollama (Local)', value: 'ollama' },
15
+ { title: 'Anthropic', value: 'anthropic' },
16
+ { title: 'OpenRouter', value: 'openrouter' },
17
+ ],
18
+ },
19
+ {
20
+ type: (prev) => (prev !== 'ollama' ? 'text' : null),
21
+ name: 'apiKey',
22
+ message: 'Enter your API Key:',
23
+ },
24
+ {
25
+ type: 'text',
26
+ name: 'model',
27
+ message: 'Enter the model name (e.g., llama3, claude-3-opus-20240229):',
28
+ initial: (prev, values) => {
29
+ if (values.llmProvider === 'ollama')
30
+ return 'llama3';
31
+ if (values.llmProvider === 'anthropic')
32
+ return 'claude-3-5-sonnet-20240620';
33
+ return 'meta-llama/llama-3-70b-instruct';
34
+ },
35
+ },
36
+ {
37
+ type: (prev, values) => (values.llmProvider === 'ollama' ? 'text' : null),
38
+ name: 'baseUrl',
39
+ message: 'Enter Ollama base URL:',
40
+ initial: 'http://localhost:11434',
41
+ },
42
+ {
43
+ type: 'multiselect',
44
+ name: 'deliveryProviders',
45
+ message: 'Where should we deliver the digest?',
46
+ choices: [
47
+ { title: 'Slack', value: 'slack' },
48
+ { title: 'Generic Webhook', value: 'webhook' },
49
+ ],
50
+ min: 1,
51
+ },
52
+ ]);
53
+ if (!response.llmProvider) {
54
+ console.log('Initialization cancelled.');
55
+ return;
56
+ }
57
+ const deliveryConfigs = [];
58
+ for (const provider of response.deliveryProviders) {
59
+ const { webhookUrl } = await prompts({
60
+ type: 'text',
61
+ name: 'webhookUrl',
62
+ message: `Enter ${provider} Webhook URL:`,
63
+ });
64
+ deliveryConfigs.push({ type: provider, webhookUrl });
65
+ }
66
+ const config = {
67
+ llm: {
68
+ provider: response.llmProvider,
69
+ apiKey: response.apiKey,
70
+ model: response.model,
71
+ baseUrl: response.baseUrl,
72
+ },
73
+ delivery: deliveryConfigs,
74
+ preferences: {
75
+ signalThreshold: 60,
76
+ maxItemsPerCategory: 5,
77
+ sourceCooldownMinutes: 5,
78
+ customKeywords: {},
79
+ },
80
+ dbPath: path.join(configDir, 'alif.db'),
81
+ feedsPath: path.join(configDir, 'feeds.json'),
82
+ };
83
+ configManager.save(config);
84
+ console.log(`\nConfiguration saved to ${configManager.getConfigFile()}`);
85
+ console.log(`Database will be located at ${config.dbPath}`);
86
+ console.log('\nAlif is ready! Run "alif run" to start (after adding feeds).');
87
+ }
88
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAG7D,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,aAAa,GAAG,aAAa,CAAC,WAAW,EAAE,CAAC;IAClD,MAAM,SAAS,GAAG,aAAa,CAAC,YAAY,EAAE,CAAC;IAE/C,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAE3C,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC;QAC7B;YACE,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,2CAA2C;YACpD,OAAO,EAAE;gBACP,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,QAAQ,EAAE;gBAC5C,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE;gBAC1C,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE;aAC7C;SACF;QACD;YACE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;YACnD,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,qBAAqB;SAC/B;QACD;YACE,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,8DAA8D;YACvE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;gBACxB,IAAI,MAAM,CAAC,WAAW,KAAK,QAAQ;oBAAE,OAAO,QAAQ,CAAC;gBACrD,IAAI,MAAM,CAAC,WAAW,KAAK,WAAW;oBAAE,OAAO,4BAA4B,CAAC;gBAC5E,OAAO,iCAAiC,CAAC;YAC3C,CAAC;SACF;QACD;YACE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;YACzE,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,wBAAwB;YACjC,OAAO,EAAE,wBAAwB;SAClC;QACD;YACE,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,mBAAmB;YACzB,OAAO,EAAE,qCAAqC;YAC9C,OAAO,EAAE;gBACP,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;gBAClC,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,SAAS,EAAE;aAC/C;YACD,GAAG,EAAE,CAAC;SACP;KACF,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;QACzC,OAAO;IACT,CAAC;IAED,MAAM,eAAe,GAAG,EAAE,CAAC;IAC3B,KAAK,MAAM,QAAQ,IAAI,QAAQ,CAAC,iBAAiB,EAAE,CAAC;QAClD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,OAAO,CAAC;YACnC,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,SAAS,QAAQ,eAAe;SAC1C,CAAC,CAAC;QACH,eAAe,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,MAAM,GAAW;QACrB,GAAG,EAAE;YACH,QAAQ,EAAE,QAAQ,CAAC,WAAW;YAC9B,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,OAAO,EAAE,QAAQ,CAAC,OAAO;SAC1B;QACD,QAAQ,EAAE,eAAe;QACzB,WAAW,EAAE;YACX,eAAe,EAAE,EAAE;YACnB,mBAAmB,EAAE,CAAC;YACtB,qBAAqB,EAAE,CAAC;YACxB,cAAc,EAAE,EAAE;SACnB;QACD,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC;QACvC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC;KAC9C,CAAC;IAEF,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAE3B,OAAO,CAAC,GAAG,CAAC,4BAA4B,aAAa,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,+BAA+B,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAC;AAChF,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function runPipeline(config: any, db: any, force?: boolean): Promise<void>;
2
+ export declare function runCommand(options?: {
3
+ force?: boolean;
4
+ }): Promise<void>;
@@ -0,0 +1,46 @@
1
+ import fs from 'fs';
2
+ import { ConfigManager } from '../../core/config-manager.js';
3
+ import { createDatabase } from '../../db/connection.js';
4
+ import { runMigrations } from '../../db/migrate.js';
5
+ import { Pipeline } from '../../core/pipeline.js';
6
+ export async function runPipeline(config, db, force = false) {
7
+ const pipeline = new Pipeline(config, db);
8
+ // Load feeds
9
+ if (!fs.existsSync(config.feedsPath)) {
10
+ console.log(`[Alif] Feeds file not found at ${config.feedsPath}. Initializing with default sources...`);
11
+ const { defaultFeeds } = await import('../../resources/index.js');
12
+ fs.writeFileSync(config.feedsPath, JSON.stringify(defaultFeeds, null, 2));
13
+ console.log(`[Alif] Created default feeds.json at ${config.feedsPath} with ${defaultFeeds.length} sources.`);
14
+ }
15
+ const feeds = JSON.parse(fs.readFileSync(config.feedsPath, 'utf-8'));
16
+ await pipeline.run(feeds, force);
17
+ }
18
+ export async function runCommand(options = {}) {
19
+ const configManager = ConfigManager.getInstance();
20
+ if (!configManager.exists()) {
21
+ console.error('Alif is not initialized. Run "alif init" first.');
22
+ process.exit(1);
23
+ }
24
+ let db;
25
+ try {
26
+ const config = configManager.load();
27
+ db = createDatabase(config.dbPath);
28
+ runMigrations(db);
29
+ await runPipeline(config, db, options.force);
30
+ }
31
+ catch (error) {
32
+ if (error instanceof Error) {
33
+ console.error(`Error: ${error.message}`);
34
+ }
35
+ else {
36
+ console.error('An unknown error occurred.');
37
+ }
38
+ process.exit(1);
39
+ }
40
+ finally {
41
+ if (db)
42
+ db.close();
43
+ process.exit(0);
44
+ }
45
+ }
46
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/cli/commands/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAW,EAAE,EAAO,EAAE,KAAK,GAAG,KAAK;IACnE,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAE1C,aAAa;IACb,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,OAAO,CAAC,GAAG,CACT,kCAAkC,MAAM,CAAC,SAAS,wCAAwC,CAC3F,CAAC;QACF,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;QAClE,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1E,OAAO,CAAC,GAAG,CACT,wCAAwC,MAAM,CAAC,SAAS,SAAS,YAAY,CAAC,MAAM,WAAW,CAChG,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IACrE,MAAM,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAA+B,EAAE;IAChE,MAAM,aAAa,GAAG,aAAa,CAAC,WAAW,EAAE,CAAC;IAElD,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,EAAE,CAAC;IACP,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC;QACpC,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACnC,aAAa,CAAC,EAAE,CAAC,CAAC;QAElB,MAAM,WAAW,CAAC,MAAM,EAAE,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;YAAS,CAAC;QACT,IAAI,EAAE;YAAE,EAAE,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function scheduleCommand(action: string): Promise<void>;
@@ -0,0 +1,94 @@
1
+ import prompts from 'prompts';
2
+ import { ConfigManager } from '../../core/config-manager.js';
3
+ import { createDatabase } from '../../db/connection.js';
4
+ import { runMigrations } from '../../db/migrate.js';
5
+ import { Scheduler } from '../../core/scheduler.js';
6
+ import { runPipeline } from './run.js';
7
+ export async function scheduleCommand(action) {
8
+ const configManager = ConfigManager.getInstance();
9
+ if (!configManager.exists()) {
10
+ console.error('Alif is not initialized. Run "alif init" first.');
11
+ return;
12
+ }
13
+ const config = configManager.load();
14
+ const db = createDatabase(config.dbPath);
15
+ try {
16
+ runMigrations(db);
17
+ const scheduler = new Scheduler(db);
18
+ if (action === 'add') {
19
+ const response = await prompts([
20
+ {
21
+ type: 'text',
22
+ name: 'name',
23
+ message: 'Name for this schedule:',
24
+ initial: 'Daily Digest',
25
+ },
26
+ {
27
+ type: 'text',
28
+ name: 'cron',
29
+ message: 'Enter frequency (e.g. daily, hourly):',
30
+ initial: 'daily',
31
+ },
32
+ {
33
+ type: (prev) => (prev === 'daily' ? 'text' : null),
34
+ name: 'time',
35
+ message: 'At what time? (HH:mm format, 24h):',
36
+ initial: '09:00',
37
+ validate: (val) => /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/.test(val) ? true : 'Please enter valid HH:mm time',
38
+ },
39
+ ]);
40
+ if (response.name && response.cron) {
41
+ const id = await scheduler.add(response.name, response.cron, response.time);
42
+ console.log(`Schedule added! ID: ${id} (Runs ${response.cron}${response.time ? ` at ${response.time}` : ''})`);
43
+ }
44
+ }
45
+ else if (action === 'list') {
46
+ const schedules = scheduler.list();
47
+ if (schedules.length === 0) {
48
+ console.log('No schedules found.');
49
+ }
50
+ else {
51
+ console.table(schedules.map((s) => ({
52
+ ID: s.id,
53
+ Name: s.name,
54
+ Frequency: s.cron,
55
+ Time: s.scheduled_time || '-',
56
+ Active: s.active ? 'Yes' : 'No',
57
+ 'Last Run': s.last_run || 'Never',
58
+ })));
59
+ }
60
+ }
61
+ else if (action === 'delete') {
62
+ const schedules = scheduler.list();
63
+ if (schedules.length === 0) {
64
+ console.log('No schedules to delete.');
65
+ return;
66
+ }
67
+ const { id } = await prompts({
68
+ type: 'select',
69
+ name: 'id',
70
+ message: 'Select schedule to delete:',
71
+ choices: schedules.map((s) => ({ title: s.name, value: s.id })),
72
+ });
73
+ if (id) {
74
+ scheduler.remove(id);
75
+ console.log(`Schedule ${id} deleted.`);
76
+ }
77
+ }
78
+ else if (action === 'check') {
79
+ console.log('[Scheduler] Checking for due tasks...');
80
+ await scheduler.checkAndRun(async () => {
81
+ await runPipeline(config, db);
82
+ });
83
+ console.log('[Scheduler] Check complete.');
84
+ }
85
+ else {
86
+ console.error(`Unknown action: ${action}. Available: add, list, delete, check`);
87
+ }
88
+ }
89
+ finally {
90
+ db.close();
91
+ process.exit(0);
92
+ }
93
+ }
94
+ //# sourceMappingURL=schedule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule.js","sourceRoot":"","sources":["../../../src/cli/commands/schedule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { initCommand } from './commands/init.js';
4
+ import { runCommand } from './commands/run.js';
5
+ import { scheduleCommand } from './commands/schedule.js';
6
+ const program = new Command();
7
+ program.name('alif').description('Alif - Daily AI Signal Digest CLI').version('1.0.0');
8
+ program
9
+ .command('init')
10
+ .description('Initialize Alif configuration')
11
+ .action(async () => {
12
+ await initCommand();
13
+ });
14
+ program
15
+ .command('run')
16
+ .description('Run the AI Signal Digest pipeline')
17
+ .option('-f, --force', 'Bypass source cooldown')
18
+ .action(async (options) => {
19
+ await runCommand(options);
20
+ });
21
+ program
22
+ .command('schedule <action>')
23
+ .description('Manage digest schedules')
24
+ .addHelpText('after', '\nActions: add, list, delete, check')
25
+ .action(async (action) => {
26
+ await scheduleCommand(action);
27
+ });
28
+ program.parse(process.argv);
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,mCAAmC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAEvF,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,+BAA+B,CAAC;KAC5C,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,WAAW,EAAE,CAAC;AACtB,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,mCAAmC,CAAC;KAChD,MAAM,CAAC,aAAa,EAAE,wBAAwB,CAAC;KAC/C,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;AAC5B,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,mBAAmB,CAAC;KAC5B,WAAW,CAAC,yBAAyB,CAAC;KACtC,WAAW,CAAC,OAAO,EAAE,qCAAqC,CAAC;KAC3D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;IACvB,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { Config } from './config-schema.js';
2
+ export declare class ConfigManager {
3
+ private static instance;
4
+ private config;
5
+ private configDir;
6
+ private configFile;
7
+ private constructor();
8
+ static getInstance(): ConfigManager;
9
+ getConfigDir(): string;
10
+ getConfigFile(): string;
11
+ load(): Config;
12
+ save(config: Config): void;
13
+ exists(): boolean;
14
+ }
@@ -0,0 +1,65 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { ConfigSchema } from './config-schema.js';
5
+ export class ConfigManager {
6
+ static instance;
7
+ config = null;
8
+ configDir;
9
+ configFile;
10
+ constructor() {
11
+ this.configDir = path.join(os.homedir(), '.config', 'alif');
12
+ this.configFile = path.join(this.configDir, 'config.json');
13
+ }
14
+ static getInstance() {
15
+ if (!ConfigManager.instance) {
16
+ ConfigManager.instance = new ConfigManager();
17
+ }
18
+ return ConfigManager.instance;
19
+ }
20
+ getConfigDir() {
21
+ return this.configDir;
22
+ }
23
+ getConfigFile() {
24
+ return this.configFile;
25
+ }
26
+ load() {
27
+ if (this.config)
28
+ return this.config;
29
+ if (!fs.existsSync(this.configFile)) {
30
+ throw new Error(`Configuration file not found at ${this.configFile}. Run 'alif init' first.`);
31
+ }
32
+ try {
33
+ const raw = fs.readFileSync(this.configFile, 'utf-8');
34
+ const parsed = JSON.parse(raw);
35
+ this.config = ConfigSchema.parse(parsed);
36
+ return this.config;
37
+ }
38
+ catch (error) {
39
+ if (error instanceof Error) {
40
+ throw new Error(`Failed to load configuration: ${error.message}`, { cause: error });
41
+ }
42
+ throw error;
43
+ }
44
+ }
45
+ save(config) {
46
+ if (!fs.existsSync(this.configDir)) {
47
+ fs.mkdirSync(this.configDir, { recursive: true });
48
+ }
49
+ try {
50
+ ConfigSchema.parse(config);
51
+ fs.writeFileSync(this.configFile, JSON.stringify(config, null, 2), 'utf-8');
52
+ this.config = config;
53
+ }
54
+ catch (error) {
55
+ if (error instanceof Error) {
56
+ throw new Error(`Failed to save configuration: ${error.message}`, { cause: error });
57
+ }
58
+ throw error;
59
+ }
60
+ }
61
+ exists() {
62
+ return fs.existsSync(this.configFile);
63
+ }
64
+ }
65
+ //# sourceMappingURL=config-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-manager.js","sourceRoot":"","sources":["../../src/core/config-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAU,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,OAAO,aAAa;IAChB,MAAM,CAAC,QAAQ,CAAgB;IAC/B,MAAM,GAAkB,IAAI,CAAC;IAC7B,SAAS,CAAS;IAClB,UAAU,CAAS;IAE3B;QACE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QAC5D,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,CAAC,WAAW;QAChB,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC;YAC5B,aAAa,CAAC,QAAQ,GAAG,IAAI,aAAa,EAAE,CAAC;QAC/C,CAAC;QACD,OAAO,aAAa,CAAC,QAAQ,CAAC;IAChC,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,aAAa;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC;QAEpC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,mCAAmC,IAAI,CAAC,UAAU,0BAA0B,CAAC,CAAC;QAChG,CAAC;QAED,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACtD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACzC,OAAO,IAAI,CAAC,MAAM,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,iCAAiC,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtF,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,IAAI,CAAC,MAAc;QACjB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,CAAC;YACH,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC3B,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YAC5E,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,iCAAiC,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtF,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,MAAM;QACJ,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxC,CAAC;CACF"}
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ export declare const LlmProviderType: z.ZodEnum<{
3
+ ollama: "ollama";
4
+ anthropic: "anthropic";
5
+ openrouter: "openrouter";
6
+ }>;
7
+ export declare const DeliveryProviderType: z.ZodEnum<{
8
+ slack: "slack";
9
+ webhook: "webhook";
10
+ }>;
11
+ export declare const ConfigSchema: z.ZodObject<{
12
+ llm: z.ZodObject<{
13
+ provider: z.ZodEnum<{
14
+ ollama: "ollama";
15
+ anthropic: "anthropic";
16
+ openrouter: "openrouter";
17
+ }>;
18
+ apiKey: z.ZodOptional<z.ZodString>;
19
+ model: z.ZodString;
20
+ baseUrl: z.ZodOptional<z.ZodString>;
21
+ }, z.core.$strip>;
22
+ delivery: z.ZodArray<z.ZodObject<{
23
+ type: z.ZodEnum<{
24
+ slack: "slack";
25
+ webhook: "webhook";
26
+ }>;
27
+ webhookUrl: z.ZodString;
28
+ }, z.core.$strip>>;
29
+ preferences: z.ZodObject<{
30
+ signalThreshold: z.ZodDefault<z.ZodNumber>;
31
+ maxItemsPerCategory: z.ZodDefault<z.ZodNumber>;
32
+ sourceCooldownMinutes: z.ZodDefault<z.ZodNumber>;
33
+ customKeywords: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
34
+ }, z.core.$strip>;
35
+ dbPath: z.ZodString;
36
+ feedsPath: z.ZodString;
37
+ }, z.core.$strip>;
38
+ export type Config = z.infer<typeof ConfigSchema>;
39
+ export type LlmProvider = z.infer<typeof LlmProviderType>;
40
+ export type DeliveryProvider = z.infer<typeof DeliveryProviderType>;
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ export const LlmProviderType = z.enum(['ollama', 'anthropic', 'openrouter']);
3
+ export const DeliveryProviderType = z.enum(['slack', 'webhook']);
4
+ export const ConfigSchema = z.object({
5
+ llm: z.object({
6
+ provider: LlmProviderType,
7
+ apiKey: z.string().optional(),
8
+ model: z.string(),
9
+ baseUrl: z.string().url().optional(),
10
+ }),
11
+ delivery: z.array(z.object({
12
+ type: DeliveryProviderType,
13
+ webhookUrl: z.string().url(),
14
+ })),
15
+ preferences: z.object({
16
+ signalThreshold: z.number().min(0).max(100).default(60),
17
+ maxItemsPerCategory: z.number().min(1).default(5),
18
+ sourceCooldownMinutes: z.number().min(0).default(5),
19
+ customKeywords: z.record(z.string(), z.number()).default({}),
20
+ }),
21
+ dbPath: z.string(),
22
+ feedsPath: z.string(),
23
+ });
24
+ //# sourceMappingURL=config-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-schema.js","sourceRoot":"","sources":["../../src/core/config-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;AAC7E,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;AAEjE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC;QACZ,QAAQ,EAAE,eAAe;QACzB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;KACrC,CAAC;IACF,QAAQ,EAAE,CAAC,CAAC,KAAK,CACf,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,oBAAoB;QAC1B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;KAC7B,CAAC,CACH;IACD,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QACjD,qBAAqB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QACnD,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;KAC7D,CAAC;IACF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;CACtB,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const BASE_KEYWORDS: Record<string, number>;