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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +374 -0
  3. package/package.json +76 -0
  4. package/src/app/index.ts +30 -0
  5. package/src/bootstrap/container.ts +157 -0
  6. package/src/cli.ts +380 -0
  7. package/src/config/defineConfig.ts +176 -0
  8. package/src/config/load.ts +368 -0
  9. package/src/config/types.ts +86 -0
  10. package/src/config/validation.ts +148 -0
  11. package/src/core/cache.ts +74 -0
  12. package/src/core/container.ts +268 -0
  13. package/src/core/credits/CreditManager.ts +158 -0
  14. package/src/core/credits/CreditStateProvider.ts +151 -0
  15. package/src/core/credits/FileCreditStateProvider.ts +137 -0
  16. package/src/core/credits/index.ts +3 -0
  17. package/src/core/docker/dockerComposeHelper.ts +177 -0
  18. package/src/core/docker/dockerLifecycleManager.ts +361 -0
  19. package/src/core/docker/index.ts +8 -0
  20. package/src/core/logger.ts +146 -0
  21. package/src/core/orchestrator.ts +103 -0
  22. package/src/core/paths.ts +157 -0
  23. package/src/core/provider/ILifecycleProvider.ts +120 -0
  24. package/src/core/provider/ProviderFactory.ts +120 -0
  25. package/src/core/provider.ts +61 -0
  26. package/src/core/serviceKeys.ts +45 -0
  27. package/src/core/strategy/AllProvidersStrategy.ts +245 -0
  28. package/src/core/strategy/FirstSuccessStrategy.ts +98 -0
  29. package/src/core/strategy/ISearchStrategy.ts +94 -0
  30. package/src/core/strategy/StrategyFactory.ts +204 -0
  31. package/src/core/strategy/index.ts +9 -0
  32. package/src/core/strategy/types.ts +56 -0
  33. package/src/core/types.ts +58 -0
  34. package/src/index.ts +1 -0
  35. package/src/plugin/PluginRegistry.ts +336 -0
  36. package/src/plugin/builtin.ts +130 -0
  37. package/src/plugin/index.ts +33 -0
  38. package/src/plugin/types.ts +212 -0
  39. package/src/providers/BaseProvider.ts +49 -0
  40. package/src/providers/brave.ts +66 -0
  41. package/src/providers/constants.ts +13 -0
  42. package/src/providers/helpers/index.ts +24 -0
  43. package/src/providers/helpers/lifecycleHelpers.ts +110 -0
  44. package/src/providers/helpers/resultMappers.ts +168 -0
  45. package/src/providers/index.ts +6 -0
  46. package/src/providers/linkup.ts +114 -0
  47. package/src/providers/retry.ts +95 -0
  48. package/src/providers/searchxng.ts +163 -0
  49. package/src/providers/tavily.ts +73 -0
  50. package/src/providers/types/index.ts +185 -0
  51. package/src/providers/utils.ts +182 -0
  52. package/src/tool/allSearchTool.ts +110 -0
  53. package/src/tool/interface.ts +71 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Docker Compose Helper
3
+ *
4
+ * Manages Docker Compose services for local providers
5
+ */
6
+
7
+ import { exec } from "node:child_process";
8
+ import { existsSync } from "node:fs";
9
+ import { promisify } from "node:util";
10
+ import { getSearxngPaths } from "../paths";
11
+
12
+ const execAsync = promisify(exec);
13
+
14
+ export interface DockerComposeOptions {
15
+ cwd?: string;
16
+ timeout?: number;
17
+ }
18
+
19
+ /**
20
+ * Helper for managing Docker Compose services
21
+ */
22
+ export class DockerComposeHelper {
23
+ constructor(private composeFile: string) {}
24
+
25
+ /**
26
+ * Execute docker compose command
27
+ */
28
+ private async execDockerCompose(
29
+ args: string[],
30
+ options: DockerComposeOptions = {},
31
+ ): Promise<string> {
32
+ const cmd = `docker compose -f "${this.composeFile}" ${args.join(" ")}`;
33
+ const cwd = options.cwd || this.getComposeDir();
34
+ const timeout = options.timeout || 30000;
35
+
36
+ try {
37
+ // Get SearXNG paths and ensure directories exist
38
+ const { configDir, dataDir } = getSearxngPaths();
39
+
40
+ const { stdout, stderr } = await execAsync(cmd, {
41
+ cwd,
42
+ timeout,
43
+ env: {
44
+ ...process.env,
45
+ SEARXNG_CONFIG: configDir,
46
+ SEARXNG_DATA: dataDir,
47
+ },
48
+ });
49
+
50
+ if (stderr && !stderr.includes("warning")) {
51
+ console.warn(`Docker Compose warning: ${stderr}`);
52
+ }
53
+
54
+ return stdout;
55
+ } catch (error: unknown) {
56
+ const err = error as { message?: string; stdout?: string; stderr?: string };
57
+ const errorMessage = err.message ?? "Unknown error";
58
+ const timedOut = errorMessage.includes("timed out") || errorMessage.includes("ETIMEDOUT");
59
+
60
+ const errorDetails = [
61
+ `Docker Compose command ${timedOut ? "timed out" : "failed"}: ${errorMessage}`,
62
+ `Command: ${cmd}`,
63
+ ];
64
+ if (err.stdout) {
65
+ errorDetails.push(`Output: ${err.stdout}`);
66
+ }
67
+ if (err.stderr) {
68
+ errorDetails.push(`Error: ${err.stderr}`);
69
+ }
70
+
71
+ throw new Error(errorDetails.join("\n"));
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Get directory containing compose file
77
+ */
78
+ private getComposeDir(): string {
79
+ return require("node:path").dirname(this.composeFile);
80
+ }
81
+
82
+ /**
83
+ * Start services
84
+ * @param services Optional list of services to start
85
+ */
86
+ async up(services?: string[], options?: DockerComposeOptions): Promise<void> {
87
+ const args = ["up", "-d"];
88
+ if (services && services.length > 0) {
89
+ args.push(...services);
90
+ }
91
+
92
+ await this.execDockerCompose(args, options);
93
+ }
94
+
95
+ /**
96
+ * Stop services
97
+ * @param services Optional list of services to stop
98
+ */
99
+ async stop(services?: string[], options?: DockerComposeOptions): Promise<void> {
100
+ const args = ["stop"];
101
+ if (services && services.length > 0) {
102
+ args.push(...services);
103
+ }
104
+
105
+ await this.execDockerCompose(args, options);
106
+ }
107
+
108
+ /**
109
+ * Stop and remove containers
110
+ */
111
+ async down(options?: DockerComposeOptions): Promise<void> {
112
+ await this.execDockerCompose(["down"], options);
113
+ }
114
+
115
+ /**
116
+ * Get service logs
117
+ * @param services Optional list of services
118
+ * @param tail Number of lines to show (default: 50)
119
+ */
120
+ async logs(
121
+ services?: string[],
122
+ tail: number = 50,
123
+ options?: DockerComposeOptions,
124
+ ): Promise<string> {
125
+ const args = ["logs", "--tail", String(tail)];
126
+ if (services && services.length > 0) {
127
+ args.push(...services);
128
+ }
129
+
130
+ return await this.execDockerCompose(args, options);
131
+ }
132
+
133
+ /**
134
+ * List running services
135
+ */
136
+ async ps(options?: DockerComposeOptions): Promise<string> {
137
+ return await this.execDockerCompose(["ps"], options);
138
+ }
139
+
140
+ /**
141
+ * Check if services are running
142
+ * @param service Optional specific service to check
143
+ */
144
+ async isRunning(service?: string, options?: DockerComposeOptions): Promise<boolean> {
145
+ try {
146
+ const output = await this.ps(options);
147
+
148
+ if (service) {
149
+ return output.includes(service) && !output.includes("Exit");
150
+ }
151
+
152
+ // Check if any services are running
153
+ return output.includes("Up");
154
+ } catch (_error) {
155
+ return false;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Check if Docker is available
161
+ */
162
+ static async isDockerAvailable(): Promise<boolean> {
163
+ try {
164
+ await execAsync("docker version", { timeout: 5000 });
165
+ return true;
166
+ } catch {
167
+ return false;
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Check if compose file exists
173
+ */
174
+ composeFileExists(): boolean {
175
+ return existsSync(this.composeFile);
176
+ }
177
+ }
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Docker Lifecycle Manager
3
+ *
4
+ * Handles Docker container lifecycle operations independently from search logic.
5
+ * Enables composition over inheritance for providers that need Docker management.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const dockerManager = new DockerLifecycleManager({
10
+ * composeFile: './docker-compose.yml',
11
+ * containerName: 'my-service',
12
+ * healthEndpoint: 'http://localhost:8080/health',
13
+ * autoStart: true,
14
+ * autoStop: true
15
+ * });
16
+ *
17
+ * await dockerManager.init();
18
+ * const isHealthy = await dockerManager.healthcheck();
19
+ * await dockerManager.shutdown();
20
+ * ```
21
+ */
22
+
23
+ import { createLogger } from "../logger";
24
+ import { bootstrapSearxngConfig } from "../paths";
25
+ import { DockerComposeHelper } from "./dockerComposeHelper";
26
+
27
+ const log = createLogger("DockerLifecycle");
28
+
29
+ export interface DockerLifecycleConfig {
30
+ containerName?: string;
31
+ composeFile?: string;
32
+ healthEndpoint?: string;
33
+ autoStart: boolean;
34
+ autoStop: boolean;
35
+ initTimeoutMs?: number;
36
+ projectRoot?: string; // Base path for resolving relative compose file paths
37
+ }
38
+
39
+ /**
40
+ * Manages Docker container lifecycle operations for providers
41
+ */
42
+ export class DockerLifecycleManager {
43
+ private config: DockerLifecycleConfig;
44
+ private dockerHelper?: DockerComposeHelper;
45
+ private initialized = false;
46
+ private initPromise: Promise<void> | null = null;
47
+
48
+ constructor(config: DockerLifecycleConfig) {
49
+ this.config = config;
50
+
51
+ // Initialize Docker helper if compose file is provided
52
+ if (config.composeFile) {
53
+ this.dockerHelper = new DockerComposeHelper(config.composeFile);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Initialize the Docker lifecycle manager
59
+ *
60
+ * Auto-starts containers if configured and performs health checks.
61
+ * Thread-safe - multiple calls return the same promise.
62
+ *
63
+ * @throws Error if container startup fails
64
+ */
65
+ async init(): Promise<void> {
66
+ if (this.initPromise) {
67
+ return this.initPromise;
68
+ }
69
+
70
+ if (!this.config.autoStart || !this.dockerHelper) {
71
+ this.initialized = true;
72
+ return;
73
+ }
74
+
75
+ this.initPromise = this.performInit().catch((error) => {
76
+ log.error("Initialization failed:", error);
77
+ this.initialized = false;
78
+ throw error;
79
+ });
80
+ return this.initPromise;
81
+ }
82
+
83
+ /**
84
+ * Wrap a promise with a timeout
85
+ */
86
+ private async withTimeout<T>(
87
+ promise: Promise<T>,
88
+ timeoutMs: number,
89
+ operation: string,
90
+ ): Promise<T> {
91
+ const timeoutPromise = new Promise<never>((_, reject) => {
92
+ setTimeout(() => reject(new Error(`${operation} timed out after ${timeoutMs}ms`)), timeoutMs);
93
+ });
94
+
95
+ return Promise.race([promise, timeoutPromise]);
96
+ }
97
+
98
+ private async performInit(): Promise<void> {
99
+ if (!this.dockerHelper) {
100
+ return;
101
+ }
102
+
103
+ // Bootstrap SearXNG config if needed (copy default settings.yml)
104
+ bootstrapSearxngConfig();
105
+
106
+ // Check if Docker is available (with timeout)
107
+ const dockerAvailable = await this.withTimeout(
108
+ DockerComposeHelper.isDockerAvailable(),
109
+ 10000,
110
+ "Docker availability check",
111
+ );
112
+ if (!dockerAvailable) {
113
+ log.warn("Docker is not available. Cannot auto-start container.");
114
+ this.initialized = true;
115
+ return;
116
+ }
117
+
118
+ // Check if container is already running (with timeout)
119
+ const isRunning = await this.withTimeout(this.healthcheck(), 5000, "Initial health check");
120
+ if (isRunning) {
121
+ log.info("Container is already running.");
122
+ this.initialized = true;
123
+ return;
124
+ }
125
+
126
+ // Auto-start container
127
+ log.info("Starting Docker container...");
128
+ try {
129
+ // Run from project root to ensure correct path resolution
130
+ const projectRoot = this.config.projectRoot || process.cwd();
131
+ await this.dockerHelper.up(
132
+ this.config.containerName ? [this.config.containerName] : undefined,
133
+ { cwd: projectRoot },
134
+ );
135
+ log.info("Container started successfully.");
136
+
137
+ // Wait for health check if endpoint is configured
138
+ if (this.config.healthEndpoint) {
139
+ await this.waitForHealth();
140
+ }
141
+
142
+ this.initialized = true;
143
+ } catch (error) {
144
+ log.error("Failed to start container:", error);
145
+ throw error;
146
+ } finally {
147
+ this.initPromise = null;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Check if container is healthy
153
+ *
154
+ * Checks both container running status and health endpoint if configured.
155
+ *
156
+ * @returns true if container is healthy, false otherwise
157
+ */
158
+ async healthcheck(): Promise<boolean> {
159
+ // Check if container is running
160
+ if (this.dockerHelper) {
161
+ const projectRoot = this.config.projectRoot || process.cwd();
162
+ const isRunning = await this.dockerHelper.isRunning(this.config.containerName, {
163
+ cwd: projectRoot,
164
+ });
165
+ if (!isRunning) {
166
+ return false;
167
+ }
168
+ }
169
+
170
+ // Check health endpoint if configured
171
+ if (this.config.healthEndpoint) {
172
+ try {
173
+ const controller = new AbortController();
174
+ const timeoutId = setTimeout(() => controller.abort(), 3000);
175
+
176
+ const response = await fetch(this.config.healthEndpoint, {
177
+ signal: controller.signal,
178
+ });
179
+
180
+ clearTimeout(timeoutId);
181
+ return response.ok;
182
+ } catch {
183
+ return false;
184
+ }
185
+ }
186
+
187
+ // If no health endpoint, assume healthy if initialized
188
+ return this.initialized;
189
+ }
190
+
191
+ /**
192
+ * Wait for health endpoint to be ready
193
+ */
194
+ private async waitForHealth(timeoutMs: number = 30000): Promise<void> {
195
+ if (!this.config.healthEndpoint) {
196
+ return;
197
+ }
198
+
199
+ log.info("Waiting for health check...");
200
+
201
+ const startTime = Date.now();
202
+ while (Date.now() - startTime < timeoutMs) {
203
+ if (await this.healthcheck()) {
204
+ log.info("Health check passed.");
205
+ return;
206
+ }
207
+
208
+ await this.sleep(1000);
209
+ }
210
+
211
+ throw new Error(
212
+ `[DockerLifecycleManager] Health check failed after ${timeoutMs}ms. ` +
213
+ `Endpoint: ${this.config.healthEndpoint}`,
214
+ );
215
+ }
216
+
217
+ /**
218
+ * Shutdown Docker lifecycle manager
219
+ *
220
+ * Stops containers if autoStop is enabled. Errors during shutdown are logged
221
+ * but not thrown to prevent cleanup failures from propagating.
222
+ */
223
+ async shutdown(): Promise<void> {
224
+ if (!this.config.autoStop || !this.dockerHelper) {
225
+ return;
226
+ }
227
+
228
+ const projectRoot = this.config.projectRoot || process.cwd();
229
+ const isRunning = await this.dockerHelper.isRunning(this.config.containerName, {
230
+ cwd: projectRoot,
231
+ });
232
+ if (!isRunning) {
233
+ return;
234
+ }
235
+
236
+ log.info("Stopping Docker container...");
237
+ try {
238
+ await this.dockerHelper.stop(
239
+ this.config.containerName ? [this.config.containerName] : undefined,
240
+ { cwd: projectRoot },
241
+ );
242
+ log.info("Container stopped.");
243
+ } catch (error) {
244
+ log.error("Failed to stop container:", error);
245
+ // Don't throw on shutdown errors
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Validate Docker configuration
251
+ *
252
+ * Performs comprehensive validation of Docker configuration including:
253
+ * - Docker availability
254
+ * - Compose file existence and validity
255
+ * - Health endpoint URL validation
256
+ * - Container name format validation
257
+ *
258
+ * @returns Validation results with errors and warnings
259
+ */
260
+ async validateDockerConfig(): Promise<{
261
+ valid: boolean;
262
+ errors: string[];
263
+ warnings: string[];
264
+ }> {
265
+ const errors: string[] = [];
266
+ const warnings: string[] = [];
267
+
268
+ // Check Docker is available
269
+ const dockerAvailable = await DockerComposeHelper.isDockerAvailable();
270
+ if (!dockerAvailable) {
271
+ errors.push("Docker is not available or not running");
272
+ return { valid: false, errors, warnings };
273
+ }
274
+
275
+ // Check compose file exists
276
+ if (this.config.composeFile) {
277
+ const composeExists = await this.dockerHelper?.composeFileExists();
278
+ if (!composeExists) {
279
+ errors.push(`Compose file not found: ${this.config.composeFile}`);
280
+ }
281
+ }
282
+
283
+ // Check if compose file is valid
284
+ if (this.dockerHelper) {
285
+ try {
286
+ const projectRoot = this.config.projectRoot || process.cwd();
287
+ await this.dockerHelper.ps({ cwd: projectRoot });
288
+ } catch (error: unknown) {
289
+ const message = error instanceof Error ? error.message : String(error);
290
+ if (message.includes("config")) {
291
+ errors.push(`Invalid compose file: ${message}`);
292
+ }
293
+ }
294
+ }
295
+
296
+ // Check health endpoint if configured
297
+ if (this.config.healthEndpoint) {
298
+ try {
299
+ // Try to parse the URL
300
+ new URL(this.config.healthEndpoint);
301
+ } catch {
302
+ warnings.push(`Health endpoint URL is invalid: ${this.config.healthEndpoint}`);
303
+ }
304
+ }
305
+
306
+ // Check container name if specified
307
+ if (this.config.containerName && !/^[a-zA-Z0-9_-]+$/.test(this.config.containerName)) {
308
+ warnings.push(`Container name contains invalid characters: ${this.config.containerName}`);
309
+ }
310
+
311
+ return {
312
+ valid: errors.length === 0,
313
+ errors,
314
+ warnings,
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Helper: Sleep for ms milliseconds
320
+ */
321
+ private sleep(ms: number): Promise<void> {
322
+ return new Promise((resolve) => setTimeout(resolve, ms));
323
+ }
324
+
325
+ /**
326
+ * Check if lifecycle manager is initialized
327
+ */
328
+ isInitialized(): boolean {
329
+ return this.initialized;
330
+ }
331
+
332
+ /**
333
+ * Check if container is running
334
+ *
335
+ * @returns true if container is running, false otherwise
336
+ */
337
+ async isRunning(): Promise<boolean> {
338
+ if (!this.dockerHelper) {
339
+ return false;
340
+ }
341
+
342
+ try {
343
+ const projectRoot = this.config.projectRoot || process.cwd();
344
+ return await this.dockerHelper.isRunning(this.config.containerName, {
345
+ cwd: projectRoot,
346
+ });
347
+ } catch (error) {
348
+ log.warn("Error checking if container is running:", error);
349
+ return false;
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Get the current configuration
355
+ *
356
+ * @returns Current Docker lifecycle configuration
357
+ */
358
+ getConfig(): DockerLifecycleConfig {
359
+ return { ...this.config };
360
+ }
361
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Docker core module exports
3
+ */
4
+
5
+ export type { DockerComposeOptions } from "./dockerComposeHelper";
6
+ export { DockerComposeHelper } from "./dockerComposeHelper";
7
+ export type { DockerLifecycleConfig } from "./dockerLifecycleManager";
8
+ export { DockerLifecycleManager } from "./dockerLifecycleManager";
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Logger Abstraction
3
+ *
4
+ * Provides a consistent logging interface across the codebase.
5
+ * Supports log levels and can be configured for different environments.
6
+ */
7
+
8
+ export type LogLevel = "debug" | "info" | "warn" | "error" | "none";
9
+
10
+ /**
11
+ * Logger configuration
12
+ */
13
+ export interface LoggerConfig {
14
+ /** Minimum log level to output */
15
+ level: LogLevel;
16
+ /** Prefix for all log messages */
17
+ prefix?: string;
18
+ /** Whether to include timestamps */
19
+ timestamp?: boolean;
20
+ }
21
+
22
+ const LOG_LEVELS: Record<LogLevel, number> = {
23
+ debug: 0,
24
+ info: 1,
25
+ warn: 2,
26
+ error: 3,
27
+ none: 4,
28
+ };
29
+
30
+ /**
31
+ * Simple logger implementation
32
+ */
33
+ export class Logger {
34
+ private config: LoggerConfig;
35
+
36
+ constructor(config: Partial<LoggerConfig> = {}) {
37
+ this.config = {
38
+ level: config.level ?? (process.env.DEBUG ? "debug" : "info"),
39
+ prefix: config.prefix,
40
+ timestamp: config.timestamp ?? false,
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Check if a log level should be output
46
+ */
47
+ private shouldLog(level: LogLevel): boolean {
48
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.config.level];
49
+ }
50
+
51
+ /**
52
+ * Format a log message
53
+ */
54
+ private format(_level: LogLevel, message: string): string {
55
+ const parts: string[] = [];
56
+
57
+ if (this.config.timestamp) {
58
+ parts.push(new Date().toISOString());
59
+ }
60
+
61
+ if (this.config.prefix) {
62
+ parts.push(`[${this.config.prefix}]`);
63
+ }
64
+
65
+ parts.push(message);
66
+ return parts.join(" ");
67
+ }
68
+
69
+ /**
70
+ * Log a debug message
71
+ */
72
+ debug(message: string, ...args: unknown[]): void {
73
+ if (this.shouldLog("debug")) {
74
+ console.log(this.format("debug", message), ...args);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Log an info message
80
+ */
81
+ info(message: string, ...args: unknown[]): void {
82
+ if (this.shouldLog("info")) {
83
+ console.log(this.format("info", message), ...args);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Log a warning message
89
+ */
90
+ warn(message: string, ...args: unknown[]): void {
91
+ if (this.shouldLog("warn")) {
92
+ console.warn(this.format("warn", message), ...args);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Log an error message
98
+ */
99
+ error(message: string, ...args: unknown[]): void {
100
+ if (this.shouldLog("error")) {
101
+ console.error(this.format("error", message), ...args);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Create a child logger with a prefix
107
+ */
108
+ child(prefix: string): Logger {
109
+ const parentPrefix = this.config.prefix;
110
+ return new Logger({
111
+ ...this.config,
112
+ prefix: parentPrefix ? `${parentPrefix}:${prefix}` : prefix,
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Set the log level
118
+ */
119
+ setLevel(level: LogLevel): void {
120
+ this.config.level = level;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Default logger instance
126
+ * Can be configured globally via environment variables:
127
+ * - DEBUG: Set to any value to enable debug logging
128
+ * - LOG_LEVEL: Set to debug, info, warn, error, or none
129
+ */
130
+ export const logger = new Logger({
131
+ level: (process.env.LOG_LEVEL as LogLevel) ?? (process.env.DEBUG ? "debug" : "warn"),
132
+ });
133
+
134
+ /**
135
+ * Create a logger with a specific prefix
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * const log = createLogger('DockerLifecycle');
140
+ * log.info('Starting container...');
141
+ * // Output: [DockerLifecycle] Starting container...
142
+ * ```
143
+ */
144
+ export function createLogger(prefix: string): Logger {
145
+ return logger.child(prefix);
146
+ }