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 CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "ubersearch",
3
- "version": "1.1.3",
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
  );
@@ -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: 10,
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: 10,
276
+ defaultLimit: 15,
277
277
  monthlyQuota: 10000,
278
278
  creditCostPerSearch: 0,
279
279
  lowCreditThresholdPercent: 80,
@@ -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) {
@@ -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
- if (override?.length) {
60
- return override;
61
- }
62
- return this.config.defaultEngineOrder;
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 { engineId } = eligibleEngines[i];
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)
@@ -64,6 +64,7 @@ export class FirstSuccessStrategy implements ISearchStrategy {
64
64
  query,
65
65
  limit: options.limit,
66
66
  includeRaw: options.includeRaw,
67
+ categories: options.categories,
67
68
  });
68
69
 
69
70
  // Deduct credits
@@ -42,6 +42,9 @@ export interface UberSearchOptions {
42
42
  * When false (default), providers are queried sequentially
43
43
  */
44
44
  parallel?: boolean;
45
+
46
+ /** SearXNG categories to search (e.g., ["general", "it", "science"]) */
47
+ categories?: string[];
45
48
  }
46
49
 
47
50
  /**
@@ -6,4 +6,3 @@ export * from "./AllProvidersStrategy";
6
6
  export * from "./FirstSuccessStrategy";
7
7
  export * from "./ISearchStrategy";
8
8
  export * from "./StrategyFactory";
9
- export * from "./types";
package/src/core/types.ts CHANGED
@@ -8,6 +8,8 @@ export interface SearchQuery {
8
8
  query: string;
9
9
  limit?: number;
10
10
  includeRaw?: boolean;
11
+ /** SearXNG categories to search (e.g., "general", "it", "science") */
12
+ categories?: string[];
11
13
  }
12
14
 
13
15
  export interface SearchResultItem {
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",
@@ -77,14 +77,14 @@ export const searchxngPlugin: PluginDefinition<SearchxngConfig, SearchxngProvide
77
77
 
78
78
  /**
79
79
  * All built-in plugins
80
- * Using base types since specific configs/providers extend them
80
+ * Using type assertion since specific configs/providers extend base types
81
81
  */
82
- export const builtInPlugins: PluginDefinition<EngineConfigBase, SearchProvider>[] = [
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
@@ -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, unknown> ? C : never;
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<unknown, infer P> ? P : never;
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 &&
@@ -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 ?? 10;
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 url = buildUrl(this.config.endpoint, {
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,
@@ -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 {
@@ -55,6 +55,7 @@ export async function uberSearch(
55
55
  engineOrderOverride: input.engines,
56
56
  includeRaw: input.includeRaw,
57
57
  strategy: input.strategy ?? "all",
58
+ categories: input.categories,
58
59
  });
59
60
 
60
61
  // Format output
@@ -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
- }