omnikey-cli 1.5.3 → 1.5.4

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.
@@ -0,0 +1,432 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startTelegramDaemon = startTelegramDaemon;
7
+ exports.stopTelegramDaemon = stopTelegramDaemon;
8
+ exports.restartTelegramDaemon = restartTelegramDaemon;
9
+ exports.statusTelegramDaemon = statusTelegramDaemon;
10
+ exports.logsTelegramDaemon = logsTelegramDaemon;
11
+ exports.uninstallTelegramDaemon = uninstallTelegramDaemon;
12
+ const path_1 = __importDefault(require("path"));
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const os_1 = __importDefault(require("os"));
15
+ const child_process_1 = require("child_process");
16
+ const inquirer_1 = __importDefault(require("inquirer"));
17
+ const utils_1 = require("./utils");
18
+ const telegramClient_1 = require("./telegramClient");
19
+ const LABEL = `com.${os_1.default.userInfo().username}.telegram`;
20
+ const PLIST_NAME = `${LABEL}.plist`;
21
+ const WINDOWS_SERVICE_NAME = 'OmnikeyTelegram';
22
+ // At runtime __dirname is cli/dist/. The bundled telegram app is copied into
23
+ // cli/telegram-client-dist/ by the build:telegram-client script, so one level
24
+ // up from dist/ lands at the package root, then into the bundle directory.
25
+ // This matches resolveBundleRoot() in telegramClient.ts and works correctly
26
+ // both in the monorepo and after `npm install -g omnikey-cli`.
27
+ const TELEGRAM_BOT_ROOT = path_1.default.resolve(__dirname, '..', 'telegram-client-dist');
28
+ const ENTRY_POINT = path_1.default.join(TELEGRAM_BOT_ROOT, 'dist', 'index.js');
29
+ const HOME = (0, utils_1.getHomeDir)();
30
+ // macOS — launchd LaunchAgent paths
31
+ const LAUNCH_AGENTS_DIR = path_1.default.join(HOME, 'Library', 'LaunchAgents');
32
+ const PLIST_PATH = path_1.default.join(LAUNCH_AGENTS_DIR, PLIST_NAME);
33
+ const MAC_LOG_DIR = path_1.default.join(HOME, 'Library', 'Logs', 'telegram');
34
+ const MAC_STDOUT_LOG = path_1.default.join(MAC_LOG_DIR, 'out.log');
35
+ const MAC_STDERR_LOG = path_1.default.join(MAC_LOG_DIR, 'err.log');
36
+ // Windows — store logs alongside the rest of the CLI config
37
+ const WIN_CONFIG_DIR = path_1.default.join((0, utils_1.getConfigDir)(), 'telegram');
38
+ const WIN_LOG_PATH = path_1.default.join(WIN_CONFIG_DIR, 'daemon.log');
39
+ const WIN_ERROR_LOG_PATH = path_1.default.join(WIN_CONFIG_DIR, 'daemon-error.log');
40
+ const FORWARD_ENV_KEYS = ['TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID', 'PORT', 'LOG_LEVEL'];
41
+ // ─── Shared helpers ───────────────────────────────────────────────────────────
42
+ function ensureBuilt() {
43
+ if (fs_1.default.existsSync(ENTRY_POINT))
44
+ return;
45
+ console.error(`Bundled telegram entry point not found at ${ENTRY_POINT}.\n` +
46
+ 'Reinstall omnikey-cli to restore the bundle: npm install -g omnikey-cli');
47
+ process.exit(1);
48
+ }
49
+ function escapeXml(s) {
50
+ return s
51
+ .replace(/&/g, '&')
52
+ .replace(/</g, '&lt;')
53
+ .replace(/>/g, '&gt;')
54
+ .replace(/"/g, '&quot;');
55
+ }
56
+ // ─── macOS (launchd) ─────────────────────────────────────────────────────────
57
+ function buildPlist() {
58
+ const envEntries = [
59
+ `<key>PATH</key><string>${escapeXml(process.env.PATH ?? '/usr/local/bin:/usr/bin:/bin')}</string>`,
60
+ `<key>HOME</key><string>${escapeXml(HOME)}</string>`,
61
+ ];
62
+ for (const key of FORWARD_ENV_KEYS) {
63
+ const value = process.env[key];
64
+ if (value)
65
+ envEntries.push(`<key>${key}</key><string>${escapeXml(value)}</string>`);
66
+ }
67
+ return `<?xml version="1.0" encoding="UTF-8"?>
68
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
69
+ <plist version="1.0">
70
+ <dict>
71
+ <key>Label</key>
72
+ <string>${escapeXml(LABEL)}</string>
73
+ <key>ProgramArguments</key>
74
+ <array>
75
+ <string>${escapeXml(process.execPath)}</string>
76
+ <string>${escapeXml(ENTRY_POINT)}</string>
77
+ </array>
78
+ <key>WorkingDirectory</key>
79
+ <string>${escapeXml(TELEGRAM_BOT_ROOT)}</string>
80
+ <key>EnvironmentVariables</key>
81
+ <dict>
82
+ ${envEntries.join('\n ')}
83
+ </dict>
84
+ <key>RunAtLoad</key>
85
+ <true/>
86
+ <key>KeepAlive</key>
87
+ <true/>
88
+ <key>ThrottleInterval</key>
89
+ <integer>10</integer>
90
+ <key>StandardOutPath</key>
91
+ <string>${escapeXml(MAC_STDOUT_LOG)}</string>
92
+ <key>StandardErrorPath</key>
93
+ <string>${escapeXml(MAC_STDERR_LOG)}</string>
94
+ <key>ProcessType</key>
95
+ <string>Background</string>
96
+ </dict>
97
+ </plist>
98
+ `;
99
+ }
100
+ function unloadIfLoaded() {
101
+ if (!fs_1.default.existsSync(PLIST_PATH))
102
+ return;
103
+ try {
104
+ (0, child_process_1.execSync)(`launchctl unload "${PLIST_PATH}"`, { stdio: 'pipe' });
105
+ }
106
+ catch {
107
+ /* not loaded — fine */
108
+ }
109
+ }
110
+ function startMacOS() {
111
+ fs_1.default.mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true });
112
+ fs_1.default.mkdirSync(MAC_LOG_DIR, { recursive: true });
113
+ for (const f of [MAC_STDOUT_LOG, MAC_STDERR_LOG]) {
114
+ if (!fs_1.default.existsSync(f))
115
+ fs_1.default.writeFileSync(f, '');
116
+ }
117
+ fs_1.default.writeFileSync(PLIST_PATH, buildPlist(), 'utf-8');
118
+ console.log(`Wrote LaunchAgent: ${PLIST_PATH}`);
119
+ unloadIfLoaded();
120
+ try {
121
+ (0, child_process_1.execSync)(`launchctl load "${PLIST_PATH}"`, { stdio: 'inherit' });
122
+ }
123
+ catch (e) {
124
+ console.error('launchctl load failed:', e.message);
125
+ process.exit(1);
126
+ }
127
+ console.log(`Loaded ${LABEL}. The service will run at login and on reboot.`);
128
+ console.log(`stdout: ${MAC_STDOUT_LOG}`);
129
+ console.log(`stderr: ${MAC_STDERR_LOG}`);
130
+ }
131
+ function stopMacOS() {
132
+ if (!fs_1.default.existsSync(PLIST_PATH)) {
133
+ console.log(`No LaunchAgent at ${PLIST_PATH}. Nothing to stop.`);
134
+ return;
135
+ }
136
+ unloadIfLoaded();
137
+ console.log(`Unloaded ${LABEL}.`);
138
+ }
139
+ function statusMacOS() {
140
+ if (!fs_1.default.existsSync(PLIST_PATH)) {
141
+ console.log(`Not installed (${PLIST_PATH} missing).`);
142
+ return;
143
+ }
144
+ console.log(`Plist: ${PLIST_PATH}`);
145
+ try {
146
+ const out = (0, child_process_1.execSync)(`launchctl list | grep ${LABEL} || true`).toString();
147
+ if (out.trim()) {
148
+ console.log('launchctl list:');
149
+ console.log(out.trim());
150
+ }
151
+ else {
152
+ console.log('Not currently loaded by launchd.');
153
+ }
154
+ }
155
+ catch (e) {
156
+ console.warn('launchctl list failed:', e.message);
157
+ }
158
+ const port = process.env.PORT || '6666';
159
+ try {
160
+ const lsof = (0, child_process_1.execSync)(`lsof -i :${port} -sTCP:LISTEN -t || true`).toString().trim();
161
+ console.log(lsof
162
+ ? `Listening on port ${port} (pid ${lsof.split('\n')[0]}).`
163
+ : `Nothing listening on port ${port}.`);
164
+ }
165
+ catch {
166
+ /* ignore */
167
+ }
168
+ }
169
+ function logsMacOS() {
170
+ if (!fs_1.default.existsSync(MAC_STDOUT_LOG) && !fs_1.default.existsSync(MAC_STDERR_LOG)) {
171
+ console.log('No log files yet. Start the daemon first.');
172
+ return;
173
+ }
174
+ console.log(`Tailing ${MAC_STDOUT_LOG} and ${MAC_STDERR_LOG}. Ctrl-C to stop.`);
175
+ const child = (0, child_process_1.spawnSync)('tail', ['-n', '100', '-F', MAC_STDOUT_LOG, MAC_STDERR_LOG], {
176
+ stdio: 'inherit',
177
+ });
178
+ process.exit(child.status ?? 0);
179
+ }
180
+ function uninstallMacOS() {
181
+ unloadIfLoaded();
182
+ if (fs_1.default.existsSync(PLIST_PATH)) {
183
+ fs_1.default.rmSync(PLIST_PATH);
184
+ console.log(`Removed ${PLIST_PATH}.`);
185
+ }
186
+ else {
187
+ console.log(`No plist at ${PLIST_PATH}.`);
188
+ }
189
+ }
190
+ // ─── Windows (NSSM) ──────────────────────────────────────────────────────────
191
+ function resolveNssm() {
192
+ try {
193
+ return (0, child_process_1.execSync)('where nssm', { stdio: 'pipe' }).toString().trim().split('\n')[0].trim();
194
+ }
195
+ catch {
196
+ return null;
197
+ }
198
+ }
199
+ async function startWindows() {
200
+ let nssmPath = resolveNssm();
201
+ if (!nssmPath) {
202
+ const { install } = await inquirer_1.default.prompt([
203
+ {
204
+ type: 'confirm',
205
+ name: 'install',
206
+ message: 'NSSM is required but not found. Install it now via winget?',
207
+ default: true,
208
+ },
209
+ ]);
210
+ if (!install) {
211
+ console.log('Aborted. Install NSSM manually and re-run in an elevated (Administrator) terminal.');
212
+ return;
213
+ }
214
+ console.log('Installing NSSM via winget...');
215
+ try {
216
+ (0, child_process_1.execSync)('winget install nssm --accept-package-agreements --accept-source-agreements', {
217
+ stdio: 'inherit',
218
+ });
219
+ }
220
+ catch (e) {
221
+ console.error('winget install failed:', e?.message ?? e);
222
+ console.log('Try manually: scoop install nssm or choco install nssm');
223
+ return;
224
+ }
225
+ // winget updates the registry PATH; spawn a new cmd session to pick it up.
226
+ try {
227
+ nssmPath = (0, child_process_1.execSync)('cmd /c where nssm', { stdio: 'pipe' })
228
+ .toString()
229
+ .trim()
230
+ .split('\n')[0]
231
+ .trim();
232
+ }
233
+ catch {
234
+ nssmPath = null;
235
+ }
236
+ if (!nssmPath) {
237
+ console.log('NSSM installed successfully.');
238
+ console.log('Please open a new elevated (Administrator) terminal and re-run this command.');
239
+ return;
240
+ }
241
+ }
242
+ fs_1.default.mkdirSync(WIN_CONFIG_DIR, { recursive: true });
243
+ (0, utils_1.initLogFiles)(WIN_LOG_PATH, WIN_ERROR_LOG_PATH);
244
+ // Remove any pre-existing service so a fresh install is idempotent.
245
+ try {
246
+ (0, child_process_1.execFileSync)(nssmPath, ['stop', WINDOWS_SERVICE_NAME], { stdio: 'pipe' });
247
+ }
248
+ catch {
249
+ /* not running */
250
+ }
251
+ try {
252
+ (0, child_process_1.execFileSync)(nssmPath, ['remove', WINDOWS_SERVICE_NAME, 'confirm'], { stdio: 'pipe' });
253
+ }
254
+ catch {
255
+ /* didn't exist */
256
+ }
257
+ // NSSM services run as LocalSystem; forward the user home so the bot's
258
+ // dotenv / config resolution works correctly.
259
+ const env = { USERPROFILE: HOME, HOME };
260
+ for (const key of FORWARD_ENV_KEYS) {
261
+ const value = process.env[key];
262
+ if (value)
263
+ env[key] = value;
264
+ }
265
+ try {
266
+ (0, child_process_1.execFileSync)(nssmPath, ['install', WINDOWS_SERVICE_NAME, process.execPath, ENTRY_POINT], {
267
+ stdio: 'pipe',
268
+ });
269
+ (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'AppDirectory', TELEGRAM_BOT_ROOT], {
270
+ stdio: 'pipe',
271
+ });
272
+ const envEntries = Object.entries(env).map(([k, v]) => `${k}=${v}`);
273
+ (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'AppEnvironmentExtra', ...envEntries], {
274
+ stdio: 'pipe',
275
+ });
276
+ (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'AppStdout', WIN_LOG_PATH], {
277
+ stdio: 'pipe',
278
+ });
279
+ (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'AppStderr', WIN_ERROR_LOG_PATH], {
280
+ stdio: 'pipe',
281
+ });
282
+ (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'AppRotateFiles', '1'], { stdio: 'pipe' });
283
+ (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'AppExit', 'Default', 'Restart'], {
284
+ stdio: 'pipe',
285
+ });
286
+ (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'AppRestartDelay', '3000'], {
287
+ stdio: 'pipe',
288
+ });
289
+ (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'Start', 'SERVICE_AUTO_START'], {
290
+ stdio: 'pipe',
291
+ });
292
+ (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'DisplayName', 'Omnikey Telegram'], { stdio: 'pipe' });
293
+ (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'Description', 'Omnikey Telegram Daemon'], { stdio: 'pipe' });
294
+ (0, child_process_1.execFileSync)(nssmPath, ['start', WINDOWS_SERVICE_NAME], { stdio: 'pipe' });
295
+ console.log(`NSSM service installed and started: ${WINDOWS_SERVICE_NAME}`);
296
+ console.log('Telegram bot daemon runs on boot, auto-restarts on crash.');
297
+ console.log(`Logs: ${WIN_LOG_PATH}`);
298
+ console.log(` ${WIN_ERROR_LOG_PATH}`);
299
+ }
300
+ catch (e) {
301
+ const msg = e?.stderr?.toString() || e?.message || String(e);
302
+ if (msg.toLowerCase().includes('access') || msg.toLowerCase().includes('privilege')) {
303
+ console.error('Failed to install NSSM service: administrator privileges are required.');
304
+ console.error('Re-run this command in an elevated (Administrator) terminal.');
305
+ }
306
+ else {
307
+ console.error('Failed to install NSSM service:', msg);
308
+ }
309
+ }
310
+ }
311
+ function stopWindows() {
312
+ const nssmPath = resolveNssm();
313
+ if (!nssmPath) {
314
+ console.log('NSSM not found. Cannot stop service.');
315
+ return;
316
+ }
317
+ try {
318
+ (0, child_process_1.execFileSync)(nssmPath, ['stop', WINDOWS_SERVICE_NAME], { stdio: 'inherit' });
319
+ console.log(`Service ${WINDOWS_SERVICE_NAME} stopped.`);
320
+ }
321
+ catch {
322
+ console.log(`Service ${WINDOWS_SERVICE_NAME} was not running.`);
323
+ }
324
+ }
325
+ function statusWindows() {
326
+ const nssmPath = resolveNssm();
327
+ if (!nssmPath) {
328
+ console.log('NSSM not found. Service status unknown.');
329
+ return;
330
+ }
331
+ try {
332
+ const out = (0, child_process_1.execSync)(`"${nssmPath}" status ${WINDOWS_SERVICE_NAME}`, { stdio: 'pipe' })
333
+ .toString()
334
+ .trim();
335
+ console.log(`Service ${WINDOWS_SERVICE_NAME}: ${out}`);
336
+ }
337
+ catch {
338
+ console.log(`Service ${WINDOWS_SERVICE_NAME} is not installed.`);
339
+ }
340
+ }
341
+ function logsWindows() {
342
+ for (const [label, file] of [
343
+ ['stdout', WIN_LOG_PATH],
344
+ ['stderr', WIN_ERROR_LOG_PATH],
345
+ ]) {
346
+ if (fs_1.default.existsSync(file)) {
347
+ console.log(`\n── ${label} (${file}) ──`);
348
+ const lines = fs_1.default.readFileSync(file, 'utf-8').split('\n').slice(-100).join('\n');
349
+ console.log(lines || '(empty)');
350
+ }
351
+ }
352
+ if (!fs_1.default.existsSync(WIN_LOG_PATH) && !fs_1.default.existsSync(WIN_ERROR_LOG_PATH)) {
353
+ console.log('No log files yet. Start the daemon first.');
354
+ }
355
+ }
356
+ function uninstallWindows() {
357
+ const nssmPath = resolveNssm();
358
+ if (!nssmPath) {
359
+ console.log('NSSM not found. Nothing to uninstall.');
360
+ return;
361
+ }
362
+ try {
363
+ (0, child_process_1.execFileSync)(nssmPath, ['stop', WINDOWS_SERVICE_NAME], { stdio: 'pipe' });
364
+ }
365
+ catch {
366
+ /* ok */
367
+ }
368
+ try {
369
+ (0, child_process_1.execFileSync)(nssmPath, ['remove', WINDOWS_SERVICE_NAME, 'confirm'], { stdio: 'pipe' });
370
+ console.log(`Service ${WINDOWS_SERVICE_NAME} removed.`);
371
+ }
372
+ catch {
373
+ console.log(`Service ${WINDOWS_SERVICE_NAME} was not installed.`);
374
+ }
375
+ }
376
+ // ─── Public API (consumed by cli/src/index.ts) ───────────────────────────────
377
+ async function startTelegramDaemon() {
378
+ // Prompt for TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID if not already saved,
379
+ // and persist them before handing off to the OS service manager so the
380
+ // daemon process inherits the correct credentials.
381
+ const cfg = await (0, telegramClient_1.ensureTelegramConfig)();
382
+ // Inject resolved credentials into process.env so buildPlist() / Windows
383
+ // env forwarding picks them up (they come from config.json, not the shell).
384
+ for (const [key, value] of Object.entries(cfg)) {
385
+ process.env[key] = value;
386
+ }
387
+ // Ensure PORT has a value so the plist always includes it.
388
+ process.env.PORT = process.env.PORT ?? '6666';
389
+ ensureBuilt();
390
+ if (utils_1.isWindows) {
391
+ await startWindows();
392
+ }
393
+ else {
394
+ startMacOS();
395
+ }
396
+ }
397
+ function stopTelegramDaemon() {
398
+ if (utils_1.isWindows) {
399
+ stopWindows();
400
+ }
401
+ else {
402
+ stopMacOS();
403
+ }
404
+ }
405
+ async function restartTelegramDaemon() {
406
+ stopTelegramDaemon();
407
+ await startTelegramDaemon();
408
+ }
409
+ function statusTelegramDaemon() {
410
+ if (utils_1.isWindows) {
411
+ statusWindows();
412
+ }
413
+ else {
414
+ statusMacOS();
415
+ }
416
+ }
417
+ function logsTelegramDaemon() {
418
+ if (utils_1.isWindows) {
419
+ logsWindows();
420
+ }
421
+ else {
422
+ logsMacOS();
423
+ }
424
+ }
425
+ function uninstallTelegramDaemon() {
426
+ if (utils_1.isWindows) {
427
+ uninstallWindows();
428
+ }
429
+ else {
430
+ uninstallMacOS();
431
+ }
432
+ }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "registry": "https://registry.npmjs.org/"
6
6
  },
7
- "version": "1.5.3",
7
+ "version": "1.5.4",
8
8
  "description": "CLI for onboarding users to Omnikey AI and configuring OPENAI_API_KEY. Use Yarn for install/build.",
9
9
  "engines": {
10
10
  "node": ">=14.0.0",
@@ -14,9 +14,10 @@
14
14
  "omnikey": "dist/index.js"
15
15
  },
16
16
  "scripts": {
17
- "build": "tsc",
17
+ "build": "tsc && npm run copy-backend && npm run build:telegram-client",
18
18
  "start": "node dist/index.js",
19
- "copy-backend": "rm -rf backend-dist && mkdir -p backend-dist && cp -R ../dist/* backend-dist/"
19
+ "copy-backend": "rm -rf backend-dist && mkdir -p backend-dist && cp -R ../dist/* backend-dist/",
20
+ "build:telegram-client": "rm -rf telegram-client-dist && mkdir -p telegram-client-dist && cp -R ../telegram/dist/* telegram-client-dist/"
20
21
  },
21
22
  "keywords": [
22
23
  "cli",
@@ -32,13 +33,16 @@
32
33
  "@google/genai": "^1.46.0",
33
34
  "@modelcontextprotocol/sdk": "^1.29.0",
34
35
  "axios": "^1.13.5",
36
+ "better-sqlite3": "^12.10.0",
35
37
  "commander": "^11.0.0",
36
38
  "cors": "^2.8.5",
39
+ "cron-parser": "^4.9.0",
37
40
  "cuid": "^3.0.0",
38
41
  "dotenv": "^17.2.3",
39
42
  "express": "^4.21.2",
40
43
  "inquirer": "^9.0.0",
41
44
  "jsonwebtoken": "^9.0.3",
45
+ "node-telegram-bot-api": "^0.61.0",
42
46
  "openai": "^6.37.0",
43
47
  "pg": "^8.18.0",
44
48
  "pg-hstore": "^2.3.4",
@@ -47,11 +51,12 @@
47
51
  "sqlite3": "^5.1.6",
48
52
  "winston": "^3.19.0",
49
53
  "ws": "^8.18.0",
50
- "zod": "^4.3.6",
51
- "cron-parser": "^4.9.0"
54
+ "zod": "^4.3.6"
52
55
  },
53
56
  "devDependencies": {
57
+ "@types/better-sqlite3": "^7.6.13",
54
58
  "@types/inquirer": "^9.0.9",
59
+ "@types/node-telegram-bot-api": "^0.64.0",
55
60
  "typescript": "^5.0.0"
56
61
  },
57
62
  "repository": {
@@ -62,6 +67,7 @@
62
67
  "dist",
63
68
  "backend-dist",
64
69
  "src",
65
- "README.md"
70
+ "README.md",
71
+ "telegram-client-dist"
66
72
  ]
67
73
  }
package/src/index.ts CHANGED
@@ -12,13 +12,21 @@ import { setConfig } from './setConfig';
12
12
  import { grantBrowserAccess, reopenBrowserDebugProfile } from './grantBrowserAccess';
13
13
  import { scheduleAdd, scheduleList, scheduleRemove, scheduleRunNow } from './scheduleJob';
14
14
  import { mcpAdd, mcpList, mcpRemove, mcpToggle, mcpUpdate } from './mcpServer';
15
+ import {
16
+ startTelegramDaemon,
17
+ stopTelegramDaemon,
18
+ restartTelegramDaemon,
19
+ statusTelegramDaemon,
20
+ logsTelegramDaemon,
21
+ uninstallTelegramDaemon,
22
+ } from './telegramDaemon';
15
23
 
16
24
  const program = new Command();
17
25
 
18
26
  program
19
27
  .name('omnikey')
20
28
  .description('Omnikey CLI for onboarding and configuration')
21
- .version('1.0.0');
29
+ .version('1.5.4');
22
30
 
23
31
  program
24
32
  .command('onboard')
@@ -31,9 +39,13 @@ program
31
39
  .command('daemon')
32
40
  .description('Start the Omnikey API backend as a daemon on a specified port')
33
41
  .option('--port <port>', 'Port to run the backend on', '7071')
42
+ .option('--telegram', 'Also install and start the Telegram bot daemon')
34
43
  .action(async (options) => {
35
44
  const port = Number(options.port) || 7071;
36
45
  await startDaemon(port);
46
+ if (options.telegram) {
47
+ await startTelegramDaemon();
48
+ }
37
49
  });
38
50
 
39
51
  program
@@ -87,10 +99,14 @@ program
87
99
  .command('restart-daemon')
88
100
  .description('Restart the Omnikey API backend daemon')
89
101
  .option('--port <port>', 'Port to run the backend on', '7071')
102
+ .option('--telegram', 'Also restart the Telegram bot daemon')
90
103
  .action(async (options) => {
91
104
  killDaemon();
92
105
  const port = Number(options.port) || 7071;
93
106
  await startDaemon(port);
107
+ if (options.telegram) {
108
+ await restartTelegramDaemon();
109
+ }
94
110
  });
95
111
 
96
112
  program
@@ -180,4 +196,53 @@ mcpCmd
180
196
  await mcpUpdate(id);
181
197
  });
182
198
 
199
+ const telegramDaemonCmd = program
200
+ .command('telegram')
201
+ .description(
202
+ 'Manage the Telegram bot daemon (launchd on macOS, NSSM on Windows). ' +
203
+ 'Run `omnikey telegram start` to install and start.',
204
+ );
205
+
206
+ telegramDaemonCmd
207
+ .command('start')
208
+ .description('Install and start the Telegram bot daemon (survives reboots, auto-restarts)')
209
+ .action(async () => {
210
+ await startTelegramDaemon();
211
+ });
212
+
213
+ telegramDaemonCmd
214
+ .command('stop')
215
+ .description('Stop the Telegram bot daemon')
216
+ .action(() => {
217
+ stopTelegramDaemon();
218
+ });
219
+
220
+ telegramDaemonCmd
221
+ .command('restart')
222
+ .description('Restart the Telegram bot daemon')
223
+ .action(async () => {
224
+ await restartTelegramDaemon();
225
+ });
226
+
227
+ telegramDaemonCmd
228
+ .command('status')
229
+ .description('Show the current status of the Telegram bot daemon')
230
+ .action(() => {
231
+ statusTelegramDaemon();
232
+ });
233
+
234
+ telegramDaemonCmd
235
+ .command('logs')
236
+ .description('Tail the Telegram bot daemon logs')
237
+ .action(() => {
238
+ logsTelegramDaemon();
239
+ });
240
+
241
+ telegramDaemonCmd
242
+ .command('uninstall')
243
+ .description('Stop and remove the Telegram bot daemon')
244
+ .action(() => {
245
+ uninstallTelegramDaemon();
246
+ });
247
+
183
248
  program.parseAsync(process.argv);