@mmmbuto/nexuscrew 0.2.0 → 0.2.2

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 (41) hide show
  1. package/README.md +124 -56
  2. package/bin/nexuscrew.js +79 -152
  3. package/frontend/dist/assets/{index-8pw4eMB-.js → index-BsldfYnr.js} +1704 -1716
  4. package/frontend/dist/assets/index-DcQGg3dp.css +1 -0
  5. package/frontend/dist/index.html +2 -2
  6. package/lib/server/routes/hosts.js +3 -2
  7. package/lib/server/routes/launchers.js +12 -0
  8. package/lib/server/routes/models.js +16 -63
  9. package/lib/server/routes/runtime.js +10 -0
  10. package/lib/server/routes/send.js +97 -218
  11. package/lib/server/routes/sessions.js +7 -39
  12. package/lib/server/routes/status.js +16 -18
  13. package/lib/server/routes/tmux.js +88 -0
  14. package/lib/server/server.js +45 -65
  15. package/lib/services/engine-discovery.js +192 -445
  16. package/lib/services/session-store.js +86 -60
  17. package/lib/services/tmux-manager.js +103 -154
  18. package/package.json +3 -7
  19. package/frontend/dist/assets/index-BF0tdvNT.css +0 -1
  20. package/lib/config/manager.js +0 -362
  21. package/lib/config/models.js +0 -408
  22. package/lib/server/db/adapter.js +0 -274
  23. package/lib/server/db/drivers/sql-js.js +0 -75
  24. package/lib/server/db/migrate.js +0 -174
  25. package/lib/server/db/migrations/001_base_schema.sql +0 -70
  26. package/lib/server/middleware/auth.js +0 -134
  27. package/lib/server/middleware/rate-limit.js +0 -63
  28. package/lib/server/models/User.js +0 -128
  29. package/lib/server/routes/auth.js +0 -168
  30. package/lib/server/routes/keys.js +0 -15
  31. package/lib/server/routes/runtimes.js +0 -34
  32. package/lib/server/routes/speech.js +0 -75
  33. package/lib/server/routes/upload.js +0 -134
  34. package/lib/server/routes/wake-lock.js +0 -95
  35. package/lib/server/routes/workspaces.js +0 -101
  36. package/lib/server/services/context-bridge.js +0 -425
  37. package/lib/server/services/runtime-manager.js +0 -462
  38. package/lib/server/services/summary-generator.js +0 -309
  39. package/lib/server/services/workspace-manager.js +0 -79
  40. package/lib/utils/paths.js +0 -107
  41. package/lib/utils/termux.js +0 -145
@@ -1,408 +0,0 @@
1
- /**
2
- * Shared model catalog for NexusCrew.
3
- * Unified endpoint: all engines use /api/v1/send
4
- */
5
-
6
- const UNIFIED_ENDPOINT = '/api/v1/send';
7
-
8
- const CATALOG = {
9
- claude: {
10
- id: 'claude',
11
- name: 'Claude Code',
12
- icon: 'Terminal',
13
- endpoint: UNIFIED_ENDPOINT,
14
- thinkModes: ['think', 'no-think'],
15
- defaultThinkMode: 'think',
16
- lanes: {
17
- native: {
18
- runtimeId: 'claude-native',
19
- label: 'Native',
20
- models: [
21
- {
22
- id: 'sonnet',
23
- name: 'sonnet',
24
- label: 'Claude Sonnet 4.6',
25
- description: 'Latest balanced Claude coding model',
26
- providerId: 'anthropic',
27
- default: true,
28
- },
29
- {
30
- id: 'opus',
31
- name: 'opus',
32
- label: 'Claude Opus 4.6',
33
- description: 'Highest capability Claude model',
34
- providerId: 'anthropic',
35
- },
36
- {
37
- id: 'haiku',
38
- name: 'haiku',
39
- label: 'Claude Haiku 4.5',
40
- description: 'Fast Claude lane',
41
- providerId: 'anthropic',
42
- },
43
- ],
44
- },
45
- custom: {
46
- runtimeId: 'claude-custom',
47
- label: 'Custom',
48
- models: [
49
- {
50
- id: 'deepseek-reasoner',
51
- name: 'deepseek-reasoner',
52
- label: 'DeepSeek Reasoner',
53
- description: 'Claude CLI via DeepSeek Anthropic-compatible API',
54
- providerId: 'deepseek',
55
- },
56
- {
57
- id: 'deepseek-chat',
58
- name: 'deepseek-chat',
59
- label: 'DeepSeek Chat',
60
- description: 'Fast DeepSeek chat over Claude CLI',
61
- providerId: 'deepseek',
62
- },
63
- {
64
- id: 'glm-4.7',
65
- name: 'glm-4.7',
66
- label: 'GLM 4.7',
67
- description: 'Z.ai Anthropic-compatible model',
68
- providerId: 'zai',
69
- },
70
- {
71
- id: 'glm-5',
72
- name: 'glm-5',
73
- label: 'GLM 5',
74
- description: 'Z.ai latest custom runtime',
75
- providerId: 'zai',
76
- },
77
- {
78
- id: 'qwen3.5-plus',
79
- name: 'qwen3.5-plus',
80
- label: 'Qwen 3.5 Plus',
81
- description: 'Alibaba custom provider over Claude CLI',
82
- providerId: 'alibaba',
83
- },
84
- {
85
- id: 'qwen3-max-2026-01-23',
86
- name: 'qwen3-max-2026-01-23',
87
- label: 'Qwen 3 Max',
88
- description: 'Alibaba custom provider over Claude CLI',
89
- providerId: 'alibaba',
90
- },
91
- {
92
- id: 'kimi-k2.5',
93
- name: 'kimi-k2.5',
94
- label: 'Kimi K2.5',
95
- description: 'Moonshot-compatible custom lane',
96
- providerId: 'alibaba',
97
- },
98
- {
99
- id: 'MiniMax-M2.7',
100
- name: 'MiniMax-M2.7',
101
- label: 'MiniMax M2.7',
102
- description: 'MiniMax custom lane over Claude CLI',
103
- providerId: 'minimax',
104
- },
105
- ],
106
- },
107
- },
108
- },
109
-
110
- codex: {
111
- id: 'codex',
112
- name: 'Codex',
113
- icon: 'Code2',
114
- endpoint: UNIFIED_ENDPOINT,
115
- lanes: {
116
- native: {
117
- runtimeId: 'codex-native',
118
- label: 'Native',
119
- models: [
120
- {
121
- id: 'gpt-5.4',
122
- name: 'gpt-5.4',
123
- label: 'GPT-5.4',
124
- description: 'Latest flagship reasoning model',
125
- providerId: 'openai',
126
- reasoningEfforts: ['low', 'medium', 'high', 'xhigh'],
127
- defaultReasoning: 'high',
128
- },
129
- {
130
- id: 'gpt-5.3-codex',
131
- name: 'gpt-5.3-codex',
132
- label: 'GPT-5.3 Codex',
133
- description: 'Current codex-specialized model',
134
- providerId: 'openai',
135
- reasoningEfforts: ['low', 'medium', 'high', 'xhigh'],
136
- defaultReasoning: 'high',
137
- default: true,
138
- },
139
- {
140
- id: 'gpt-5.2-codex',
141
- name: 'gpt-5.2-codex',
142
- label: 'GPT-5.2 Codex',
143
- description: 'Stable codex model',
144
- providerId: 'openai',
145
- reasoningEfforts: ['low', 'medium', 'high', 'xhigh'],
146
- defaultReasoning: 'high',
147
- },
148
- {
149
- id: 'gpt-5.1-codex-max',
150
- name: 'gpt-5.1-codex-max',
151
- label: 'GPT-5.1 Codex Max',
152
- description: 'Deep codex reasoning',
153
- providerId: 'openai',
154
- reasoningEfforts: ['low', 'medium', 'high', 'xhigh'],
155
- defaultReasoning: 'high',
156
- },
157
- {
158
- id: 'codex-mini-latest',
159
- name: 'codex-mini-latest',
160
- label: 'Codex Mini Latest',
161
- description: 'Fast latest codex lane',
162
- providerId: 'openai',
163
- reasoningEfforts: ['medium', 'high'],
164
- defaultReasoning: 'medium',
165
- },
166
- ],
167
- },
168
- custom: {
169
- runtimeId: 'codex-custom',
170
- label: 'Custom',
171
- models: [
172
- {
173
- id: 'qwen3-coder-plus',
174
- name: 'qwen3-coder-plus',
175
- label: 'Qwen 3 Coder Plus',
176
- description: 'Custom coding provider via codex-lts',
177
- providerId: 'alibaba',
178
- reasoningEfforts: ['medium', 'high'],
179
- defaultReasoning: 'high',
180
- },
181
- {
182
- id: 'qwen3-coder-next',
183
- name: 'qwen3-coder-next',
184
- label: 'Qwen 3 Coder Next',
185
- description: 'Custom coding provider via codex-lts',
186
- providerId: 'alibaba',
187
- reasoningEfforts: ['medium', 'high'],
188
- defaultReasoning: 'high',
189
- },
190
- {
191
- id: 'qwen3.5-plus',
192
- name: 'qwen3.5-plus',
193
- label: 'Qwen 3.5 Plus',
194
- description: 'Custom provider lane via codex-lts',
195
- providerId: 'alibaba',
196
- reasoningEfforts: ['medium', 'high'],
197
- defaultReasoning: 'high',
198
- },
199
- {
200
- id: 'glm-5',
201
- name: 'glm-5',
202
- label: 'GLM 5',
203
- description: 'Z.ai custom lane via codex-lts',
204
- providerId: 'zai',
205
- reasoningEfforts: ['medium', 'high'],
206
- defaultReasoning: 'high',
207
- },
208
- {
209
- id: 'deepseek-ai/DeepSeek-V3.2-TEE',
210
- name: 'deepseek-ai/DeepSeek-V3.2-TEE',
211
- label: 'DeepSeek V3.2 TEE',
212
- description: 'Chutes custom runtime via codex-lts',
213
- providerId: 'chutes',
214
- reasoningEfforts: ['medium', 'high'],
215
- defaultReasoning: 'high',
216
- },
217
- ],
218
- },
219
- },
220
- },
221
-
222
- gemini: {
223
- id: 'gemini',
224
- name: 'Gemini',
225
- icon: 'Sparkles',
226
- endpoint: UNIFIED_ENDPOINT,
227
- lanes: {
228
- native: {
229
- runtimeId: 'gemini-native',
230
- label: 'Native',
231
- models: [
232
- {
233
- id: 'gemini-3-pro-preview',
234
- name: 'gemini-3-pro-preview',
235
- label: 'Gemini 3 Pro',
236
- description: 'Current Gemini 3 pro line',
237
- providerId: 'google',
238
- default: true,
239
- },
240
- {
241
- id: 'gemini-3-flash-preview',
242
- name: 'gemini-3-flash-preview',
243
- label: 'Gemini 3 Flash',
244
- description: 'Current Gemini 3 flash line',
245
- providerId: 'google',
246
- },
247
- {
248
- id: 'gemini-2.5-pro',
249
- name: 'gemini-2.5-pro',
250
- label: 'Gemini 2.5 Pro',
251
- description: 'Stable Gemini 2.5 pro',
252
- providerId: 'google',
253
- },
254
- {
255
- id: 'gemini-2.5-flash',
256
- name: 'gemini-2.5-flash',
257
- label: 'Gemini 2.5 Flash',
258
- description: 'Stable Gemini 2.5 flash',
259
- providerId: 'google',
260
- },
261
- ],
262
- },
263
- custom: {
264
- runtimeId: 'gemini-custom',
265
- label: 'Custom',
266
- models: [],
267
- },
268
- },
269
- },
270
-
271
- qwen: {
272
- id: 'qwen',
273
- name: 'Qwen Code',
274
- icon: 'Cpu',
275
- endpoint: UNIFIED_ENDPOINT,
276
- lanes: {
277
- native: {
278
- runtimeId: 'qwen-native',
279
- label: 'Native',
280
- models: [
281
- {
282
- id: 'qwen3-coder-plus',
283
- name: 'qwen3-coder-plus',
284
- label: 'Qwen 3 Coder Plus',
285
- description: 'Primary Qwen coding model',
286
- providerId: 'qwen',
287
- default: true,
288
- },
289
- {
290
- id: 'qwen3-coder-next',
291
- name: 'qwen3-coder-next',
292
- label: 'Qwen 3 Coder Next',
293
- description: 'Latest Qwen coding lane',
294
- providerId: 'qwen',
295
- },
296
- {
297
- id: 'qwen3.5-plus',
298
- name: 'qwen3.5-plus',
299
- label: 'Qwen 3.5 Plus',
300
- description: 'Higher-capability Qwen lane',
301
- providerId: 'qwen',
302
- },
303
- {
304
- id: 'qwen3-max',
305
- name: 'qwen3-max',
306
- label: 'Qwen 3 Max',
307
- description: 'Largest Qwen lane',
308
- providerId: 'qwen',
309
- },
310
- ],
311
- },
312
- custom: {
313
- runtimeId: 'qwen-custom',
314
- label: 'Custom',
315
- models: [
316
- {
317
- id: 'glm-4.7',
318
- name: 'glm-4.7',
319
- label: 'GLM 4.7',
320
- description: 'Provider-side custom lane supported by Qwen Code',
321
- providerId: 'zai',
322
- },
323
- {
324
- id: 'kimi-k2.5',
325
- name: 'kimi-k2.5',
326
- label: 'Kimi K2.5',
327
- description: 'Provider-side custom lane supported by Qwen Code',
328
- providerId: 'kimi',
329
- },
330
- ],
331
- },
332
- },
333
- },
334
- };
335
-
336
- function flattenEngineModels(engineId, engine) {
337
- return Object.entries(engine.lanes || {}).flatMap(([laneId, lane]) =>
338
- (lane.models || []).map((model) => ({
339
- ...model,
340
- lane: laneId,
341
- laneLabel: lane.label || laneId,
342
- runtimeId: model.runtimeId || lane.runtimeId,
343
- category: engineId,
344
- engine: engineId,
345
- endpoint: engine.endpoint,
346
- custom: laneId === 'custom',
347
- }))
348
- );
349
- }
350
-
351
- function getCatalog() {
352
- return JSON.parse(JSON.stringify(CATALOG));
353
- }
354
-
355
- function getCliTools() {
356
- const catalog = getCatalog();
357
- const tools = {};
358
-
359
- for (const [engineId, engine] of Object.entries(catalog)) {
360
- tools[engineId] = {
361
- id: engineId,
362
- name: engine.name,
363
- icon: engine.icon,
364
- enabled: true,
365
- endpoint: engine.endpoint,
366
- thinkModes: engine.thinkModes || [],
367
- defaultThinkMode: engine.defaultThinkMode,
368
- lanes: engine.lanes,
369
- models: flattenEngineModels(engineId, engine),
370
- };
371
- }
372
-
373
- return tools;
374
- }
375
-
376
- function getAllModels() {
377
- return Object.values(getCliTools()).flatMap((cli) => cli.models || []);
378
- }
379
-
380
- function getModelById(modelId) {
381
- if (!modelId) return null;
382
- return getAllModels().find((model) => model.id === modelId) || null;
383
- }
384
-
385
- function getEngineForModel(modelId) {
386
- return getModelById(modelId)?.engine || null;
387
- }
388
-
389
- function isValidModelId(modelId) {
390
- return Boolean(getModelById(modelId));
391
- }
392
-
393
- function getDefaultModelId() {
394
- const models = getAllModels();
395
- const withDefault = models.find((m) => m.default);
396
- return withDefault ? withDefault.id : (models[0]?.id || null);
397
- }
398
-
399
- module.exports = {
400
- UNIFIED_ENDPOINT,
401
- getCatalog,
402
- getCliTools,
403
- getAllModels,
404
- getModelById,
405
- getEngineForModel,
406
- isValidModelId,
407
- getDefaultModelId,
408
- };
@@ -1,274 +0,0 @@
1
- const path = require('path');
2
- const fs = require('fs');
3
-
4
- // NexusCrew data directory
5
- const dbDir = path.join(process.env.HOME, '.nexuscrew');
6
- const dbPath = path.join(dbDir, 'nexuscrew.db');
7
-
8
- // Ensure directory exists
9
- if (!fs.existsSync(dbDir)) {
10
- fs.mkdirSync(dbDir, { recursive: true });
11
- console.log(`✅ Created database directory: ${dbDir}`);
12
- }
13
-
14
- // Use sql.js driver
15
- const Driver = require('./drivers/sql-js');
16
- let db = null;
17
-
18
- console.log(`📦 Using sql.js driver`);
19
-
20
- // Initialize database
21
- async function initDb(options = {}) {
22
- const { skipMigrationCheck = false } = options;
23
- const driver = new Driver(dbPath);
24
- await driver.init();
25
- db = driver;
26
-
27
- console.log(`✅ Database ready: ${dbPath}`);
28
-
29
- // Initialize schema
30
- initSchema();
31
-
32
- if (!skipMigrationCheck) {
33
- const needsMigration = await checkMigrationNeeded();
34
-
35
- if (needsMigration) {
36
- console.log('[DB] Migration required - running migrations...');
37
- const { runMigrations } = require('./migrate');
38
- await runMigrations({ skipInit: true });
39
- }
40
- }
41
-
42
- return db;
43
- }
44
-
45
- function initSchema() {
46
- db.exec(`
47
- -- Conversations table (NexusCrew + auth schema)
48
- CREATE TABLE IF NOT EXISTS conversations (
49
- id TEXT PRIMARY KEY,
50
- title TEXT DEFAULT 'New Chat',
51
- engine TEXT NOT NULL DEFAULT 'claude',
52
- model TEXT,
53
- tmux_window TEXT,
54
- log_path TEXT,
55
- workspace TEXT DEFAULT '',
56
- host TEXT DEFAULT 'local',
57
- status TEXT DEFAULT 'active',
58
- pinned INTEGER DEFAULT 0,
59
- metadata TEXT,
60
- created_at INTEGER DEFAULT (unixepoch()),
61
- updated_at INTEGER DEFAULT (unixepoch())
62
- );
63
-
64
- -- Messages table
65
- CREATE TABLE IF NOT EXISTS messages (
66
- id TEXT PRIMARY KEY,
67
- conversation_id TEXT NOT NULL,
68
- role TEXT NOT NULL,
69
- content TEXT DEFAULT '',
70
- engine TEXT,
71
- model TEXT,
72
- tokens_in INTEGER DEFAULT 0,
73
- tokens_out INTEGER DEFAULT 0,
74
- created_at INTEGER DEFAULT (unixepoch()),
75
- FOREIGN KEY (conversation_id) REFERENCES conversations(id)
76
- );
77
-
78
- -- Users table (auth)
79
- CREATE TABLE IF NOT EXISTS users (
80
- id TEXT PRIMARY KEY,
81
- username TEXT UNIQUE NOT NULL,
82
- password_hash TEXT NOT NULL,
83
- role TEXT NOT NULL DEFAULT 'user',
84
- is_locked INTEGER NOT NULL DEFAULT 0,
85
- failed_attempts INTEGER NOT NULL DEFAULT 0,
86
- last_failed_attempt INTEGER,
87
- locked_until INTEGER,
88
- created_at INTEGER NOT NULL,
89
- last_login INTEGER
90
- );
91
-
92
- -- API Keys table
93
- CREATE TABLE IF NOT EXISTS api_keys (
94
- provider TEXT PRIMARY KEY,
95
- api_key TEXT NOT NULL,
96
- created_at INTEGER NOT NULL,
97
- updated_at INTEGER NOT NULL
98
- );
99
-
100
- -- Login attempts table
101
- CREATE TABLE IF NOT EXISTS login_attempts (
102
- id INTEGER PRIMARY KEY AUTOINCREMENT,
103
- ip_address TEXT NOT NULL,
104
- username TEXT,
105
- success INTEGER NOT NULL DEFAULT 0,
106
- timestamp INTEGER NOT NULL
107
- );
108
-
109
- CREATE INDEX IF NOT EXISTS idx_login_attempts_ip
110
- ON login_attempts(ip_address, timestamp DESC);
111
-
112
- -- Session summaries table
113
- CREATE TABLE IF NOT EXISTS session_summaries (
114
- conversation_id TEXT PRIMARY KEY,
115
- summary_short TEXT,
116
- key_decisions TEXT,
117
- files_modified TEXT,
118
- updated_at INTEGER,
119
- FOREIGN KEY (conversation_id) REFERENCES conversations(id)
120
- );
121
-
122
- -- Indexes
123
- CREATE INDEX IF NOT EXISTS idx_messages_conv ON messages(conversation_id, created_at);
124
- CREATE INDEX IF NOT EXISTS idx_conversations_status ON conversations(status, updated_at);
125
- CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
126
- `);
127
-
128
- console.log('✅ Database schema initialized');
129
- }
130
-
131
- /**
132
- * Get API key for a provider
133
- * @param {string} provider - Provider name (e.g., 'deepseek', 'openai')
134
- * @returns {string|null} API key or null if not found
135
- */
136
- function getApiKey(provider) {
137
- if (!db) return null;
138
- try {
139
- const stmt = db.prepare('SELECT api_key FROM api_keys WHERE provider = ?');
140
- const row = stmt.get(provider.toLowerCase());
141
- return row?.api_key || null;
142
- } catch (err) {
143
- console.error(`[DB] Error getting API key for ${provider}:`, err.message);
144
- return null;
145
- }
146
- }
147
-
148
- /**
149
- * Set API key for a provider
150
- * @param {string} provider - Provider name
151
- * @param {string} apiKey - API key value
152
- * @returns {boolean} Success
153
- */
154
- function setApiKey(provider, apiKey) {
155
- if (!db) return false;
156
- try {
157
- const now = Date.now();
158
- const stmt = db.prepare(`
159
- INSERT INTO api_keys (provider, api_key, created_at, updated_at)
160
- VALUES (?, ?, ?, ?)
161
- ON CONFLICT(provider) DO UPDATE SET api_key = ?, updated_at = ?
162
- `);
163
- stmt.run(provider.toLowerCase(), apiKey, now, now, apiKey, now);
164
- db.save();
165
- return true;
166
- } catch (err) {
167
- console.error(`[DB] Error setting API key for ${provider}:`, err.message);
168
- return false;
169
- }
170
- }
171
-
172
- /**
173
- * Delete API key for a provider
174
- * @param {string} provider - Provider name
175
- * @returns {boolean} Success
176
- */
177
- function deleteApiKey(provider) {
178
- if (!db) return false;
179
- try {
180
- const stmt = db.prepare('DELETE FROM api_keys WHERE provider = ?');
181
- stmt.run(provider.toLowerCase());
182
- db.save();
183
- return true;
184
- } catch (err) {
185
- console.error(`[DB] Error deleting API key for ${provider}:`, err.message);
186
- return false;
187
- }
188
- }
189
-
190
- /**
191
- * List all configured API key providers (without exposing keys)
192
- * @returns {Array} List of provider names
193
- */
194
- function listApiKeyProviders() {
195
- if (!db) return [];
196
- try {
197
- const stmt = db.prepare('SELECT provider, created_at, updated_at FROM api_keys');
198
- return stmt.all();
199
- } catch (err) {
200
- console.error('[DB] Error listing API key providers:', err.message);
201
- return [];
202
- }
203
- }
204
-
205
- async function checkMigrationNeeded() {
206
- const currentDb = getDb();
207
-
208
- if (!currentDb) {
209
- console.error('[DB] Cannot check migration status - DB not initialized');
210
- return false;
211
- }
212
-
213
- try {
214
- const hasMigrationsStmt = currentDb.prepare(`
215
- SELECT name FROM sqlite_master
216
- WHERE type='table' AND name='_migrations'
217
- `);
218
- const hasMigrations = hasMigrationsStmt.get();
219
-
220
- if (!hasMigrations) {
221
- console.log('[DB] _migrations table not found - first run or migration needed');
222
- return true;
223
- }
224
-
225
- console.log('[DB] No migration needed');
226
- return false;
227
- } catch (error) {
228
- console.error('[DB] Error checking migration status:', error);
229
- return false;
230
- }
231
- }
232
-
233
- function getDb() {
234
- return db;
235
- }
236
-
237
- function prepare(sql) {
238
- return db.prepare(sql);
239
- }
240
-
241
- function saveDb() {
242
- if (db) db.save();
243
- }
244
-
245
- // Graceful shutdown
246
- process.on('exit', () => {
247
- if (db) {
248
- db.close();
249
- console.log('✅ Database connection closed');
250
- }
251
- });
252
-
253
- process.on('SIGINT', () => {
254
- if (db) {
255
- db.close();
256
- console.log('✅ Database connection closed (SIGINT)');
257
- }
258
- process.exit(0);
259
- });
260
-
261
- process.on('SIGTERM', () => {
262
- if (db) db.close();
263
- });
264
-
265
- module.exports = {
266
- initDb,
267
- getDb,
268
- prepare,
269
- saveDb,
270
- getApiKey,
271
- setApiKey,
272
- deleteApiKey,
273
- listApiKeyProviders
274
- };