@exulu/backend 1.59.0 → 1.61.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.
@@ -2,7 +2,7 @@ import {
2
2
  __resetLiteLLMCatalogCacheForTesting,
3
3
  fetchLiteLLMCatalog,
4
4
  findLiteLLMModel
5
- } from "./chunk-YS27XOXI.js";
5
+ } from "./chunk-ILAHW4UT.js";
6
6
  export {
7
7
  __resetLiteLLMCatalogCacheForTesting,
8
8
  fetchLiteLLMCatalog,
@@ -0,0 +1,210 @@
1
+ // src/utils/python-setup.ts
2
+ import { exec } from "child_process";
3
+ import { promisify } from "util";
4
+ import { resolve, join, dirname } from "path";
5
+ import { existsSync, readFileSync } from "fs";
6
+ import { fileURLToPath } from "url";
7
+ var execAsync = promisify(exec);
8
+ function getPackageRoot() {
9
+ const currentFile = fileURLToPath(import.meta.url);
10
+ let currentDir = dirname(currentFile);
11
+ let attempts = 0;
12
+ const maxAttempts = 10;
13
+ while (attempts < maxAttempts) {
14
+ const packageJsonPath = join(currentDir, "package.json");
15
+ if (existsSync(packageJsonPath)) {
16
+ try {
17
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
18
+ if (packageJson.name === "@exulu/backend") {
19
+ return currentDir;
20
+ }
21
+ } catch {
22
+ }
23
+ }
24
+ const parentDir = resolve(currentDir, "..");
25
+ if (parentDir === currentDir) {
26
+ break;
27
+ }
28
+ currentDir = parentDir;
29
+ attempts++;
30
+ }
31
+ const fallback = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
32
+ return fallback;
33
+ }
34
+ function getSetupScriptPath(packageRoot) {
35
+ return resolve(packageRoot, "ee/python/setup.sh");
36
+ }
37
+ function getVenvPath(packageRoot) {
38
+ return resolve(packageRoot, "ee/python/.venv");
39
+ }
40
+ function isPythonEnvironmentSetup(packageRoot) {
41
+ const root = packageRoot ?? getPackageRoot();
42
+ const venvPath = getVenvPath(root);
43
+ const pythonPath = join(venvPath, "bin", "python");
44
+ return existsSync(venvPath) && existsSync(pythonPath);
45
+ }
46
+ async function setupPythonEnvironment(options = {}) {
47
+ const {
48
+ packageRoot = getPackageRoot(),
49
+ force = false,
50
+ verbose = false,
51
+ timeout = 6e5
52
+ // 10 minutes
53
+ } = options;
54
+ if (!force && isPythonEnvironmentSetup(packageRoot)) {
55
+ if (verbose) {
56
+ console.log("\u2713 Python environment already set up");
57
+ }
58
+ return {
59
+ success: true,
60
+ message: "Python environment already exists",
61
+ alreadyExists: true
62
+ };
63
+ }
64
+ const setupScriptPath = getSetupScriptPath(packageRoot);
65
+ if (!existsSync(setupScriptPath)) {
66
+ return {
67
+ success: false,
68
+ message: `Setup script not found at: ${setupScriptPath}`,
69
+ alreadyExists: false
70
+ };
71
+ }
72
+ try {
73
+ if (verbose) {
74
+ console.log("Setting up Python environment...");
75
+ }
76
+ const { stdout, stderr } = await execAsync(`bash "${setupScriptPath}"`, {
77
+ cwd: packageRoot,
78
+ timeout,
79
+ env: {
80
+ ...process.env,
81
+ // Ensure script can write to the directory
82
+ PYTHONDONTWRITEBYTECODE: "1"
83
+ },
84
+ maxBuffer: 10 * 1024 * 1024
85
+ // 10MB buffer
86
+ });
87
+ const output = stdout + stderr;
88
+ const versionMatch = output.match(/Python (\d+\.\d+\.\d+)/);
89
+ const pythonVersion = versionMatch ? versionMatch[1] : void 0;
90
+ if (verbose) {
91
+ console.log(output);
92
+ }
93
+ return {
94
+ success: true,
95
+ message: "Python environment set up successfully",
96
+ alreadyExists: false,
97
+ pythonVersion,
98
+ output
99
+ };
100
+ } catch (error) {
101
+ const errorOutput = error.stdout + error.stderr;
102
+ return {
103
+ success: false,
104
+ message: `Setup failed: ${error.message}`,
105
+ alreadyExists: false,
106
+ output: errorOutput
107
+ };
108
+ }
109
+ }
110
+ function getPythonSetupInstructions() {
111
+ return `
112
+ Python environment not set up. Please run one of the following commands:
113
+
114
+ Option 1 (Automatic):
115
+ import { setupPythonEnvironment } from '@exulu/backend';
116
+ await setupPythonEnvironment();
117
+
118
+ Option 2 (Manual - for package consumers):
119
+ npx @exulu/backend setup-python
120
+
121
+ Option 3 (Manual - for contributors):
122
+ npm run python:setup
123
+
124
+ These commands will automatically create a Python virtual environment (.venv)
125
+ in the @exulu/backend package and install all required dependencies.
126
+
127
+ Requirements:
128
+ - Python 3.10 or higher must be installed
129
+ - pip must be available
130
+ - venv module must be available (for creating virtual environments)
131
+
132
+ If Python dependencies are not installed, install them first, then run one of the commands above:
133
+ - macOS: brew install python@3.12
134
+ - Ubuntu/Debian: sudo apt-get install python3.12 python3-pip python3-venv
135
+ - Alpine Linux: apk add python3 py3-pip python3-dev
136
+ - Windows: Download from https://www.python.org/downloads/
137
+
138
+ Note: In Docker containers, ensure you install all three components:
139
+ Ubuntu/Debian: apt-get install -y python3 python3-pip python3-venv
140
+ Alpine: apk add python3 py3-pip python3-dev
141
+ `.trim();
142
+ }
143
+ async function validatePythonEnvironment(packageRoot, checkPackages = true) {
144
+ const root = packageRoot ?? getPackageRoot();
145
+ const venvPath = getVenvPath(root);
146
+ const pythonPath = join(venvPath, "bin", "python");
147
+ if (!existsSync(venvPath)) {
148
+ return {
149
+ valid: false,
150
+ message: getPythonSetupInstructions()
151
+ };
152
+ }
153
+ if (!existsSync(pythonPath)) {
154
+ return {
155
+ valid: false,
156
+ message: "Python virtual environment is corrupted. Please run:\n await setupPythonEnvironment({ force: true })"
157
+ };
158
+ }
159
+ try {
160
+ await execAsync(`"${pythonPath}" --version`, { cwd: root });
161
+ } catch {
162
+ return {
163
+ valid: false,
164
+ message: "Python executable is not working. Please run:\n await setupPythonEnvironment({ force: true })"
165
+ };
166
+ }
167
+ if (checkPackages) {
168
+ const criticalPackages = ["docling", "transformers"];
169
+ const missingPackages = [];
170
+ for (const pkg of criticalPackages) {
171
+ try {
172
+ await execAsync(`"${pythonPath}" -c "import ${pkg}"`, {
173
+ cwd: root,
174
+ timeout: 1e4
175
+ // 10 second timeout per import check
176
+ });
177
+ } catch {
178
+ missingPackages.push(pkg);
179
+ }
180
+ }
181
+ if (missingPackages.length > 0) {
182
+ return {
183
+ valid: false,
184
+ message: `Python environment exists but required packages are not installed: ${missingPackages.join(", ")}
185
+
186
+ This usually happens when:
187
+ 1. The .venv folder was copied but dependencies were not installed
188
+ 2. The package was installed via npm but setup script was not run
189
+
190
+ Please run:
191
+ await setupPythonEnvironment({ force: true })
192
+
193
+ Or manually run the setup script:
194
+ bash ` + getSetupScriptPath(root)
195
+ };
196
+ }
197
+ }
198
+ return {
199
+ valid: true,
200
+ message: "Python environment is valid"
201
+ };
202
+ }
203
+
204
+ export {
205
+ getPackageRoot,
206
+ isPythonEnvironmentSetup,
207
+ setupPythonEnvironment,
208
+ getPythonSetupInstructions,
209
+ validatePythonEnvironment
210
+ };
@@ -40,7 +40,11 @@ var fetchLiteLLMCatalog = async () => {
40
40
  supports_vision: !!m.model_info?.supports_vision,
41
41
  supports_function_calling: !!m.model_info?.supports_function_calling,
42
42
  supports_pdf_input: !!m.model_info?.supports_pdf_input,
43
- supports_audio_input: !!m.model_info?.supports_audio_input
43
+ supports_audio_input: !!m.model_info?.supports_audio_input,
44
+ sizes: Array.isArray(m.model_info?.sizes) ? m.model_info.sizes : null,
45
+ qualities: Array.isArray(m.model_info?.qualities) ? m.model_info.qualities : null,
46
+ supports_edit: !!m.model_info?.supports_edit,
47
+ max_n: typeof m.model_info?.max_n === "number" ? m.model_info.max_n : null
44
48
  }));
45
49
  _cache = { expiresAt: Date.now() + CACHE_TTL_MS, items };
46
50
  return items.filter((m) => m.type !== "speech_to_text" && m.type !== "text_to_speech");
@@ -262,7 +262,8 @@ var spawnLiteLLM = (cfg) => {
262
262
  log(
263
263
  `Spawning LiteLLM: ${cfg.litellmBin} --config ${cfg.configPath} --port ${cfg.port} --host ${cfg.host}`
264
264
  );
265
- const { DEBUG: _debug, ...envWithoutDebug } = process.env;
265
+ const { DEBUG: _debug, ...rest } = process.env;
266
+ const childEnv = { ...rest, DEBUG: "false" };
266
267
  const child = spawn(
267
268
  cfg.litellmBin,
268
269
  [
@@ -275,7 +276,7 @@ var spawnLiteLLM = (cfg) => {
275
276
  ],
276
277
  {
277
278
  stdio: ["ignore", "pipe", "pipe"],
278
- env: envWithoutDebug
279
+ env: childEnv
279
280
  }
280
281
  );
281
282
  child.stdout?.on("data", (chunk) => {
@@ -743,7 +744,7 @@ var ExuluTool = class {
743
744
  });
744
745
  providerapikey = resolved.apiKey;
745
746
  }
746
- const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-ZEECMX43.js");
747
+ const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-CULC37U6.js");
747
748
  const tools = await convertExuluToolsToAiSdkTools2(
748
749
  [this],
749
750
  [],
@@ -3167,6 +3168,10 @@ var usersSchema = {
3167
3168
  name: "anthropic_token",
3168
3169
  type: "text"
3169
3170
  },
3171
+ {
3172
+ name: "personal_system_prompt",
3173
+ type: "longText"
3174
+ },
3170
3175
  {
3171
3176
  name: "role",
3172
3177
  type: "uuid"
@@ -3175,6 +3180,7 @@ var usersSchema = {
3175
3180
  };
3176
3181
  var platformConfigurationsSchema = {
3177
3182
  type: "platform_configurations",
3183
+ RBAC: true,
3178
3184
  name: {
3179
3185
  plural: "platform_configurations",
3180
3186
  singular: "platform_configuration"
@@ -3294,6 +3300,60 @@ var promptFavoritesSchema = {
3294
3300
  }
3295
3301
  ]
3296
3302
  };
3303
+ var transcriptionJobsSchema = {
3304
+ type: "transcription_jobs",
3305
+ name: {
3306
+ plural: "transcription_jobs",
3307
+ singular: "transcription_job"
3308
+ },
3309
+ RBAC: true,
3310
+ fields: [
3311
+ { name: "audio", type: "file" },
3312
+ { name: "title", type: "text" },
3313
+ { name: "status", type: "text", index: true },
3314
+ { name: "whisper_job_id", type: "text" },
3315
+ { name: "raw_segments", type: "json" },
3316
+ { name: "speakers", type: "json" },
3317
+ { name: "language", type: "text" },
3318
+ { name: "duration_seconds", type: "number" },
3319
+ { name: "project_id", type: "uuid", required: false },
3320
+ { name: "target_rights_mode", type: "text", default: "private" },
3321
+ { name: "target_rbac_users", type: "json" },
3322
+ { name: "target_rbac_roles", type: "json" },
3323
+ { name: "saved_item_id", type: "uuid", required: false },
3324
+ { name: "error", type: "text" }
3325
+ ]
3326
+ };
3327
+ var imageGenerationsSchema = {
3328
+ type: "image_generations",
3329
+ name: {
3330
+ plural: "image_generations",
3331
+ singular: "image_generation"
3332
+ },
3333
+ // Access is gated by the parent agent_sessions RBAC — rows have no
3334
+ // independent visibility, so no row-level RBAC fields are needed here.
3335
+ RBAC: false,
3336
+ fields: [
3337
+ { name: "session_id", type: "uuid", required: true, index: true },
3338
+ { name: "tool_call_id", type: "text", required: true, index: true },
3339
+ { name: "user_id", type: "number", required: true, index: true },
3340
+ { name: "operation", type: "text", required: true },
3341
+ // 'generate' | 'edit'
3342
+ { name: "model", type: "text", required: true },
3343
+ { name: "prompt", type: "longText", required: true },
3344
+ { name: "applied_style_id", type: "uuid", required: false },
3345
+ { name: "applied_style_markdown", type: "longText", required: false },
3346
+ { name: "size", type: "text", required: false },
3347
+ { name: "quality", type: "text", required: false },
3348
+ { name: "n", type: "number", default: 1 },
3349
+ { name: "reference_image_keys", type: "json", required: false },
3350
+ { name: "mask_image_key", type: "text", required: false },
3351
+ { name: "image_keys", type: "json", required: true },
3352
+ { name: "revised_prompts", type: "json", required: false },
3353
+ { name: "selected", type: "boolean", default: false },
3354
+ { name: "error", type: "text", required: false }
3355
+ ]
3356
+ };
3297
3357
  var contextPresetsSchema = {
3298
3358
  type: "context_presets",
3299
3359
  name: {
@@ -3384,7 +3444,9 @@ var coreSchemas = {
3384
3444
  promptLibrarySchema: () => addCoreFields(promptLibrarySchema),
3385
3445
  embedderSettingsSchema: () => addCoreFields(embedderSettingsSchema),
3386
3446
  promptFavoritesSchema: () => addCoreFields(promptFavoritesSchema),
3387
- contextPresetsSchema: () => addCoreFields(contextPresetsSchema)
3447
+ contextPresetsSchema: () => addCoreFields(contextPresetsSchema),
3448
+ transcriptionJobsSchema: () => addCoreFields(transcriptionJobsSchema),
3449
+ imageGenerationsSchema: () => addCoreFields(imageGenerationsSchema)
3388
3450
  };
3389
3451
  if (license["agent-feedback"]) {
3390
3452
  schemas.feedbackSchema = () => addCoreFields(feedbackSchema);
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // node_modules/tsup/assets/cjs_shims.js
5
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
6
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
7
+
8
+ // src/cli/start-whisper.ts
9
+ var import_config = require("dotenv/config");
10
+
11
+ // src/utils/python-setup.ts
12
+ var import_child_process = require("child_process");
13
+ var import_util = require("util");
14
+ var import_path = require("path");
15
+ var import_fs = require("fs");
16
+ var import_url = require("url");
17
+ var execAsync = (0, import_util.promisify)(import_child_process.exec);
18
+ function getPackageRoot() {
19
+ const currentFile = (0, import_url.fileURLToPath)(importMetaUrl);
20
+ let currentDir = (0, import_path.dirname)(currentFile);
21
+ let attempts = 0;
22
+ const maxAttempts = 10;
23
+ while (attempts < maxAttempts) {
24
+ const packageJsonPath = (0, import_path.join)(currentDir, "package.json");
25
+ if ((0, import_fs.existsSync)(packageJsonPath)) {
26
+ try {
27
+ const packageJson = JSON.parse((0, import_fs.readFileSync)(packageJsonPath, "utf-8"));
28
+ if (packageJson.name === "@exulu/backend") {
29
+ return currentDir;
30
+ }
31
+ } catch {
32
+ }
33
+ }
34
+ const parentDir = (0, import_path.resolve)(currentDir, "..");
35
+ if (parentDir === currentDir) {
36
+ break;
37
+ }
38
+ currentDir = parentDir;
39
+ attempts++;
40
+ }
41
+ const fallback = (0, import_path.resolve)((0, import_path.dirname)((0, import_url.fileURLToPath)(importMetaUrl)), "../..");
42
+ return fallback;
43
+ }
44
+
45
+ // src/exulu/transcription/supervisor.ts
46
+ var import_node_child_process = require("child_process");
47
+ var import_node_fs = require("fs");
48
+ var import_node_path = require("path");
49
+ var MAX_CRASHES = 5;
50
+ var INITIAL_BACKOFF_MS = 1e3;
51
+ var MAX_BACKOFF_MS = 3e4;
52
+ var READY_TIMEOUT_MS = 30 * 6e4;
53
+ var READY_POLL_INTERVAL_MS = 500;
54
+ var SHUTDOWN_GRACE_MS = 5e3;
55
+ var internal = {
56
+ child: void 0,
57
+ state: "idle",
58
+ crashCount: 0,
59
+ backoffMs: INITIAL_BACKOFF_MS,
60
+ readyPromise: void 0,
61
+ shutdownRequested: false
62
+ };
63
+ var resolveConfig = (packageRoot) => {
64
+ const host = process.env.WHISPER_HOST ?? "127.0.0.1";
65
+ const port = process.env.WHISPER_PORT ?? "9876";
66
+ const venvBin = (0, import_node_path.resolve)(packageRoot, "ee/python/.venv/bin");
67
+ const venvPython = (0, import_node_path.resolve)(venvBin, "python");
68
+ const cwd = (0, import_node_path.resolve)(packageRoot, "ee/python/transcription");
69
+ return { host, port, venvBin, venvPython, cwd };
70
+ };
71
+ var log = (line) => {
72
+ const text = line.startsWith("[EXULU-WHISPER]") ? line : `[EXULU-WHISPER] ${line}`;
73
+ console.log(text);
74
+ };
75
+ var pollHealth = async (host, port) => {
76
+ const url = `http://${host}:${port}/healthz`;
77
+ const deadline = Date.now() + READY_TIMEOUT_MS;
78
+ while (Date.now() < deadline) {
79
+ try {
80
+ const res = await fetch(url, { method: "GET" });
81
+ if (res.ok) return;
82
+ } catch {
83
+ }
84
+ await new Promise((r) => setTimeout(r, READY_POLL_INTERVAL_MS));
85
+ }
86
+ throw new Error(
87
+ `Whisper server did not become ready at ${url} within ${READY_TIMEOUT_MS}ms`
88
+ );
89
+ };
90
+ var spawnWhisper = (cfg) => {
91
+ log(
92
+ `Spawning: ${cfg.venvPython} -m uvicorn server:app --host ${cfg.host} --port ${cfg.port}`
93
+ );
94
+ const child = (0, import_node_child_process.spawn)(
95
+ cfg.venvPython,
96
+ [
97
+ "-m",
98
+ "uvicorn",
99
+ "server:app",
100
+ "--host",
101
+ cfg.host,
102
+ "--port",
103
+ cfg.port,
104
+ "--log-level",
105
+ "info"
106
+ ],
107
+ {
108
+ cwd: cfg.cwd,
109
+ stdio: ["ignore", "pipe", "pipe"],
110
+ env: process.env
111
+ }
112
+ );
113
+ child.stdout?.on("data", (chunk) => {
114
+ chunk.toString().split("\n").filter((l) => l.length > 0).forEach((l) => log(l));
115
+ });
116
+ child.stderr?.on("data", (chunk) => {
117
+ chunk.toString().split("\n").filter((l) => l.length > 0).forEach((l) => log(`stderr: ${l}`));
118
+ });
119
+ return child;
120
+ };
121
+ var supervise = async (cfg) => {
122
+ while (!internal.shutdownRequested && internal.crashCount < MAX_CRASHES) {
123
+ internal.state = internal.crashCount === 0 ? "starting" : "respawning";
124
+ internal.child = spawnWhisper(cfg);
125
+ const exitPromise = new Promise((resolveFn) => {
126
+ internal.child.on("exit", (code2) => resolveFn(code2));
127
+ });
128
+ try {
129
+ await Promise.race([
130
+ pollHealth(cfg.host, cfg.port).then(() => "ready"),
131
+ exitPromise.then((code2) => ({ exited: code2 }))
132
+ ]);
133
+ } catch (err) {
134
+ log(`Readiness probe failed: ${err.message}`);
135
+ try {
136
+ internal.child?.kill("SIGTERM");
137
+ } catch {
138
+ }
139
+ }
140
+ if (!internal.child?.killed && internal.child?.exitCode === null) {
141
+ internal.state = "ready";
142
+ internal.crashCount = 0;
143
+ internal.backoffMs = INITIAL_BACKOFF_MS;
144
+ log("Whisper server is ready.");
145
+ }
146
+ const code = await exitPromise;
147
+ internal.state = "respawning";
148
+ internal.child = void 0;
149
+ if (internal.shutdownRequested) {
150
+ log("Child exited during shutdown; supervisor stopping.");
151
+ internal.state = "stopped";
152
+ return;
153
+ }
154
+ internal.crashCount += 1;
155
+ log(
156
+ `Whisper server exited (code=${code}). Crash ${internal.crashCount}/${MAX_CRASHES}. Respawning in ${internal.backoffMs}ms.`
157
+ );
158
+ if (internal.crashCount >= MAX_CRASHES) {
159
+ log(
160
+ "Whisper server keeps crashing \u2014 fix the install (try `npx @exulu/backend setup-python --force`) and re-run `npx @exulu/backend exulu-start-whisper`. Giving up."
161
+ );
162
+ internal.state = "given_up";
163
+ return;
164
+ }
165
+ await new Promise((r) => setTimeout(r, internal.backoffMs));
166
+ internal.backoffMs = Math.min(internal.backoffMs * 2, MAX_BACKOFF_MS);
167
+ }
168
+ };
169
+ var startWhisperSupervisor = async (options) => {
170
+ if (internal.readyPromise) {
171
+ return internal.readyPromise;
172
+ }
173
+ const cfg = resolveConfig(options.packageRoot);
174
+ if (!(0, import_node_fs.existsSync)(cfg.venvPython)) {
175
+ throw new Error(
176
+ `Whisper supervisor: Python venv not found at ${cfg.venvPython}. Run \`npx @exulu/backend setup-python\` first.`
177
+ );
178
+ }
179
+ if (!(0, import_node_fs.existsSync)(cfg.cwd)) {
180
+ throw new Error(
181
+ `Whisper supervisor: transcription scripts not found at ${cfg.cwd}. The @exulu/backend package may be corrupt; reinstall it.`
182
+ );
183
+ }
184
+ internal.readyPromise = (async () => {
185
+ supervise(cfg);
186
+ const deadline = Date.now() + READY_TIMEOUT_MS + 5e3;
187
+ while (Date.now() < deadline) {
188
+ if (internal.state === "ready") return;
189
+ if (internal.state === "given_up") {
190
+ throw new Error("Whisper supervisor gave up before becoming ready.");
191
+ }
192
+ await new Promise((r) => setTimeout(r, READY_POLL_INTERVAL_MS));
193
+ }
194
+ throw new Error("Timed out waiting for whisper supervisor readiness.");
195
+ })();
196
+ registerShutdownHandlers();
197
+ return internal.readyPromise;
198
+ };
199
+ var stopWhisper = (signal = "SIGTERM") => {
200
+ internal.shutdownRequested = true;
201
+ const child = internal.child;
202
+ if (!child) return;
203
+ try {
204
+ child.kill(signal);
205
+ } catch {
206
+ }
207
+ setTimeout(() => {
208
+ try {
209
+ if (!child.killed && child.exitCode === null) {
210
+ child.kill("SIGKILL");
211
+ }
212
+ } catch {
213
+ }
214
+ }, SHUTDOWN_GRACE_MS).unref();
215
+ };
216
+ var shutdownHandlersRegistered = false;
217
+ var registerShutdownHandlers = () => {
218
+ if (shutdownHandlersRegistered) return;
219
+ shutdownHandlersRegistered = true;
220
+ process.on("SIGINT", () => stopWhisper("SIGTERM"));
221
+ process.on("SIGTERM", () => stopWhisper("SIGTERM"));
222
+ process.on("exit", () => stopWhisper("SIGTERM"));
223
+ };
224
+
225
+ // src/cli/start-whisper.ts
226
+ var main = async () => {
227
+ const packageRoot = getPackageRoot();
228
+ try {
229
+ await startWhisperSupervisor({ packageRoot });
230
+ } catch (err) {
231
+ console.error(`[EXULU-WHISPER] ${err.message}`);
232
+ process.exit(1);
233
+ }
234
+ await new Promise(() => {
235
+ });
236
+ };
237
+ main().catch((err) => {
238
+ console.error(`[EXULU-WHISPER] Unexpected error: ${err.stack ?? err}`);
239
+ process.exit(1);
240
+ });
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node