ubersearch 1.1.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/cli.ts +22 -4
- package/src/config/load.ts +2 -2
- package/src/core/container.ts +2 -2
- package/src/core/orchestrator.ts +8 -4
- package/src/core/strategy/AllProvidersStrategy.ts +10 -1
- package/src/core/strategy/FirstSuccessStrategy.ts +1 -0
- package/src/core/strategy/ISearchStrategy.ts +3 -0
- package/src/core/strategy/index.ts +0 -1
- package/src/core/types.ts +2 -0
- package/src/mcp-server.ts +15 -0
- package/src/plugin/builtin.ts +3 -3
- package/src/plugin/types.ts +3 -3
- package/src/providers/brave.ts +1 -1
- package/src/providers/searchxng.ts +9 -2
- package/src/tool/interface.ts +3 -0
- package/src/tool/uberSearchTool.ts +1 -0
- package/src/core/strategy/types.ts +0 -56
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ubersearch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"module": "src/index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"ubersearch": "src/cli.ts"
|
|
7
|
+
"ubersearch": "src/cli.ts",
|
|
8
|
+
"ubersearch-mcp": "src/mcp-server.ts"
|
|
8
9
|
},
|
|
9
10
|
"exports": {
|
|
10
11
|
".": {
|
package/src/cli.ts
CHANGED
|
@@ -43,20 +43,27 @@ ARGUMENTS:
|
|
|
43
43
|
OPTIONS:
|
|
44
44
|
--json Output results as JSON
|
|
45
45
|
--engines <engine,list> Use specific engines (comma-separated)
|
|
46
|
+
--categories <cat,list> SearXNG categories (comma-separated)
|
|
46
47
|
--strategy <strategy> Search strategy: 'all' or 'first-success' (default: all)
|
|
47
48
|
--limit <number> Maximum results per engine
|
|
48
49
|
--include-raw Include raw provider responses
|
|
49
50
|
--config <path> Path to configuration file
|
|
50
51
|
--help, -h Show this help message
|
|
51
52
|
|
|
53
|
+
CATEGORIES (SearXNG):
|
|
54
|
+
general Web search (brave, duckduckgo, startpage, qwant)
|
|
55
|
+
it Tech (github, stackoverflow, npm, pypi, huggingface)
|
|
56
|
+
science Academic (arxiv, google_scholar)
|
|
57
|
+
news News (hackernews, reddit, bbc)
|
|
58
|
+
videos Video (youtube)
|
|
59
|
+
|
|
52
60
|
EXAMPLES:
|
|
53
61
|
ubersearch "best TypeScript ORM 2025"
|
|
54
62
|
ubersearch "llm observability" --engines tavily,brave --json
|
|
63
|
+
ubersearch "typescript error" --categories it
|
|
64
|
+
ubersearch "machine learning" --categories science,it
|
|
55
65
|
ubersearch "hawaii dev meetups" --strategy first-success
|
|
56
66
|
ubersearch credits
|
|
57
|
-
ubersearch health
|
|
58
|
-
ubersearch --config /path/to/config.json credits
|
|
59
|
-
ubersearch "query" --config /path/to/config.json
|
|
60
67
|
|
|
61
68
|
CONFIGURATION:
|
|
62
69
|
Config files are searched in order:
|
|
@@ -91,6 +98,7 @@ const options = {
|
|
|
91
98
|
engines: undefined as string[] | undefined,
|
|
92
99
|
strategy: undefined as "all" | "first-success" | undefined,
|
|
93
100
|
limit: undefined as number | undefined,
|
|
101
|
+
categories: undefined as string[] | undefined,
|
|
94
102
|
};
|
|
95
103
|
|
|
96
104
|
// Parse --engines
|
|
@@ -128,9 +136,18 @@ if (limitIdx !== -1) {
|
|
|
128
136
|
}
|
|
129
137
|
}
|
|
130
138
|
|
|
139
|
+
// Parse --categories
|
|
140
|
+
const categoriesIdx = args.indexOf("--categories");
|
|
141
|
+
if (categoriesIdx !== -1) {
|
|
142
|
+
const categoriesArg = args[categoriesIdx + 1];
|
|
143
|
+
if (categoriesArg !== undefined) {
|
|
144
|
+
options.categories = categoriesArg.split(",").map((c) => c.trim());
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
131
148
|
// Extract query (non-option arguments)
|
|
132
149
|
// Filter out option flags (--*) and their values
|
|
133
|
-
const optionsWithValues = ["--engines", "--strategy", "--limit", "--config"];
|
|
150
|
+
const optionsWithValues = ["--engines", "--strategy", "--limit", "--config", "--categories"];
|
|
134
151
|
const queryParts = args.filter((arg, idx) => {
|
|
135
152
|
// Skip arguments starting with --
|
|
136
153
|
if (arg.startsWith("--")) {
|
|
@@ -169,6 +186,7 @@ async function main() {
|
|
|
169
186
|
engines: options.engines,
|
|
170
187
|
includeRaw: options.includeRaw,
|
|
171
188
|
strategy: options.strategy,
|
|
189
|
+
categories: options.categories,
|
|
172
190
|
},
|
|
173
191
|
{ containerOverride: container },
|
|
174
192
|
);
|
package/src/config/load.ts
CHANGED
|
@@ -235,7 +235,7 @@ function getDefaultConfig(): ExtendedSearchConfig {
|
|
|
235
235
|
displayName: "Brave Search",
|
|
236
236
|
apiKeyEnv: "BRAVE_API_KEY",
|
|
237
237
|
endpoint: "https://api.search.brave.com/res/v1/web/search",
|
|
238
|
-
defaultLimit:
|
|
238
|
+
defaultLimit: 15,
|
|
239
239
|
monthlyQuota: 1000,
|
|
240
240
|
creditCostPerSearch: 1,
|
|
241
241
|
lowCreditThresholdPercent: 80,
|
|
@@ -273,7 +273,7 @@ function getDefaultConfig(): ExtendedSearchConfig {
|
|
|
273
273
|
composeFile,
|
|
274
274
|
containerName: "searxng",
|
|
275
275
|
healthEndpoint: "http://localhost:8888/healthz",
|
|
276
|
-
defaultLimit:
|
|
276
|
+
defaultLimit: 15,
|
|
277
277
|
monthlyQuota: 10000,
|
|
278
278
|
creditCostPerSearch: 0,
|
|
279
279
|
lowCreditThresholdPercent: 80,
|
package/src/core/container.ts
CHANGED
|
@@ -127,7 +127,7 @@ export class Container {
|
|
|
127
127
|
|
|
128
128
|
// Handle singleton caching
|
|
129
129
|
if (binding.singleton && binding.cached !== undefined) {
|
|
130
|
-
return binding.cached;
|
|
130
|
+
return binding.cached as T;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// Track resolution for circular dependency detection
|
|
@@ -142,7 +142,7 @@ export class Container {
|
|
|
142
142
|
binding.cached = instance;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
return instance;
|
|
145
|
+
return instance as T;
|
|
146
146
|
} catch (error) {
|
|
147
147
|
// Enhance error message with context
|
|
148
148
|
if (error instanceof Error) {
|
package/src/core/orchestrator.ts
CHANGED
|
@@ -23,6 +23,9 @@ export interface UberSearchOptions {
|
|
|
23
23
|
|
|
24
24
|
/** Search strategy */
|
|
25
25
|
strategy?: "all" | "first-success";
|
|
26
|
+
|
|
27
|
+
/** SearXNG categories to search (e.g., ["general", "it", "science"]) */
|
|
28
|
+
categories?: string[];
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export interface EngineAttempt {
|
|
@@ -54,12 +57,13 @@ export class UberSearchOrchestrator {
|
|
|
54
57
|
|
|
55
58
|
/**
|
|
56
59
|
* Determine the engine order to use for this search
|
|
60
|
+
* Only includes engines that are actually registered in the provider registry
|
|
57
61
|
*/
|
|
58
62
|
private getEngineOrder(override?: EngineId[]): EngineId[] {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return this.
|
|
63
|
+
const baseOrder = override?.length ? override : this.config.defaultEngineOrder;
|
|
64
|
+
|
|
65
|
+
// Filter to only include engines that are registered (have valid providers)
|
|
66
|
+
return baseOrder.filter((engineId) => this.registry.get(engineId) !== undefined);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
/**
|
|
@@ -92,6 +92,7 @@ export class AllProvidersStrategy implements ISearchStrategy {
|
|
|
92
92
|
query,
|
|
93
93
|
limit: options.limit,
|
|
94
94
|
includeRaw: options.includeRaw,
|
|
95
|
+
categories: options.categories,
|
|
95
96
|
});
|
|
96
97
|
|
|
97
98
|
// Deduct credits
|
|
@@ -175,6 +176,7 @@ export class AllProvidersStrategy implements ISearchStrategy {
|
|
|
175
176
|
query,
|
|
176
177
|
limit: options.limit,
|
|
177
178
|
includeRaw: options.includeRaw,
|
|
179
|
+
categories: options.categories,
|
|
178
180
|
});
|
|
179
181
|
return { engineId, response };
|
|
180
182
|
} catch (error) {
|
|
@@ -189,7 +191,14 @@ export class AllProvidersStrategy implements ISearchStrategy {
|
|
|
189
191
|
// Process results in original order (maintain engine priority)
|
|
190
192
|
for (let i = 0; i < searchResults.length; i++) {
|
|
191
193
|
const settledResult = searchResults[i];
|
|
192
|
-
const
|
|
194
|
+
const eligibleEngine = eligibleEngines[i];
|
|
195
|
+
|
|
196
|
+
// Safety check (should always exist, but TypeScript needs this)
|
|
197
|
+
if (!settledResult || !eligibleEngine) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const { engineId } = eligibleEngine;
|
|
193
202
|
|
|
194
203
|
if (settledResult.status === "rejected") {
|
|
195
204
|
// Promise itself was rejected (shouldn't happen with our try/catch, but handle it)
|
package/src/core/types.ts
CHANGED
package/src/mcp-server.ts
CHANGED
|
@@ -82,6 +82,17 @@ export async function serve() {
|
|
|
82
82
|
description: "Maximum results per engine",
|
|
83
83
|
default: 10,
|
|
84
84
|
},
|
|
85
|
+
categories: {
|
|
86
|
+
type: "string",
|
|
87
|
+
description: `SearXNG categories (comma-separated). Available categories:
|
|
88
|
+
- general: Web search (brave, duckduckgo, startpage, qwant)
|
|
89
|
+
- it: Tech (github, stackoverflow, npm, pypi, huggingface)
|
|
90
|
+
- science: Academic (arxiv, google_scholar)
|
|
91
|
+
- news: News (hackernews, reddit, bbc)
|
|
92
|
+
- videos: Video (youtube)
|
|
93
|
+
Example: "it,science" for tech and academic results`,
|
|
94
|
+
default: "",
|
|
95
|
+
},
|
|
85
96
|
},
|
|
86
97
|
required: ["query"],
|
|
87
98
|
},
|
|
@@ -165,12 +176,16 @@ export async function serve() {
|
|
|
165
176
|
const engines = args.engines
|
|
166
177
|
? args.engines.split(",").map((e: string) => e.trim())
|
|
167
178
|
: undefined;
|
|
179
|
+
const categories = args.categories
|
|
180
|
+
? args.categories.split(",").map((c: string) => c.trim())
|
|
181
|
+
: undefined;
|
|
168
182
|
result = await withTimeout(
|
|
169
183
|
uberSearch({
|
|
170
184
|
query: args.query,
|
|
171
185
|
limit: args.limit,
|
|
172
186
|
engines,
|
|
173
187
|
strategy: args.strategy,
|
|
188
|
+
categories,
|
|
174
189
|
}),
|
|
175
190
|
60000,
|
|
176
191
|
"uberSearch",
|
package/src/plugin/builtin.ts
CHANGED
|
@@ -77,14 +77,14 @@ export const searchxngPlugin: PluginDefinition<SearchxngConfig, SearchxngProvide
|
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* All built-in plugins
|
|
80
|
-
* Using
|
|
80
|
+
* Using type assertion since specific configs/providers extend base types
|
|
81
81
|
*/
|
|
82
|
-
export const builtInPlugins
|
|
82
|
+
export const builtInPlugins = [
|
|
83
83
|
tavilyPlugin,
|
|
84
84
|
bravePlugin,
|
|
85
85
|
linkupPlugin,
|
|
86
86
|
searchxngPlugin,
|
|
87
|
-
];
|
|
87
|
+
] as unknown as PluginDefinition<EngineConfigBase, SearchProvider>[];
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* Register all built-in plugins with the registry
|
package/src/plugin/types.ts
CHANGED
|
@@ -119,12 +119,12 @@ export interface PluginDefinition<
|
|
|
119
119
|
/**
|
|
120
120
|
* Helper type to extract config type from a plugin definition
|
|
121
121
|
*/
|
|
122
|
-
export type PluginConfig<T> = T extends PluginDefinition<infer C,
|
|
122
|
+
export type PluginConfig<T> = T extends PluginDefinition<infer C, SearchProvider> ? C : never;
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
125
|
* Helper type to extract provider type from a plugin definition
|
|
126
126
|
*/
|
|
127
|
-
export type PluginProvider<T> = T extends PluginDefinition<
|
|
127
|
+
export type PluginProvider<T> = T extends PluginDefinition<EngineConfigBase, infer P> ? P : never;
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
130
|
* Registration options when adding a plugin to the registry
|
|
@@ -190,7 +190,7 @@ export function isLifecycleProvider(
|
|
|
190
190
|
if (provider == null || typeof provider !== "object") {
|
|
191
191
|
return false;
|
|
192
192
|
}
|
|
193
|
-
const obj = provider as Record<string, unknown>;
|
|
193
|
+
const obj = provider as unknown as Record<string, unknown>;
|
|
194
194
|
return (
|
|
195
195
|
"init" in provider &&
|
|
196
196
|
"healthcheck" in provider &&
|
package/src/providers/brave.ts
CHANGED
|
@@ -21,7 +21,7 @@ export class BraveProvider extends BaseProvider<BraveConfig> {
|
|
|
21
21
|
async search(query: SearchQuery): Promise<SearchResponse> {
|
|
22
22
|
const apiKey = this.getApiKey();
|
|
23
23
|
|
|
24
|
-
const limit = query.limit ?? this.config.defaultLimit ??
|
|
24
|
+
const limit = query.limit ?? this.config.defaultLimit ?? 15;
|
|
25
25
|
const url = buildUrl(this.config.endpoint, {
|
|
26
26
|
q: query.query,
|
|
27
27
|
count: limit,
|
|
@@ -102,13 +102,20 @@ export class SearchxngProvider
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
const limit = query.limit ?? this.defaultLimit;
|
|
105
|
-
const
|
|
105
|
+
const params: Record<string, string | number> = {
|
|
106
106
|
q: query.query,
|
|
107
107
|
format: "json",
|
|
108
108
|
language: "all",
|
|
109
109
|
pageno: 1,
|
|
110
110
|
safesearch: 0,
|
|
111
|
-
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Add categories if specified (e.g., "general,it,science")
|
|
114
|
+
if (query.categories && query.categories.length > 0) {
|
|
115
|
+
params.categories = query.categories.join(",");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const url = buildUrl(this.config.endpoint, params);
|
|
112
119
|
|
|
113
120
|
const { data: json, tookMs } = await fetchWithErrorHandling<SearxngApiResponse>(
|
|
114
121
|
this.id,
|
package/src/tool/interface.ts
CHANGED
|
@@ -24,6 +24,9 @@ export interface UberSearchInput {
|
|
|
24
24
|
* When false (default), providers are queried sequentially
|
|
25
25
|
*/
|
|
26
26
|
parallel?: boolean;
|
|
27
|
+
|
|
28
|
+
/** SearXNG categories to search (e.g., ["general", "it", "science"]) */
|
|
29
|
+
categories?: string[];
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export interface UberSearchEngineAttempt {
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Strategy interface and types for search execution patterns
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { CreditManager } from "../credits";
|
|
6
|
-
import type { ProviderRegistry } from "../provider";
|
|
7
|
-
import type { EngineId, SearchQuery, SearchResultItem } from "../types";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Context passed to search strategies containing dependencies
|
|
11
|
-
*/
|
|
12
|
-
export interface StrategyContext {
|
|
13
|
-
/** Provider registry for accessing search providers */
|
|
14
|
-
registry: ProviderRegistry;
|
|
15
|
-
/** Credit manager for checking and charging credits */
|
|
16
|
-
credits: CreditManager;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Result from a search strategy execution
|
|
21
|
-
*/
|
|
22
|
-
export interface StrategyResult {
|
|
23
|
-
/** Search results from successful providers */
|
|
24
|
-
results: SearchResultItem[];
|
|
25
|
-
/** Metadata about engine attempts */
|
|
26
|
-
attempts: EngineAttempt[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Engine attempt metadata
|
|
31
|
-
*/
|
|
32
|
-
export interface EngineAttempt {
|
|
33
|
-
engineId: EngineId;
|
|
34
|
-
success: boolean;
|
|
35
|
-
reason?: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Interface for implementing different search execution strategies
|
|
40
|
-
*/
|
|
41
|
-
export interface ISearchStrategy {
|
|
42
|
-
/**
|
|
43
|
-
* Execute search using this strategy
|
|
44
|
-
* @param query - The search query
|
|
45
|
-
* @param engineIds - Ordered list of engine IDs to try
|
|
46
|
-
* @param options - Search options (limit, includeRaw, etc.)
|
|
47
|
-
* @param context - Strategy context with dependencies
|
|
48
|
-
* @returns Promise resolving to strategy result
|
|
49
|
-
*/
|
|
50
|
-
execute(
|
|
51
|
-
query: string,
|
|
52
|
-
engineIds: EngineId[],
|
|
53
|
-
options: SearchQuery,
|
|
54
|
-
context: StrategyContext,
|
|
55
|
-
): Promise<StrategyResult>;
|
|
56
|
-
}
|