omnikey-cli 1.5.8 → 1.6.1
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/backend-dist/__tests__/ai-client.nemotron.test.js +127 -0
- package/backend-dist/agent/agentPrompts.js +4 -3
- package/backend-dist/agent/utils.js +6 -5
- package/backend-dist/ai-client.js +151 -16
- package/backend-dist/aiProviderRoutes.js +247 -0
- package/backend-dist/config.js +16 -1
- package/backend-dist/db.js +5 -1
- package/backend-dist/index.js +27 -3
- package/backend-dist/mcpServerRoutes.js +16 -4
- package/backend-dist/scheduledJobRoutes.js +5 -2
- package/dist/index.js +1 -1
- package/dist/onboard.js +38 -0
- package/dist/telegramClient.js +1 -1
- package/dist/telegramDaemon.js +6 -4
- package/package.json +8 -6
- package/src/index.ts +1 -1
- package/src/onboard.ts +38 -0
- package/src/telegramClient.ts +1 -1
- package/src/telegramDaemon.ts +6 -8
- package/telegram-client-dist/{dist/agentClient.js → agentClient.js} +69 -75
- package/telegram-client-dist/{dist/config.js → config.js} +10 -12
- package/telegram-client-dist/{dist/index.js → index.js} +23 -23
- package/telegram-client-dist/{dist/notifyTelegram.js → notifyTelegram.js} +177 -191
- package/telegram-client-dist/{dist/omnikeyAuth.js → omnikeyAuth.js} +8 -13
package/src/telegramClient.ts
CHANGED
|
@@ -24,7 +24,7 @@ function resolveBundleRoot(): string {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function resolveBundledEntry(): string {
|
|
27
|
-
return path.join(resolveBundleRoot(), '
|
|
27
|
+
return path.join(resolveBundleRoot(), 'index.js');
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function persistConfig(values: Record<string, string>): void {
|
package/src/telegramDaemon.ts
CHANGED
|
@@ -11,12 +11,12 @@ const PLIST_NAME = `${LABEL}.plist`;
|
|
|
11
11
|
const WINDOWS_SERVICE_NAME = 'OmnikeyTelegram';
|
|
12
12
|
|
|
13
13
|
// At runtime __dirname is cli/dist/. The bundled telegram app is copied into
|
|
14
|
-
// cli/telegram-client-dist/ by the build:telegram-client script
|
|
15
|
-
// up from dist/ lands at the package
|
|
14
|
+
// cli/telegram-client-dist/ by the build:telegram-client script (flat layout,
|
|
15
|
+
// matching backend-dist/), so one level up from dist/ lands at the package
|
|
16
16
|
// This matches resolveBundleRoot() in telegramClient.ts and works correctly
|
|
17
17
|
// both in the monorepo and after `npm install -g omnikey-cli`.
|
|
18
18
|
const TELEGRAM_BOT_ROOT = path.resolve(__dirname, '..', 'telegram-client-dist');
|
|
19
|
-
const ENTRY_POINT = path.join(TELEGRAM_BOT_ROOT, '
|
|
19
|
+
const ENTRY_POINT = path.join(TELEGRAM_BOT_ROOT, 'index.js');
|
|
20
20
|
|
|
21
21
|
const HOME = getHomeDir();
|
|
22
22
|
|
|
@@ -300,11 +300,9 @@ async function startWindows(): Promise<void> {
|
|
|
300
300
|
execFileSync(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'Start', 'SERVICE_AUTO_START'], {
|
|
301
301
|
stdio: 'pipe',
|
|
302
302
|
});
|
|
303
|
-
execFileSync(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
{ stdio: 'pipe' },
|
|
307
|
-
);
|
|
303
|
+
execFileSync(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'DisplayName', 'Omnikey Telegram'], {
|
|
304
|
+
stdio: 'pipe',
|
|
305
|
+
});
|
|
308
306
|
execFileSync(
|
|
309
307
|
nssmPath,
|
|
310
308
|
['set', WINDOWS_SERVICE_NAME, 'Description', 'Omnikey Telegram Daemon'],
|
|
@@ -70,7 +70,7 @@ async function setDefaultTaskTemplate(logger, templateId) {
|
|
|
70
70
|
timeout: 10000,
|
|
71
71
|
headers: { Authorization: `Bearer ${token}` },
|
|
72
72
|
});
|
|
73
|
-
logger.info(
|
|
73
|
+
logger.info('Set default task template', { templateId });
|
|
74
74
|
}
|
|
75
75
|
async function listProjectGroups(logger) {
|
|
76
76
|
const token = await (0, omnikeyAuth_1.fetchJwtToken)(logger);
|
|
@@ -82,67 +82,63 @@ async function listProjectGroups(logger) {
|
|
|
82
82
|
return resp.data?.groups ?? [];
|
|
83
83
|
}
|
|
84
84
|
class AgentAbortError extends Error {
|
|
85
|
-
constructor(message =
|
|
85
|
+
constructor(message = 'Agent run aborted') {
|
|
86
86
|
super(message);
|
|
87
|
-
this.name =
|
|
87
|
+
this.name = 'AgentAbortError';
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
exports.AgentAbortError = AgentAbortError;
|
|
91
91
|
function extractTagged(content, tag) {
|
|
92
|
-
const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`,
|
|
92
|
+
const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'i');
|
|
93
93
|
const m = content.match(re);
|
|
94
94
|
return m?.[1]?.trim() || null;
|
|
95
95
|
}
|
|
96
96
|
function stripTagged(content, tag) {
|
|
97
|
-
return content.replace(new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`,
|
|
97
|
+
return content.replace(new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`, 'gi'), '');
|
|
98
98
|
}
|
|
99
99
|
function cleanReasoning(content) {
|
|
100
100
|
return content
|
|
101
|
-
.replace(/<\/?shell_function_calls>/gi,
|
|
102
|
-
.replace(/<final_answer>([\s\S]*?)<\/final_answer>/gi,
|
|
101
|
+
.replace(/<\/?shell_function_calls>/gi, '')
|
|
102
|
+
.replace(/<final_answer>([\s\S]*?)<\/final_answer>/gi, '$1')
|
|
103
103
|
.trim();
|
|
104
104
|
}
|
|
105
105
|
const SHELL_TIMEOUT_MS = 5 * 60 * 1000;
|
|
106
106
|
const SHELL_OUTPUT_MAX = 64 * 1024;
|
|
107
107
|
// Mirrors WINDOWS_SHELL_CANDIDATES in src/agent/mcpRuntime.ts
|
|
108
108
|
const WINDOWS_SHELL_CANDIDATES = [
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
'C:\\Program Files\\PowerShell\\7\\pwsh.exe',
|
|
110
|
+
'C:\\Program Files\\PowerShell\\6\\pwsh.exe',
|
|
111
|
+
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
|
|
112
|
+
'C:\\Windows\\System32\\cmd.exe',
|
|
113
|
+
'C:\\Windows\\cmd.exe',
|
|
114
114
|
];
|
|
115
115
|
// Resolve the Windows shell: COMSPEC → SystemRoot\System32\cmd.exe → candidate list.
|
|
116
116
|
// Mirrors resolveLoginShell() in src/agent/mcpRuntime.ts, with SystemRoot used to
|
|
117
117
|
// locate cmd.exe from the Win32 system root rather than a hardcoded drive letter.
|
|
118
118
|
function resolveWindowsShell() {
|
|
119
|
-
const comspec = process.env.COMSPEC ??
|
|
119
|
+
const comspec = process.env.COMSPEC ?? '';
|
|
120
120
|
if (comspec && (0, fs_1.existsSync)(comspec))
|
|
121
121
|
return comspec;
|
|
122
|
-
const systemRoot = process.env.SystemRoot ??
|
|
123
|
-
const cmdFromRoot = path_1.default.join(systemRoot,
|
|
122
|
+
const systemRoot = process.env.SystemRoot ?? 'C:\\Windows';
|
|
123
|
+
const cmdFromRoot = path_1.default.join(systemRoot, 'System32', 'cmd.exe');
|
|
124
124
|
if ((0, fs_1.existsSync)(cmdFromRoot))
|
|
125
125
|
return cmdFromRoot;
|
|
126
126
|
for (const candidate of WINDOWS_SHELL_CANDIDATES) {
|
|
127
127
|
if ((0, fs_1.existsSync)(candidate))
|
|
128
128
|
return candidate;
|
|
129
129
|
}
|
|
130
|
-
return
|
|
130
|
+
return 'cmd.exe';
|
|
131
131
|
}
|
|
132
132
|
// Build shell args for the resolved shell — mirrors wrapWithLoginShell() in
|
|
133
133
|
// src/agent/mcpRuntime.ts. PowerShell/pwsh use -NoProfile -Command; cmd uses /c.
|
|
134
134
|
function buildWindowsShellArgs(shell, script) {
|
|
135
135
|
const name = path_1.default.basename(shell).toLowerCase();
|
|
136
|
-
if (name ===
|
|
137
|
-
return [
|
|
136
|
+
if (name === 'pwsh.exe' || name === 'powershell.exe') {
|
|
137
|
+
return ['-NoProfile', '-Command', script];
|
|
138
138
|
}
|
|
139
|
-
return [
|
|
139
|
+
return ['/c', script];
|
|
140
140
|
}
|
|
141
|
-
const PLATFORM = process.platform ===
|
|
142
|
-
? "windows"
|
|
143
|
-
: process.platform === "darwin"
|
|
144
|
-
? "macos"
|
|
145
|
-
: "linux";
|
|
141
|
+
const PLATFORM = process.platform === 'win32' ? 'windows' : process.platform === 'darwin' ? 'macos' : 'linux';
|
|
146
142
|
/**
|
|
147
143
|
* Execute a shell script locally and capture combined stdout+stderr.
|
|
148
144
|
* On macOS/Linux: invoke the login shell with `-l -c <script>` (mirrors the
|
|
@@ -156,15 +152,15 @@ function runShellScript(script, logger) {
|
|
|
156
152
|
return new Promise((resolve) => {
|
|
157
153
|
let shell;
|
|
158
154
|
let shellArgs;
|
|
159
|
-
if (process.platform !==
|
|
155
|
+
if (process.platform !== 'darwin' && process.platform === 'win32') {
|
|
160
156
|
shell = resolveWindowsShell();
|
|
161
157
|
shellArgs = buildWindowsShellArgs(shell, script);
|
|
162
158
|
}
|
|
163
159
|
else {
|
|
164
|
-
shell = process.env.SHELL ||
|
|
165
|
-
shellArgs = [
|
|
160
|
+
shell = process.env.SHELL || '/bin/zsh';
|
|
161
|
+
shellArgs = ['-l', '-c', script];
|
|
166
162
|
}
|
|
167
|
-
logger.info(
|
|
163
|
+
logger.info('Executing shell script from agent', {
|
|
168
164
|
shell,
|
|
169
165
|
platform: PLATFORM,
|
|
170
166
|
length: script.length,
|
|
@@ -173,7 +169,7 @@ function runShellScript(script, logger) {
|
|
|
173
169
|
cwd: process.env.HOME ?? process.env.USERPROFILE ?? process.cwd(),
|
|
174
170
|
env: process.env,
|
|
175
171
|
});
|
|
176
|
-
let buf =
|
|
172
|
+
let buf = '';
|
|
177
173
|
let truncated = false;
|
|
178
174
|
const append = (chunk) => {
|
|
179
175
|
if (truncated)
|
|
@@ -183,7 +179,7 @@ function runShellScript(script, logger) {
|
|
|
183
179
|
truncated = true;
|
|
184
180
|
return;
|
|
185
181
|
}
|
|
186
|
-
const text = chunk.toString(
|
|
182
|
+
const text = chunk.toString('utf8');
|
|
187
183
|
if (text.length <= room) {
|
|
188
184
|
buf += text;
|
|
189
185
|
}
|
|
@@ -192,33 +188,31 @@ function runShellScript(script, logger) {
|
|
|
192
188
|
truncated = true;
|
|
193
189
|
}
|
|
194
190
|
};
|
|
195
|
-
child.stdout.on(
|
|
196
|
-
child.stderr.on(
|
|
191
|
+
child.stdout.on('data', append);
|
|
192
|
+
child.stderr.on('data', append);
|
|
197
193
|
const timeout = setTimeout(() => {
|
|
198
|
-
logger.warn(
|
|
194
|
+
logger.warn('Shell script timed out; sending SIGTERM', {
|
|
199
195
|
timeoutMs: SHELL_TIMEOUT_MS,
|
|
200
196
|
});
|
|
201
197
|
try {
|
|
202
|
-
child.kill(
|
|
198
|
+
child.kill('SIGTERM');
|
|
203
199
|
}
|
|
204
200
|
catch {
|
|
205
201
|
/* noop */
|
|
206
202
|
}
|
|
207
203
|
}, SHELL_TIMEOUT_MS);
|
|
208
|
-
child.on(
|
|
204
|
+
child.on('error', (err) => {
|
|
209
205
|
clearTimeout(timeout);
|
|
210
206
|
resolve({
|
|
211
207
|
output: `${buf}\n[shell spawn error: ${err.message}]`,
|
|
212
208
|
status: -1,
|
|
213
209
|
});
|
|
214
210
|
});
|
|
215
|
-
child.on(
|
|
211
|
+
child.on('close', (code, signal) => {
|
|
216
212
|
clearTimeout(timeout);
|
|
217
|
-
const status = typeof code ===
|
|
218
|
-
const finalOutput = truncated
|
|
219
|
-
|
|
220
|
-
: buf;
|
|
221
|
-
logger.info("Shell script finished", {
|
|
213
|
+
const status = typeof code === 'number' ? code : signal ? 1 : 0;
|
|
214
|
+
const finalOutput = truncated ? `${buf}\n... [truncated to ${SHELL_OUTPUT_MAX} bytes]` : buf;
|
|
215
|
+
logger.info('Shell script finished', {
|
|
222
216
|
status,
|
|
223
217
|
signal,
|
|
224
218
|
outputLength: finalOutput.length,
|
|
@@ -239,7 +233,7 @@ function runShellScript(script, logger) {
|
|
|
239
233
|
async function runAgentTurn(logger, opts) {
|
|
240
234
|
const token = await (0, omnikeyAuth_1.fetchJwtToken)(logger);
|
|
241
235
|
const sessionId = opts.sessionId || (0, crypto_1.randomUUID)();
|
|
242
|
-
const url = (0, config_1.omnikeyWsUrl)(
|
|
236
|
+
const url = (0, config_1.omnikeyWsUrl)('/ws/omni-agent');
|
|
243
237
|
return new Promise((resolve, reject) => {
|
|
244
238
|
if (opts.signal?.aborted) {
|
|
245
239
|
reject(new AgentAbortError());
|
|
@@ -254,7 +248,7 @@ async function runAgentTurn(logger, opts) {
|
|
|
254
248
|
return;
|
|
255
249
|
settled = true;
|
|
256
250
|
if (opts.signal && onAbort) {
|
|
257
|
-
opts.signal.removeEventListener(
|
|
251
|
+
opts.signal.removeEventListener('abort', onAbort);
|
|
258
252
|
}
|
|
259
253
|
try {
|
|
260
254
|
ws.close();
|
|
@@ -269,12 +263,12 @@ async function runAgentTurn(logger, opts) {
|
|
|
269
263
|
};
|
|
270
264
|
const onAbort = opts.signal
|
|
271
265
|
? () => {
|
|
272
|
-
logger.info(
|
|
266
|
+
logger.info('Agent run aborted by caller', { sessionId });
|
|
273
267
|
finish(new AgentAbortError());
|
|
274
268
|
}
|
|
275
269
|
: null;
|
|
276
270
|
if (opts.signal && onAbort) {
|
|
277
|
-
opts.signal.addEventListener(
|
|
271
|
+
opts.signal.addEventListener('abort', onAbort, { once: true });
|
|
278
272
|
}
|
|
279
273
|
const send = (msg) => {
|
|
280
274
|
ws.send(JSON.stringify(msg), (err) => {
|
|
@@ -282,11 +276,11 @@ async function runAgentTurn(logger, opts) {
|
|
|
282
276
|
finish(err);
|
|
283
277
|
});
|
|
284
278
|
};
|
|
285
|
-
ws.on(
|
|
286
|
-
logger.info(
|
|
279
|
+
ws.on('open', () => {
|
|
280
|
+
logger.info('Agent WebSocket open', { sessionId });
|
|
287
281
|
send({
|
|
288
282
|
session_id: sessionId,
|
|
289
|
-
sender:
|
|
283
|
+
sender: 'client',
|
|
290
284
|
content: opts.prompt,
|
|
291
285
|
is_terminal_output: false,
|
|
292
286
|
is_error: false,
|
|
@@ -294,56 +288,56 @@ async function runAgentTurn(logger, opts) {
|
|
|
294
288
|
group_name: opts.groupName,
|
|
295
289
|
});
|
|
296
290
|
});
|
|
297
|
-
ws.on(
|
|
291
|
+
ws.on('message', async (data) => {
|
|
298
292
|
let msg;
|
|
299
293
|
try {
|
|
300
294
|
msg = JSON.parse(data.toString());
|
|
301
295
|
}
|
|
302
296
|
catch (e) {
|
|
303
|
-
logger.warn(
|
|
297
|
+
logger.warn('Failed to parse agent ws message', {
|
|
304
298
|
error: e.message,
|
|
305
299
|
});
|
|
306
300
|
return;
|
|
307
301
|
}
|
|
308
|
-
const content = msg.content ||
|
|
302
|
+
const content = msg.content || '';
|
|
309
303
|
if (msg.is_error) {
|
|
310
|
-
finish(new Error(content ||
|
|
304
|
+
finish(new Error(content || 'Agent reported an error'));
|
|
311
305
|
return;
|
|
312
306
|
}
|
|
313
307
|
if (msg.is_web_call) {
|
|
314
|
-
await opts.onBlock({ kind:
|
|
308
|
+
await opts.onBlock({ kind: 'webCall', text: content });
|
|
315
309
|
return;
|
|
316
310
|
}
|
|
317
311
|
if (msg.is_image_rendering) {
|
|
318
|
-
await opts.onBlock({ kind:
|
|
312
|
+
await opts.onBlock({ kind: 'imageRendering', text: content });
|
|
319
313
|
return;
|
|
320
314
|
}
|
|
321
315
|
if (msg.is_mcp_call) {
|
|
322
|
-
await opts.onBlock({ kind:
|
|
316
|
+
await opts.onBlock({ kind: 'mcpCall', text: content });
|
|
323
317
|
return;
|
|
324
318
|
}
|
|
325
|
-
const finalAnswer = extractTagged(content,
|
|
319
|
+
const finalAnswer = extractTagged(content, 'final_answer');
|
|
326
320
|
if (finalAnswer) {
|
|
327
|
-
await opts.onBlock({ kind:
|
|
321
|
+
await opts.onBlock({ kind: 'finalAnswer', text: finalAnswer });
|
|
328
322
|
finish(null, { sessionId, finalAnswer });
|
|
329
323
|
return;
|
|
330
324
|
}
|
|
331
|
-
const shellScript = extractTagged(content,
|
|
325
|
+
const shellScript = extractTagged(content, 'shell_script');
|
|
332
326
|
if (shellScript) {
|
|
333
|
-
const reasoning = cleanReasoning(stripTagged(content,
|
|
327
|
+
const reasoning = cleanReasoning(stripTagged(content, 'shell_script'));
|
|
334
328
|
if (reasoning)
|
|
335
|
-
await opts.onBlock({ kind:
|
|
336
|
-
await opts.onBlock({ kind:
|
|
329
|
+
await opts.onBlock({ kind: 'reasoning', text: reasoning });
|
|
330
|
+
await opts.onBlock({ kind: 'shellCommand', text: shellScript });
|
|
337
331
|
try {
|
|
338
332
|
const { output, status } = await runShellScript(shellScript, logger);
|
|
339
|
-
const statusLabel = status === 0 ?
|
|
333
|
+
const statusLabel = status === 0 ? 'success' : `error (exit code: ${status})`;
|
|
340
334
|
await opts.onBlock({
|
|
341
|
-
kind:
|
|
335
|
+
kind: 'terminalOutput',
|
|
342
336
|
text: `[terminal ${statusLabel}]\n${output}`,
|
|
343
337
|
});
|
|
344
338
|
send({
|
|
345
339
|
session_id: sessionId,
|
|
346
|
-
sender:
|
|
340
|
+
sender: 'client',
|
|
347
341
|
content: output,
|
|
348
342
|
is_terminal_output: true,
|
|
349
343
|
is_error: status !== 0,
|
|
@@ -352,14 +346,14 @@ async function runAgentTurn(logger, opts) {
|
|
|
352
346
|
}
|
|
353
347
|
catch (err) {
|
|
354
348
|
const message = err.message;
|
|
355
|
-
logger.error(
|
|
349
|
+
logger.error('Shell execution failed', { error: message });
|
|
356
350
|
await opts.onBlock({
|
|
357
|
-
kind:
|
|
351
|
+
kind: 'terminalOutput',
|
|
358
352
|
text: `[terminal error]\n${message}`,
|
|
359
353
|
});
|
|
360
354
|
send({
|
|
361
355
|
session_id: sessionId,
|
|
362
|
-
sender:
|
|
356
|
+
sender: 'client',
|
|
363
357
|
content: `Failed to execute shell script: ${message}`,
|
|
364
358
|
is_terminal_output: true,
|
|
365
359
|
is_error: true,
|
|
@@ -370,16 +364,16 @@ async function runAgentTurn(logger, opts) {
|
|
|
370
364
|
}
|
|
371
365
|
const reasoning = cleanReasoning(content);
|
|
372
366
|
if (reasoning) {
|
|
373
|
-
await opts.onBlock({ kind:
|
|
367
|
+
await opts.onBlock({ kind: 'reasoning', text: reasoning });
|
|
374
368
|
}
|
|
375
369
|
});
|
|
376
|
-
ws.on(
|
|
377
|
-
logger.error(
|
|
370
|
+
ws.on('error', (err) => {
|
|
371
|
+
logger.error('Agent WebSocket error', { error: err.message });
|
|
378
372
|
finish(err);
|
|
379
373
|
});
|
|
380
|
-
ws.on(
|
|
374
|
+
ws.on('close', () => {
|
|
381
375
|
if (!settled)
|
|
382
|
-
finish(new Error(
|
|
376
|
+
finish(new Error('Agent WebSocket closed before final answer'));
|
|
383
377
|
});
|
|
384
378
|
});
|
|
385
379
|
}
|
|
@@ -391,10 +385,10 @@ function extractFinalAnswerFromHistory(historyJson) {
|
|
|
391
385
|
const history = JSON.parse(historyJson);
|
|
392
386
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
393
387
|
const entry = history[i];
|
|
394
|
-
if (entry.role !==
|
|
388
|
+
if (entry.role !== 'assistant')
|
|
395
389
|
continue;
|
|
396
|
-
const content = typeof entry.content ===
|
|
397
|
-
const fa = extractTagged(content,
|
|
390
|
+
const content = typeof entry.content === 'string' ? entry.content : '';
|
|
391
|
+
const fa = extractTagged(content, 'final_answer');
|
|
398
392
|
if (fa)
|
|
399
393
|
return fa;
|
|
400
394
|
}
|
|
@@ -9,23 +9,21 @@ exports.omnikeyWsUrl = omnikeyWsUrl;
|
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const os_1 = __importDefault(require("os"));
|
|
12
|
-
const DEFAULT_HOST =
|
|
12
|
+
const DEFAULT_HOST = '127.0.0.1';
|
|
13
13
|
const DEFAULT_PORT = 7071;
|
|
14
|
-
const DEFAULT_SQLITE = path_1.default.join(os_1.default.homedir(),
|
|
15
|
-
const CONFIG_PATH = path_1.default.join(os_1.default.homedir(),
|
|
14
|
+
const DEFAULT_SQLITE = path_1.default.join(os_1.default.homedir(), '.omnikey', 'omnikey-selfhosted.sqlite');
|
|
15
|
+
const CONFIG_PATH = path_1.default.join(os_1.default.homedir(), '.omnikey', 'config.json');
|
|
16
16
|
let cached = null;
|
|
17
17
|
function resolveSqlitePath(raw) {
|
|
18
|
-
if (typeof raw ===
|
|
19
|
-
return path_1.default.isAbsolute(raw)
|
|
20
|
-
? raw
|
|
21
|
-
: path_1.default.join(os_1.default.homedir(), ".omnikey", raw);
|
|
18
|
+
if (typeof raw === 'string' && raw.trim()) {
|
|
19
|
+
return path_1.default.isAbsolute(raw) ? raw : path_1.default.join(os_1.default.homedir(), '.omnikey', raw);
|
|
22
20
|
}
|
|
23
21
|
return DEFAULT_SQLITE;
|
|
24
22
|
}
|
|
25
23
|
function resolvePort(raw) {
|
|
26
|
-
if (typeof raw ===
|
|
24
|
+
if (typeof raw === 'number' && Number.isFinite(raw))
|
|
27
25
|
return raw;
|
|
28
|
-
if (typeof raw ===
|
|
26
|
+
if (typeof raw === 'string' && raw.trim()) {
|
|
29
27
|
const n = Number(raw);
|
|
30
28
|
if (Number.isFinite(n))
|
|
31
29
|
return n;
|
|
@@ -38,7 +36,7 @@ function loadOmnikeyConfig() {
|
|
|
38
36
|
let parsed = {};
|
|
39
37
|
try {
|
|
40
38
|
if (fs_1.default.existsSync(CONFIG_PATH)) {
|
|
41
|
-
const raw = fs_1.default.readFileSync(CONFIG_PATH,
|
|
39
|
+
const raw = fs_1.default.readFileSync(CONFIG_PATH, 'utf-8');
|
|
42
40
|
parsed = JSON.parse(raw);
|
|
43
41
|
}
|
|
44
42
|
}
|
|
@@ -50,7 +48,7 @@ function loadOmnikeyConfig() {
|
|
|
50
48
|
cached = {
|
|
51
49
|
sqlitePath: resolveSqlitePath(parsed.SQLITE_PATH),
|
|
52
50
|
omnikeyPort: resolvePort(parsed.OMNIKEY_PORT),
|
|
53
|
-
omnikeyHost: typeof parsed.OMNIKEY_HOST ===
|
|
51
|
+
omnikeyHost: typeof parsed.OMNIKEY_HOST === 'string' && parsed.OMNIKEY_HOST.trim()
|
|
54
52
|
? parsed.OMNIKEY_HOST
|
|
55
53
|
: DEFAULT_HOST,
|
|
56
54
|
};
|
|
@@ -62,6 +60,6 @@ function omnikeyBaseUrl() {
|
|
|
62
60
|
}
|
|
63
61
|
function omnikeyWsUrl(path) {
|
|
64
62
|
const { omnikeyHost, omnikeyPort } = loadOmnikeyConfig();
|
|
65
|
-
const suffix = path.startsWith(
|
|
63
|
+
const suffix = path.startsWith('/') ? path : `/${path}`;
|
|
66
64
|
return `ws://${omnikeyHost}:${omnikeyPort}${suffix}`;
|
|
67
65
|
}
|
|
@@ -12,10 +12,10 @@ const winston_1 = __importDefault(require("winston"));
|
|
|
12
12
|
const zod_1 = require("zod");
|
|
13
13
|
const notifyTelegram_1 = require("./notifyTelegram");
|
|
14
14
|
exports.logger = winston_1.default.createLogger({
|
|
15
|
-
level: process.env.LOG_LEVEL ||
|
|
15
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
16
16
|
defaultMeta: { conId: (0, crypto_1.randomUUID)() },
|
|
17
17
|
format: winston_1.default.format.combine(winston_1.default.format.colorize(), winston_1.default.format.timestamp(), winston_1.default.format.printf(({ timestamp, level, message, ...meta }) => {
|
|
18
|
-
const metaString = Object.keys(meta).length ? JSON.stringify(meta) :
|
|
18
|
+
const metaString = Object.keys(meta).length ? JSON.stringify(meta) : '';
|
|
19
19
|
const date = new Date(timestamp).toLocaleString();
|
|
20
20
|
return `[${date}] ${level}: ${message} ${metaString}`;
|
|
21
21
|
})),
|
|
@@ -23,37 +23,37 @@ exports.logger = winston_1.default.createLogger({
|
|
|
23
23
|
});
|
|
24
24
|
const app = (0, express_1.default)();
|
|
25
25
|
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 6666;
|
|
26
|
-
const botToken = process.env.TELEGRAM_BOT_TOKEN ??
|
|
26
|
+
const botToken = process.env.TELEGRAM_BOT_TOKEN ?? '';
|
|
27
27
|
if (botToken) {
|
|
28
28
|
try {
|
|
29
29
|
const bot = (0, notifyTelegram_1.initTelegram)(botToken);
|
|
30
|
-
exports.logger.info(
|
|
30
|
+
exports.logger.info('Telegram bot initialized', {
|
|
31
31
|
botTokenSet: !!botToken,
|
|
32
32
|
bot: !!bot,
|
|
33
33
|
});
|
|
34
34
|
(0, notifyTelegram_1.setupMessageListener)(exports.logger, bot);
|
|
35
35
|
}
|
|
36
36
|
catch (e) {
|
|
37
|
-
exports.logger.error(
|
|
37
|
+
exports.logger.error('Failed to init telegram:', e);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
app.use(express_1.default.json());
|
|
41
41
|
const sendBodySchema = zod_1.z.object({
|
|
42
|
-
message: zod_1.z.string().min(1,
|
|
43
|
-
parseMode: zod_1.z.enum([
|
|
42
|
+
message: zod_1.z.string().min(1, 'message must not be empty'),
|
|
43
|
+
parseMode: zod_1.z.enum(['Markdown', 'MarkdownV2', 'HTML']).optional(),
|
|
44
44
|
});
|
|
45
|
-
app.get(
|
|
46
|
-
res.send(
|
|
45
|
+
app.get('/', (req, res) => {
|
|
46
|
+
res.send('Telegram bot service (TypeScript)');
|
|
47
47
|
});
|
|
48
|
-
app.post(
|
|
49
|
-
exports.logger.defaultMeta = { conId:
|
|
48
|
+
app.post('/telegram/send', async (req, res) => {
|
|
49
|
+
exports.logger.defaultMeta = { conId: 'sending notification' };
|
|
50
50
|
const parsed = sendBodySchema.safeParse(req.body);
|
|
51
51
|
if (!parsed.success) {
|
|
52
|
-
exports.logger.warn(
|
|
52
|
+
exports.logger.warn('Invalid /telegram/send body', {
|
|
53
53
|
issues: parsed.error.issues,
|
|
54
54
|
});
|
|
55
55
|
return res.status(400).json({
|
|
56
|
-
message:
|
|
56
|
+
message: 'Invalid request body',
|
|
57
57
|
issues: parsed.error.issues,
|
|
58
58
|
});
|
|
59
59
|
}
|
|
@@ -61,16 +61,16 @@ app.post("/telegram/send", async (req, res) => {
|
|
|
61
61
|
try {
|
|
62
62
|
await (0, notifyTelegram_1.notify)(exports.logger, message, { parseMode });
|
|
63
63
|
return res.json({
|
|
64
|
-
message:
|
|
65
|
-
parseMode: parseMode ??
|
|
64
|
+
message: 'Message sent',
|
|
65
|
+
parseMode: parseMode ?? 'Markdown',
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
catch (e) {
|
|
69
|
-
exports.logger.error(
|
|
70
|
-
const description = e?.response?.body
|
|
71
|
-
|
|
69
|
+
exports.logger.error('Failed to send message:', e);
|
|
70
|
+
const description = e?.response?.body?.description ??
|
|
71
|
+
e.message;
|
|
72
72
|
return res.status(502).json({
|
|
73
|
-
message:
|
|
73
|
+
message: 'Failed to deliver message to Telegram',
|
|
74
74
|
error: description,
|
|
75
75
|
});
|
|
76
76
|
}
|
|
@@ -78,11 +78,11 @@ app.post("/telegram/send", async (req, res) => {
|
|
|
78
78
|
app.listen(port, () => {
|
|
79
79
|
exports.logger.info(`Server listening on http://localhost:${port}`);
|
|
80
80
|
});
|
|
81
|
-
process.on(
|
|
82
|
-
exports.logger.info(
|
|
81
|
+
process.on('SIGINT', () => {
|
|
82
|
+
exports.logger.info('Received SIGINT. Exiting...');
|
|
83
83
|
process.exit(0);
|
|
84
84
|
});
|
|
85
|
-
process.on(
|
|
86
|
-
exports.logger.info(
|
|
85
|
+
process.on('SIGTERM', () => {
|
|
86
|
+
exports.logger.info('Received SIGTERM. Exiting...');
|
|
87
87
|
process.exit(0);
|
|
88
88
|
});
|