ubersearch 0.0.0-development
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/LICENSE +21 -0
- package/README.md +374 -0
- package/package.json +76 -0
- package/src/app/index.ts +30 -0
- package/src/bootstrap/container.ts +157 -0
- package/src/cli.ts +380 -0
- package/src/config/defineConfig.ts +176 -0
- package/src/config/load.ts +368 -0
- package/src/config/types.ts +86 -0
- package/src/config/validation.ts +148 -0
- package/src/core/cache.ts +74 -0
- package/src/core/container.ts +268 -0
- package/src/core/credits/CreditManager.ts +158 -0
- package/src/core/credits/CreditStateProvider.ts +151 -0
- package/src/core/credits/FileCreditStateProvider.ts +137 -0
- package/src/core/credits/index.ts +3 -0
- package/src/core/docker/dockerComposeHelper.ts +177 -0
- package/src/core/docker/dockerLifecycleManager.ts +361 -0
- package/src/core/docker/index.ts +8 -0
- package/src/core/logger.ts +146 -0
- package/src/core/orchestrator.ts +103 -0
- package/src/core/paths.ts +157 -0
- package/src/core/provider/ILifecycleProvider.ts +120 -0
- package/src/core/provider/ProviderFactory.ts +120 -0
- package/src/core/provider.ts +61 -0
- package/src/core/serviceKeys.ts +45 -0
- package/src/core/strategy/AllProvidersStrategy.ts +245 -0
- package/src/core/strategy/FirstSuccessStrategy.ts +98 -0
- package/src/core/strategy/ISearchStrategy.ts +94 -0
- package/src/core/strategy/StrategyFactory.ts +204 -0
- package/src/core/strategy/index.ts +9 -0
- package/src/core/strategy/types.ts +56 -0
- package/src/core/types.ts +58 -0
- package/src/index.ts +1 -0
- package/src/plugin/PluginRegistry.ts +336 -0
- package/src/plugin/builtin.ts +130 -0
- package/src/plugin/index.ts +33 -0
- package/src/plugin/types.ts +212 -0
- package/src/providers/BaseProvider.ts +49 -0
- package/src/providers/brave.ts +66 -0
- package/src/providers/constants.ts +13 -0
- package/src/providers/helpers/index.ts +24 -0
- package/src/providers/helpers/lifecycleHelpers.ts +110 -0
- package/src/providers/helpers/resultMappers.ts +168 -0
- package/src/providers/index.ts +6 -0
- package/src/providers/linkup.ts +114 -0
- package/src/providers/retry.ts +95 -0
- package/src/providers/searchxng.ts +163 -0
- package/src/providers/tavily.ts +73 -0
- package/src/providers/types/index.ts +185 -0
- package/src/providers/utils.ts +182 -0
- package/src/tool/allSearchTool.ts +110 -0
- package/src/tool/interface.ts +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Brian Sunter
|
|
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,374 @@
|
|
|
1
|
+
# Multi-Search
|
|
2
|
+
|
|
3
|
+
Unified, Bun-first search interface across multiple providers with credit tracking, pluggable strategies, and optional Docker-managed SearXNG.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- 🔍 Providers: Tavily, Brave, Linkup, SearXNG (local, Docker auto-start)
|
|
8
|
+
- 🔌 Extensible: Add custom providers via TypeScript plugin system
|
|
9
|
+
- 🤝 Single interface: shared types + CLI + programmatic API
|
|
10
|
+
- 💳 Credits: per-engine quotas with snapshots and low-credit warnings
|
|
11
|
+
- 🧠 Strategies: `all` (merge) or `first-success` (fastest win)
|
|
12
|
+
- ⚙️ Config: JSON or TypeScript (`defineConfig`), XDG-aware resolution
|
|
13
|
+
- 🐳 Auto-start: optional Docker lifecycle for local SearXNG
|
|
14
|
+
|
|
15
|
+
## Install & Run (Bun)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd /path/to/allsearch
|
|
19
|
+
bun install
|
|
20
|
+
|
|
21
|
+
# CLI (direct)
|
|
22
|
+
bun run src/cli.ts "best TypeScript ORM 2025"
|
|
23
|
+
|
|
24
|
+
# Or use bun link (works from any directory)
|
|
25
|
+
bun link
|
|
26
|
+
allsearch "llm observability" --json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Basic Search
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
allsearch "your search query"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Options
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
allsearch "query" [options]
|
|
41
|
+
|
|
42
|
+
Options:
|
|
43
|
+
--json Output results as JSON
|
|
44
|
+
--engines engine1,engine2 Use specific engines
|
|
45
|
+
--strategy all|first-success Search strategy (default: all)
|
|
46
|
+
--limit number Max results per engine
|
|
47
|
+
--include-raw Include raw provider responses
|
|
48
|
+
--help, -h Show help
|
|
49
|
+
health Run provider health checks (starts Docker-backed ones if needed)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Examples
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Search with specific engines
|
|
56
|
+
allsearch "hawaii dev meetups" --engines tavily,brave --json
|
|
57
|
+
|
|
58
|
+
# Use first-success strategy (stop after first working provider)
|
|
59
|
+
allsearch "emerging web frameworks" --strategy first-success
|
|
60
|
+
|
|
61
|
+
# Limit results per provider
|
|
62
|
+
allsearch "rust async patterns" --limit 3
|
|
63
|
+
|
|
64
|
+
# Check credit status
|
|
65
|
+
allsearch credits
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
Resolution order (first wins):
|
|
71
|
+
1. Explicit path passed to CLI/API (`--config /path/to/config.json`)
|
|
72
|
+
2. `./allsearch.config.(ts|json)` (current directory)
|
|
73
|
+
3. `$XDG_CONFIG_HOME/allsearch/config.(ts|json)` (default: `~/.config/allsearch/`)
|
|
74
|
+
|
|
75
|
+
### XDG Directory Structure
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
~/.config/allsearch/
|
|
79
|
+
├── config.json # Main configuration
|
|
80
|
+
└── searxng/
|
|
81
|
+
└── config/
|
|
82
|
+
└── settings.yml # SearXNG settings (auto-copied on first run)
|
|
83
|
+
|
|
84
|
+
~/.local/share/allsearch/
|
|
85
|
+
└── searxng/
|
|
86
|
+
└── data/ # SearXNG cache (auto-created)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- Example config: see `docs/config/allsearch.config.json`
|
|
90
|
+
- Schema: `docs/config/config.schema.json` (generated from Zod)
|
|
91
|
+
- TS helper: `defineConfig`, `defineTavily`, `defineBrave`, `defineLinkup`, `defineSearchxng`
|
|
92
|
+
|
|
93
|
+
### SearXNG Configuration
|
|
94
|
+
|
|
95
|
+
SearXNG uses Docker with volumes mounted to XDG directories. On first run, the default `settings.yml` is copied to `~/.config/allsearch/searxng/config/`. You can customize this file to:
|
|
96
|
+
- Enable/disable search engines
|
|
97
|
+
- Adjust rate limiting
|
|
98
|
+
- Configure output formats
|
|
99
|
+
|
|
100
|
+
## Custom Providers
|
|
101
|
+
|
|
102
|
+
Add your own search providers using the plugin system.
|
|
103
|
+
|
|
104
|
+
### TypeScript Config with Custom Provider
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// ~/.config/allsearch/config.ts
|
|
108
|
+
import { defineConfig, definePlugin } from "allsearch/config";
|
|
109
|
+
|
|
110
|
+
// 1. Define your provider class
|
|
111
|
+
class PerplexityProvider {
|
|
112
|
+
constructor(private config: any) {}
|
|
113
|
+
|
|
114
|
+
get id() { return this.config.id; }
|
|
115
|
+
|
|
116
|
+
async search(query: { query: string; limit?: number }) {
|
|
117
|
+
const response = await fetch("https://api.perplexity.ai/search", {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers: {
|
|
120
|
+
"Authorization": `Bearer ${process.env[this.config.apiKeyEnv]}`,
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
},
|
|
123
|
+
body: JSON.stringify({ query: query.query }),
|
|
124
|
+
});
|
|
125
|
+
const data = await response.json();
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
engineId: this.id,
|
|
129
|
+
items: data.results.map((r: any) => ({
|
|
130
|
+
title: r.title,
|
|
131
|
+
url: r.url,
|
|
132
|
+
snippet: r.snippet,
|
|
133
|
+
sourceEngine: this.id,
|
|
134
|
+
})),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 2. Create the plugin
|
|
140
|
+
const perplexityPlugin = definePlugin({
|
|
141
|
+
type: "perplexity",
|
|
142
|
+
displayName: "Perplexity AI",
|
|
143
|
+
hasLifecycle: false,
|
|
144
|
+
factory: (config) => new PerplexityProvider(config),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// 3. Export config with plugin
|
|
148
|
+
export default defineConfig({
|
|
149
|
+
plugins: [perplexityPlugin],
|
|
150
|
+
defaultEngineOrder: ["perplexity", "searxng"],
|
|
151
|
+
engines: [
|
|
152
|
+
{
|
|
153
|
+
id: "perplexity",
|
|
154
|
+
type: "perplexity", // matches plugin type
|
|
155
|
+
enabled: true,
|
|
156
|
+
apiKeyEnv: "PERPLEXITY_API_KEY",
|
|
157
|
+
endpoint: "https://api.perplexity.ai/search",
|
|
158
|
+
monthlyQuota: 1000,
|
|
159
|
+
creditCostPerSearch: 1,
|
|
160
|
+
lowCreditThresholdPercent: 10,
|
|
161
|
+
},
|
|
162
|
+
// ... other engines
|
|
163
|
+
],
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Provider Interface
|
|
168
|
+
|
|
169
|
+
Your provider must implement:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
interface ISearchProvider {
|
|
173
|
+
id: string;
|
|
174
|
+
search(query: SearchQuery): Promise<SearchResponse>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
interface SearchQuery {
|
|
178
|
+
query: string;
|
|
179
|
+
limit?: number;
|
|
180
|
+
includeRaw?: boolean;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface SearchResponse {
|
|
184
|
+
engineId: string;
|
|
185
|
+
items: SearchResultItem[];
|
|
186
|
+
raw?: unknown;
|
|
187
|
+
tookMs?: number;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
interface SearchResultItem {
|
|
191
|
+
title: string;
|
|
192
|
+
url: string;
|
|
193
|
+
snippet: string;
|
|
194
|
+
score?: number;
|
|
195
|
+
sourceEngine: string;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Plugin Helpers
|
|
200
|
+
|
|
201
|
+
- `definePlugin({ type, displayName, hasLifecycle, factory })` - Create a plugin
|
|
202
|
+
- `defineConfig({ plugins, engines, ... })` - Config with plugins
|
|
203
|
+
- `defineEngine<T>(config)` - Type-safe custom engine config
|
|
204
|
+
|
|
205
|
+
## Architecture (short)
|
|
206
|
+
|
|
207
|
+
- Config resolved and validated (`src/config`), plugins registered
|
|
208
|
+
- DI container bootstraps orchestrator, credit manager, provider registry (`src/bootstrap/container.ts`)
|
|
209
|
+
- Providers registered via plugins (`src/plugin`, `src/providers/*`)
|
|
210
|
+
- Orchestrator runs strategies (`src/core/strategy/*`) and aggregates results
|
|
211
|
+
- Docker-backed providers (SearXNG) use lifecycle manager with auto-start/health checks (`src/core/docker/*`)
|
|
212
|
+
|
|
213
|
+
## Output Formats
|
|
214
|
+
|
|
215
|
+
### Human-Readable (Default)
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Query: "rust async patterns"
|
|
219
|
+
Found 15 results
|
|
220
|
+
|
|
221
|
+
============================================================
|
|
222
|
+
tavily (10 results)
|
|
223
|
+
============================================================
|
|
224
|
+
|
|
225
|
+
1. Async programming in Rust - Tokio
|
|
226
|
+
https://tokio.rs/
|
|
227
|
+
Score: 0.95
|
|
228
|
+
Tokio is a runtime for writing reliable asynchronous applications with Rust.
|
|
229
|
+
|
|
230
|
+
2. Asynchronous Programming in Rust
|
|
231
|
+
https://rust-lang.github.io/async-book/
|
|
232
|
+
Score: 0.92
|
|
233
|
+
A book explaining async/await in Rust...
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### JSON (`--json`)
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"query": "rust async patterns",
|
|
241
|
+
"items": [
|
|
242
|
+
{
|
|
243
|
+
"title": "Async programming in Rust - Tokio",
|
|
244
|
+
"url": "https://tokio.rs/",
|
|
245
|
+
"snippet": "Tokio is a runtime...",
|
|
246
|
+
"score": 0.95,
|
|
247
|
+
"sourceEngine": "tavily"
|
|
248
|
+
}
|
|
249
|
+
],
|
|
250
|
+
"enginesTried": [
|
|
251
|
+
{
|
|
252
|
+
"engineId": "tavily",
|
|
253
|
+
"success": true
|
|
254
|
+
}
|
|
255
|
+
],
|
|
256
|
+
"credits": [...]
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Search Strategies
|
|
261
|
+
|
|
262
|
+
### All (Default)
|
|
263
|
+
|
|
264
|
+
Queries all configured/enabled providers and combines results.
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
allsearch "topic" --strategy all
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
- Pro: Gets maximum coverage, see different perspectives
|
|
271
|
+
- Con: Uses more credits, slower
|
|
272
|
+
- Best for: Research, comparison, getting API formats
|
|
273
|
+
|
|
274
|
+
### First Success
|
|
275
|
+
|
|
276
|
+
Stops after the first provider returns results.
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
allsearch "topic" --strategy first-success
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
- Pro: Saves credits, faster
|
|
283
|
+
- Con: Misses results from other providers
|
|
284
|
+
- Best for: Quick lookups, production use
|
|
285
|
+
|
|
286
|
+
## Development
|
|
287
|
+
|
|
288
|
+
### Source layout
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
src/
|
|
292
|
+
├── app/ # Public surface (bootstrap + API exports)
|
|
293
|
+
├── bootstrap/ # DI container wiring
|
|
294
|
+
├── config/ # Config types, schema, loaders
|
|
295
|
+
├── core/ # Orchestrator, strategy, credits, docker helpers
|
|
296
|
+
│ ├── docker/ # Docker compose helper, lifecycle manager
|
|
297
|
+
│ ├── paths.ts # XDG path utilities
|
|
298
|
+
│ └── ...
|
|
299
|
+
├── plugin/ # Plugin registry and built-ins
|
|
300
|
+
├── providers/ # Provider implementations + shared helpers
|
|
301
|
+
├── tool/ # CLI-facing tool + interfaces
|
|
302
|
+
└── cli.ts # CLI entry
|
|
303
|
+
|
|
304
|
+
providers/
|
|
305
|
+
└── searxng/
|
|
306
|
+
├── docker-compose.yml # SearXNG Docker config (uses env var volumes)
|
|
307
|
+
└── config/
|
|
308
|
+
└── settings.yml # Default SearXNG settings (copied to XDG on first run)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Building
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
# Bundle to dist/
|
|
315
|
+
bun run build
|
|
316
|
+
|
|
317
|
+
# Creates:
|
|
318
|
+
# dist/cli.js - Bundled CLI
|
|
319
|
+
# dist/providers/searxng/ - Docker compose + default settings
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Testing (Bun)
|
|
323
|
+
|
|
324
|
+
- All: `SKIP_DOCKER_TESTS=true bun test --preload ./test/setup.ts test/`
|
|
325
|
+
- Unit only: `bun run test:unit`
|
|
326
|
+
- Integration (Docker optional): `SKIP_DOCKER_TESTS=false bun run test:integration`
|
|
327
|
+
- Coverage: `SKIP_DOCKER_TESTS=true bun run test:coverage`
|
|
328
|
+
|
|
329
|
+
See `docs/testing/README.md` for suite layout.
|
|
330
|
+
|
|
331
|
+
## Troubleshooting
|
|
332
|
+
|
|
333
|
+
- **Missing config**: Copy `docs/config/allsearch.config.json` to `~/.config/allsearch/config.json`
|
|
334
|
+
- **Missing API key**: Set `TAVILY_API_KEY`, `BRAVE_API_KEY`, `LINKUP_API_KEY` environment variables
|
|
335
|
+
- **SearXNG not healthy**: Ensure Docker is running. Check `~/.config/allsearch/searxng/config/settings.yml` exists
|
|
336
|
+
- **SearXNG settings missing**: Run `allsearch health` once to bootstrap default config to XDG directory
|
|
337
|
+
- **Path issues after bun link**: The CLI resolves paths relative to XDG directories, not the working directory
|
|
338
|
+
|
|
339
|
+
## Providers
|
|
340
|
+
|
|
341
|
+
| Provider | Type | API Key Required | Free Tier | Notes |
|
|
342
|
+
|----------|------|------------------|-----------|-------|
|
|
343
|
+
| **SearXNG** | `searchxng` | No | Unlimited (local) | Self-hosted, Docker auto-start |
|
|
344
|
+
| **Tavily** | `tavily` | Yes | 1000/month | Best for AI/research queries |
|
|
345
|
+
| **Brave** | `brave` | Yes | 2000/month | General web search |
|
|
346
|
+
| **Linkup** | `linkup` | Yes | 1000/month | AI-powered search |
|
|
347
|
+
|
|
348
|
+
### Getting API Keys
|
|
349
|
+
|
|
350
|
+
- **Tavily**: https://tavily.com/ → Sign up → Dashboard → API Keys
|
|
351
|
+
- **Brave**: https://brave.com/search/api/ → Get Started → Create App
|
|
352
|
+
- **Linkup**: https://linkup.so/ → Sign up → API Keys
|
|
353
|
+
- **SearXNG**: No key needed (runs locally via Docker)
|
|
354
|
+
|
|
355
|
+
## Environment Variables
|
|
356
|
+
|
|
357
|
+
### API Keys (required per enabled engine)
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
# Add to ~/.bashrc, ~/.zshrc, or use a secrets manager
|
|
361
|
+
export TAVILY_API_KEY="tvly-..." # From tavily.com dashboard
|
|
362
|
+
export BRAVE_API_KEY="BSA..." # From brave.com/search/api
|
|
363
|
+
export LINKUP_API_KEY="xxxxxxxx-..." # UUID from linkup.so
|
|
364
|
+
# SEARXNG_API_KEY not needed (local Docker)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### XDG Directories (optional)
|
|
368
|
+
- `XDG_CONFIG_HOME` - Config directory (default: `~/.config`)
|
|
369
|
+
- `XDG_DATA_HOME` - Data directory (default: `~/.local/share`)
|
|
370
|
+
- `XDG_STATE_HOME` - State directory (default: `~/.local/state`)
|
|
371
|
+
|
|
372
|
+
## License
|
|
373
|
+
|
|
374
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ubersearch",
|
|
3
|
+
"version": "0.0.0-development",
|
|
4
|
+
"module": "src/index.ts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ubersearch": "src/cli.ts"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"./cli": "./src/cli.ts",
|
|
14
|
+
"./config": "./src/config/defineConfig.ts",
|
|
15
|
+
"./types": "./src/app/index.ts"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"src",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@biomejs/biome": "^2.3.8",
|
|
27
|
+
"@semantic-release/commit-analyzer": "^13.0.0",
|
|
28
|
+
"@semantic-release/github": "^11.0.1",
|
|
29
|
+
"@semantic-release/npm": "^12.0.1",
|
|
30
|
+
"@semantic-release/release-notes-generator": "^14.0.1",
|
|
31
|
+
"@types/bun": "latest",
|
|
32
|
+
"semantic-release": "^24.2.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"typescript": "^5"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"zod": "^4.1.12"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"lint": "bunx biome check .",
|
|
42
|
+
"lint:fix": "bunx biome check --write .",
|
|
43
|
+
"format": "bunx biome format --write .",
|
|
44
|
+
"test": "SKIP_DOCKER_TESTS=true bun test --preload ./test/setup.ts test/",
|
|
45
|
+
"test:unit": "bun test --preload ./test/setup.ts test/unit/",
|
|
46
|
+
"test:integration": "bun test --preload ./test/setup.ts test/integration/",
|
|
47
|
+
"test:e2e": "bun test --preload ./test/setup.ts test/e2e/",
|
|
48
|
+
"test:coverage": "SKIP_DOCKER_TESTS=true bun test --preload ./test/setup.ts --coverage test/",
|
|
49
|
+
"test:watch": "SKIP_DOCKER_TESTS=true bun test --preload ./test/setup.ts --watch test/",
|
|
50
|
+
"test:verbose": "DEBUG_TESTS=1 SKIP_DOCKER_TESTS=true bun test --preload ./test/setup.ts test/",
|
|
51
|
+
"test:docker": "SKIP_DOCKER_TESTS=false bun test --preload ./test/setup.ts test/integration/",
|
|
52
|
+
"mcp": "bun run mcp-server.ts",
|
|
53
|
+
"mcp:test": "bun run scripts/test-mcp.ts",
|
|
54
|
+
"build": "bun run scripts/build.ts",
|
|
55
|
+
"build:binary": "bun build --compile --minify src/cli.ts --outfile dist/allsearch"
|
|
56
|
+
},
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "https://github.com/briansunter/allsearch.git"
|
|
60
|
+
},
|
|
61
|
+
"bugs": {
|
|
62
|
+
"url": "https://github.com/briansunter/allsearch/issues"
|
|
63
|
+
},
|
|
64
|
+
"homepage": "https://github.com/briansunter/allsearch#readme",
|
|
65
|
+
"keywords": [
|
|
66
|
+
"search",
|
|
67
|
+
"ai",
|
|
68
|
+
"search-api",
|
|
69
|
+
"search-engine",
|
|
70
|
+
"tavily",
|
|
71
|
+
"brave",
|
|
72
|
+
"searxng"
|
|
73
|
+
],
|
|
74
|
+
"author": "Brian Sunter",
|
|
75
|
+
"license": "MIT"
|
|
76
|
+
}
|
package/src/app/index.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Public API surface for consumers importing the library (non-CLI).
|
|
2
|
+
|
|
3
|
+
export { bootstrapContainer } from "../bootstrap/container";
|
|
4
|
+
// Config helpers
|
|
5
|
+
export {
|
|
6
|
+
createConfig,
|
|
7
|
+
defineBrave,
|
|
8
|
+
defineConfig,
|
|
9
|
+
defineEngine,
|
|
10
|
+
defineLinkup,
|
|
11
|
+
definePlugin,
|
|
12
|
+
defineSearchxng,
|
|
13
|
+
defineTavily,
|
|
14
|
+
} from "../config/defineConfig";
|
|
15
|
+
export type {
|
|
16
|
+
BraveConfig,
|
|
17
|
+
EngineConfig,
|
|
18
|
+
LinkupConfig,
|
|
19
|
+
AllSearchConfig,
|
|
20
|
+
SearchxngConfig,
|
|
21
|
+
TavilyConfig,
|
|
22
|
+
} from "../config/types";
|
|
23
|
+
export type { EngineId, SearchQuery, SearchResponse, SearchResultItem } from "../core/types";
|
|
24
|
+
// Types
|
|
25
|
+
export type { AllSearchInput, AllSearchOutput, AllSearchOutputItem } from "../tool/interface";
|
|
26
|
+
export type {
|
|
27
|
+
GetCreditStatusOptions,
|
|
28
|
+
AllSearchOptions as ToolAllSearchOptions,
|
|
29
|
+
} from "../tool/multiSearchTool";
|
|
30
|
+
export { getCreditStatus, multiSearch } from "../tool/multiSearchTool";
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootstrap the Dependency Injection Container
|
|
3
|
+
*
|
|
4
|
+
* Sets up all services and their dependencies
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { loadConfig } from "../config/load";
|
|
8
|
+
import type { EngineConfig, AllSearchConfig } from "../config/types";
|
|
9
|
+
import { type Container, container } from "../core/container";
|
|
10
|
+
import { CreditManager } from "../core/credits";
|
|
11
|
+
import { FileCreditStateProvider } from "../core/credits/FileCreditStateProvider";
|
|
12
|
+
import { createLogger } from "../core/logger";
|
|
13
|
+
import { AllSearchOrchestrator } from "../core/orchestrator";
|
|
14
|
+
import type { ILifecycleProvider, SearchProvider } from "../core/provider";
|
|
15
|
+
import { ProviderRegistry } from "../core/provider";
|
|
16
|
+
import { ProviderFactory } from "../core/provider/ProviderFactory";
|
|
17
|
+
import { ServiceKeys } from "../core/serviceKeys";
|
|
18
|
+
import { StrategyFactory } from "../core/strategy/StrategyFactory";
|
|
19
|
+
import { PluginRegistry as PluginReg, registerBuiltInPlugins } from "../plugin";
|
|
20
|
+
|
|
21
|
+
const log = createLogger("Bootstrap");
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Bootstrap options
|
|
25
|
+
*/
|
|
26
|
+
export interface BootstrapOptions {
|
|
27
|
+
/** Custom credit state path (overrides config) */
|
|
28
|
+
creditStatePath?: string;
|
|
29
|
+
/** Skip plugin registration (for testing) */
|
|
30
|
+
skipPluginRegistration?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Bootstrap the DI container with all services
|
|
35
|
+
*
|
|
36
|
+
* @param configOrPath - Either a config file path (string) or a config object directly
|
|
37
|
+
* @param options - Bootstrap options (or legacy creditStatePath string)
|
|
38
|
+
*/
|
|
39
|
+
export async function bootstrapContainer(
|
|
40
|
+
configOrPath?: string | AllSearchConfig,
|
|
41
|
+
options?: BootstrapOptions | string,
|
|
42
|
+
): Promise<Container> {
|
|
43
|
+
// Clear existing registrations (useful for testing)
|
|
44
|
+
container.reset();
|
|
45
|
+
|
|
46
|
+
// Handle legacy second argument (creditStatePath)
|
|
47
|
+
const opts: BootstrapOptions =
|
|
48
|
+
typeof options === "string" ? { creditStatePath: options } : (options ?? {});
|
|
49
|
+
|
|
50
|
+
// Load or use provided configuration
|
|
51
|
+
let config: AllSearchConfig;
|
|
52
|
+
if (typeof configOrPath === "object" && configOrPath !== null) {
|
|
53
|
+
// Config object provided directly (useful for testing)
|
|
54
|
+
config = configOrPath;
|
|
55
|
+
|
|
56
|
+
// Ensure plugins are registered when config is provided directly
|
|
57
|
+
if (!opts.skipPluginRegistration) {
|
|
58
|
+
await registerBuiltInPlugins(PluginReg.getInstance());
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// Load from file (plugins registered by loadConfig)
|
|
62
|
+
config = await loadConfig(configOrPath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Register configuration as singleton
|
|
66
|
+
container.singleton(ServiceKeys.CONFIG, () => config);
|
|
67
|
+
|
|
68
|
+
// Register credit state provider
|
|
69
|
+
container.singleton(ServiceKeys.CREDIT_STATE_PROVIDER, () => {
|
|
70
|
+
const creditStatePath = opts.creditStatePath ?? config.storage?.creditStatePath;
|
|
71
|
+
return new FileCreditStateProvider(creditStatePath);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Register credit manager
|
|
75
|
+
container.singleton(ServiceKeys.CREDIT_MANAGER, () => {
|
|
76
|
+
const enabledEngines = config.engines.filter((e) => e.enabled);
|
|
77
|
+
const stateProvider = container.get<FileCreditStateProvider>(ServiceKeys.CREDIT_STATE_PROVIDER);
|
|
78
|
+
return new CreditManager(enabledEngines, stateProvider);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Register provider registry
|
|
82
|
+
container.singleton(ServiceKeys.PROVIDER_REGISTRY, () => {
|
|
83
|
+
const registry = new ProviderRegistry();
|
|
84
|
+
const failedProviders: string[] = [];
|
|
85
|
+
|
|
86
|
+
// Register all enabled providers
|
|
87
|
+
for (const engineConfig of config.engines) {
|
|
88
|
+
if (!engineConfig.enabled) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const provider = createProvider(engineConfig);
|
|
94
|
+
registry.register(provider);
|
|
95
|
+
log.info(`Registered provider: ${engineConfig.id}`);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
98
|
+
log.warn(`Failed to register provider ${engineConfig.id}: ${errorMsg}`);
|
|
99
|
+
failedProviders.push(engineConfig.id);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const availableProviders = registry.list();
|
|
104
|
+
if (availableProviders.length === 0) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`No providers could be registered. Failed providers: ${failedProviders.join(", ")}. ` +
|
|
107
|
+
"Check your configuration and environment variables.",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (failedProviders.length > 0) {
|
|
112
|
+
log.warn(`Some providers failed to initialize: ${failedProviders.join(", ")}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return registry;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Register strategy factory
|
|
119
|
+
container.singleton(ServiceKeys.STRATEGY_FACTORY, () => StrategyFactory);
|
|
120
|
+
|
|
121
|
+
// Register orchestrator
|
|
122
|
+
container.singleton(ServiceKeys.ORCHESTRATOR, () => {
|
|
123
|
+
const creditManager = container.get<CreditManager>(ServiceKeys.CREDIT_MANAGER);
|
|
124
|
+
const providerRegistry = container.get<ProviderRegistry>(ServiceKeys.PROVIDER_REGISTRY);
|
|
125
|
+
return new AllSearchOrchestrator(config, creditManager, providerRegistry);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Initialize services that need async setup
|
|
129
|
+
const creditManager = container.get<CreditManager>(ServiceKeys.CREDIT_MANAGER);
|
|
130
|
+
await creditManager.initialize();
|
|
131
|
+
|
|
132
|
+
// Resolve provider registry to trigger validation (throws if no providers registered)
|
|
133
|
+
container.get<ProviderRegistry>(ServiceKeys.PROVIDER_REGISTRY);
|
|
134
|
+
|
|
135
|
+
return container;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create a provider instance based on engine configuration
|
|
140
|
+
*/
|
|
141
|
+
function createProvider(engineConfig: EngineConfig): SearchProvider {
|
|
142
|
+
return ProviderFactory.createProvider(engineConfig, container);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Helper function to check if a provider implements ILifecycleProvider
|
|
147
|
+
*/
|
|
148
|
+
export function isLifecycleProvider(provider: unknown): provider is ILifecycleProvider {
|
|
149
|
+
return (
|
|
150
|
+
provider != null &&
|
|
151
|
+
typeof provider === "object" &&
|
|
152
|
+
"init" in provider &&
|
|
153
|
+
typeof provider.init === "function" &&
|
|
154
|
+
"healthcheck" in provider &&
|
|
155
|
+
typeof provider.healthcheck === "function"
|
|
156
|
+
);
|
|
157
|
+
}
|