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.
- package/README.md +64 -0
- package/dist/index.js +50 -1
- package/dist/telegramClient.js +188 -0
- package/dist/telegramDaemon.js +432 -0
- package/package.json +12 -6
- package/src/index.ts +66 -1
- package/src/telegramClient.ts +227 -0
- package/src/telegramDaemon.ts +453 -0
- package/telegram-client-dist/agentClient.js +384 -0
- package/telegram-client-dist/config.js +67 -0
- package/telegram-client-dist/db.js +78 -0
- package/telegram-client-dist/index.js +97 -0
- package/telegram-client-dist/notifyTelegram.js +901 -0
- package/telegram-client-dist/omnikeyAuth.js +53 -0
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ OmnikeyAI is a productivity tool that helps you quickly rewrite selected text us
|
|
|
21
21
|
- `omnikey grant-browser-access`: One-time setup to give Omnikey access to authenticated browser tabs for web fetch.
|
|
22
22
|
- Scheduled Jobs commands to create, list, delete, and trigger jobs from the CLI.
|
|
23
23
|
- `omnikey mcp`: manage MCP (Model Context Protocol) servers available to the agent (stdio, HTTP, SSE transports).
|
|
24
|
+
- `omnikey telegram`: run the Telegram client as a persistent background daemon — receive Omnikey notifications in your Telegram chat and interact with the agent from your phone.
|
|
24
25
|
|
|
25
26
|
## Usage
|
|
26
27
|
|
|
@@ -93,6 +94,24 @@ omnikey mcp toggle <id>
|
|
|
93
94
|
|
|
94
95
|
# Edit an MCP server by ID (interactive, current values as defaults)
|
|
95
96
|
omnikey mcp update <id>
|
|
97
|
+
|
|
98
|
+
# Install and start the Telegram bot daemon (prompts for credentials on first run)
|
|
99
|
+
omnikey telegram start
|
|
100
|
+
|
|
101
|
+
# Stop the Telegram bot daemon
|
|
102
|
+
omnikey telegram stop
|
|
103
|
+
|
|
104
|
+
# Restart the Telegram bot daemon
|
|
105
|
+
omnikey telegram restart
|
|
106
|
+
|
|
107
|
+
# Show daemon status (launchd/NSSM info + port check)
|
|
108
|
+
omnikey telegram status
|
|
109
|
+
|
|
110
|
+
# Tail the Telegram bot daemon logs
|
|
111
|
+
omnikey telegram logs
|
|
112
|
+
|
|
113
|
+
# Stop and fully remove the Telegram bot daemon
|
|
114
|
+
omnikey telegram uninstall
|
|
96
115
|
```
|
|
97
116
|
|
|
98
117
|
### Command reference
|
|
@@ -119,6 +138,12 @@ omnikey mcp update <id>
|
|
|
119
138
|
| `omnikey mcp remove` | Remove an MCP server via interactive selection and confirmation |
|
|
120
139
|
| `omnikey mcp toggle <id>` | Enable or disable an MCP server by ID |
|
|
121
140
|
| `omnikey mcp update <id>` | Edit an MCP server by ID (interactive, current values as defaults) |
|
|
141
|
+
| `omnikey telegram start` | Install and start the Telegram bot daemon (prompts for credentials) |
|
|
142
|
+
| `omnikey telegram stop` | Stop the Telegram bot daemon |
|
|
143
|
+
| `omnikey telegram restart` | Restart the Telegram bot daemon |
|
|
144
|
+
| `omnikey telegram status` | Show daemon status (launchd/NSSM info + port check) |
|
|
145
|
+
| `omnikey telegram logs` | Tail the Telegram bot daemon logs |
|
|
146
|
+
| `omnikey telegram uninstall` | Stop and fully remove the Telegram bot daemon |
|
|
122
147
|
|
|
123
148
|
## Scheduled Jobs
|
|
124
149
|
|
|
@@ -231,6 +256,45 @@ The CLI automatically enables **"Allow JavaScript from Apple Events"** for every
|
|
|
231
256
|
|
|
232
257
|
Both changes are permanent and survive reboots. Restart each browser once after setup for the change to take effect.
|
|
233
258
|
|
|
259
|
+
## Telegram Bot Daemon
|
|
260
|
+
|
|
261
|
+
The `omnikey telegram` command group runs a Telegram bot as a persistent background service. Once started, the bot sends Omnikey notifications to your Telegram chat and lets you interact with the agent directly from your phone.
|
|
262
|
+
|
|
263
|
+
For setup instructions (creating a bot, finding your chat ID, and configuring credentials) see the [Telegram README](../telegram/README.md).
|
|
264
|
+
|
|
265
|
+
### `omnikey telegram start`
|
|
266
|
+
|
|
267
|
+
Installs and starts the daemon. If `TELEGRAM_BOT_TOKEN` or `TELEGRAM_CHAT_ID` are not yet saved, the CLI prompts for them, validates the token against the Telegram API, and saves them to `~/.omnikey/config.json` before installing the service. On macOS the bot runs as a **launchd agent**; on Windows as an **NSSM service**. Both auto-restart on crash and start automatically on login.
|
|
268
|
+
|
|
269
|
+
### `omnikey telegram stop`
|
|
270
|
+
|
|
271
|
+
Unloads the launchd agent (macOS) or stops the NSSM service (Windows). The service definition is kept so `omnikey telegram start` can bring it back without re-entering credentials.
|
|
272
|
+
|
|
273
|
+
### `omnikey telegram restart`
|
|
274
|
+
|
|
275
|
+
Equivalent to `stop` followed by `start`. Useful after updating credentials or rotating the bot token.
|
|
276
|
+
|
|
277
|
+
### `omnikey telegram status`
|
|
278
|
+
|
|
279
|
+
Prints:
|
|
280
|
+
|
|
281
|
+
- Path to the service definition (plist on macOS, service name on Windows)
|
|
282
|
+
- Current launchd / NSSM status including the running PID
|
|
283
|
+
- Whether anything is listening on the bot's HTTP port (default `6666`)
|
|
284
|
+
|
|
285
|
+
### `omnikey telegram logs`
|
|
286
|
+
|
|
287
|
+
Tails the last 100 lines of both stdout and stderr logs and follows new output (Ctrl-C to stop).
|
|
288
|
+
|
|
289
|
+
| Platform | Log files |
|
|
290
|
+
| -------- | --------- |
|
|
291
|
+
| macOS | `~/Library/Logs/telegram/out.log`, `~/Library/Logs/telegram/err.log` |
|
|
292
|
+
| Windows | `~/.omnikey/telegram/daemon.log`, `~/.omnikey/telegram/daemon-error.log` |
|
|
293
|
+
|
|
294
|
+
### `omnikey telegram uninstall`
|
|
295
|
+
|
|
296
|
+
Stops the daemon and removes the service definition entirely. Run `omnikey telegram start` to reinstall from scratch.
|
|
297
|
+
|
|
234
298
|
## Platform notes
|
|
235
299
|
|
|
236
300
|
### macOS
|
package/dist/index.js
CHANGED
|
@@ -13,11 +13,12 @@ const setConfig_1 = require("./setConfig");
|
|
|
13
13
|
const grantBrowserAccess_1 = require("./grantBrowserAccess");
|
|
14
14
|
const scheduleJob_1 = require("./scheduleJob");
|
|
15
15
|
const mcpServer_1 = require("./mcpServer");
|
|
16
|
+
const telegramDaemon_1 = require("./telegramDaemon");
|
|
16
17
|
const program = new commander_1.Command();
|
|
17
18
|
program
|
|
18
19
|
.name('omnikey')
|
|
19
20
|
.description('Omnikey CLI for onboarding and configuration')
|
|
20
|
-
.version('1.
|
|
21
|
+
.version('1.5.4');
|
|
21
22
|
program
|
|
22
23
|
.command('onboard')
|
|
23
24
|
.description('Onboard and configure your AI provider')
|
|
@@ -28,9 +29,13 @@ program
|
|
|
28
29
|
.command('daemon')
|
|
29
30
|
.description('Start the Omnikey API backend as a daemon on a specified port')
|
|
30
31
|
.option('--port <port>', 'Port to run the backend on', '7071')
|
|
32
|
+
.option('--telegram', 'Also install and start the Telegram bot daemon')
|
|
31
33
|
.action(async (options) => {
|
|
32
34
|
const port = Number(options.port) || 7071;
|
|
33
35
|
await (0, daemon_1.startDaemon)(port);
|
|
36
|
+
if (options.telegram) {
|
|
37
|
+
await (0, telegramDaemon_1.startTelegramDaemon)();
|
|
38
|
+
}
|
|
34
39
|
});
|
|
35
40
|
program
|
|
36
41
|
.command('kill-daemon')
|
|
@@ -77,10 +82,14 @@ program
|
|
|
77
82
|
.command('restart-daemon')
|
|
78
83
|
.description('Restart the Omnikey API backend daemon')
|
|
79
84
|
.option('--port <port>', 'Port to run the backend on', '7071')
|
|
85
|
+
.option('--telegram', 'Also restart the Telegram bot daemon')
|
|
80
86
|
.action(async (options) => {
|
|
81
87
|
(0, killDaemon_1.killDaemon)();
|
|
82
88
|
const port = Number(options.port) || 7071;
|
|
83
89
|
await (0, daemon_1.startDaemon)(port);
|
|
90
|
+
if (options.telegram) {
|
|
91
|
+
await (0, telegramDaemon_1.restartTelegramDaemon)();
|
|
92
|
+
}
|
|
84
93
|
});
|
|
85
94
|
program
|
|
86
95
|
.command('grant-browser-access')
|
|
@@ -154,4 +163,44 @@ mcpCmd
|
|
|
154
163
|
.action(async (id) => {
|
|
155
164
|
await (0, mcpServer_1.mcpUpdate)(id);
|
|
156
165
|
});
|
|
166
|
+
const telegramDaemonCmd = program
|
|
167
|
+
.command('telegram')
|
|
168
|
+
.description('Manage the Telegram bot daemon (launchd on macOS, NSSM on Windows). ' +
|
|
169
|
+
'Run `omnikey telegram start` to install and start.');
|
|
170
|
+
telegramDaemonCmd
|
|
171
|
+
.command('start')
|
|
172
|
+
.description('Install and start the Telegram bot daemon (survives reboots, auto-restarts)')
|
|
173
|
+
.action(async () => {
|
|
174
|
+
await (0, telegramDaemon_1.startTelegramDaemon)();
|
|
175
|
+
});
|
|
176
|
+
telegramDaemonCmd
|
|
177
|
+
.command('stop')
|
|
178
|
+
.description('Stop the Telegram bot daemon')
|
|
179
|
+
.action(() => {
|
|
180
|
+
(0, telegramDaemon_1.stopTelegramDaemon)();
|
|
181
|
+
});
|
|
182
|
+
telegramDaemonCmd
|
|
183
|
+
.command('restart')
|
|
184
|
+
.description('Restart the Telegram bot daemon')
|
|
185
|
+
.action(async () => {
|
|
186
|
+
await (0, telegramDaemon_1.restartTelegramDaemon)();
|
|
187
|
+
});
|
|
188
|
+
telegramDaemonCmd
|
|
189
|
+
.command('status')
|
|
190
|
+
.description('Show the current status of the Telegram bot daemon')
|
|
191
|
+
.action(() => {
|
|
192
|
+
(0, telegramDaemon_1.statusTelegramDaemon)();
|
|
193
|
+
});
|
|
194
|
+
telegramDaemonCmd
|
|
195
|
+
.command('logs')
|
|
196
|
+
.description('Tail the Telegram bot daemon logs')
|
|
197
|
+
.action(() => {
|
|
198
|
+
(0, telegramDaemon_1.logsTelegramDaemon)();
|
|
199
|
+
});
|
|
200
|
+
telegramDaemonCmd
|
|
201
|
+
.command('uninstall')
|
|
202
|
+
.description('Stop and remove the Telegram bot daemon')
|
|
203
|
+
.action(() => {
|
|
204
|
+
(0, telegramDaemon_1.uninstallTelegramDaemon)();
|
|
205
|
+
});
|
|
157
206
|
program.parseAsync(process.argv);
|
|
@@ -0,0 +1,188 @@
|
|
|
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.ensureTelegramConfig = ensureTelegramConfig;
|
|
7
|
+
exports.spawnTelegramClient = spawnTelegramClient;
|
|
8
|
+
exports.startTelegramClientCommand = startTelegramClientCommand;
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const https_1 = require("https");
|
|
13
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
14
|
+
const utils_1 = require("./utils");
|
|
15
|
+
const REQUIRED_ENV_KEYS = ['TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID'];
|
|
16
|
+
/**
|
|
17
|
+
* Where the bundled bot lives at runtime. The CLI's `build:telegram-client`
|
|
18
|
+
* script populates this directory with the bot's compiled output plus its
|
|
19
|
+
* production `node_modules`.
|
|
20
|
+
*/
|
|
21
|
+
function resolveBundleRoot() {
|
|
22
|
+
// dist/telegramClient.js → ../telegram-client-dist
|
|
23
|
+
return path_1.default.resolve(__dirname, '..', 'telegram-client-dist');
|
|
24
|
+
}
|
|
25
|
+
function resolveBundledEntry() {
|
|
26
|
+
return path_1.default.join(resolveBundleRoot(), 'dist', 'index.js');
|
|
27
|
+
}
|
|
28
|
+
function persistConfig(values) {
|
|
29
|
+
const configDir = (0, utils_1.getConfigDir)();
|
|
30
|
+
const configPath = (0, utils_1.getConfigPath)();
|
|
31
|
+
const existing = (0, utils_1.readConfig)();
|
|
32
|
+
const merged = { ...existing, ...values };
|
|
33
|
+
fs_1.default.mkdirSync(configDir, { recursive: true });
|
|
34
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(merged, null, 2), 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Verify a Telegram bot token via the Bot API's getMe endpoint.
|
|
38
|
+
* Resolves to the bot's @username on success, throws on failure.
|
|
39
|
+
*/
|
|
40
|
+
function verifyToken(token) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const req = (0, https_1.request)({
|
|
43
|
+
method: 'GET',
|
|
44
|
+
hostname: 'api.telegram.org',
|
|
45
|
+
path: `/bot${token}/getMe`,
|
|
46
|
+
}, (res) => {
|
|
47
|
+
let body = '';
|
|
48
|
+
res.on('data', (chunk) => (body += chunk));
|
|
49
|
+
res.on('end', () => {
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(body);
|
|
52
|
+
if (!parsed.ok) {
|
|
53
|
+
reject(new Error(parsed.description || 'Telegram rejected the token'));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
resolve(parsed.result?.username || 'bot');
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
reject(new Error(`Invalid JSON from Telegram: ${body}`));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
req.on('error', reject);
|
|
64
|
+
req.end();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Ensure TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID are present in
|
|
69
|
+
* ~/.omnikey/config.json. Prompts for any missing values (unless
|
|
70
|
+
* `nonInteractive` is set) and validates the token against the Bot API.
|
|
71
|
+
*
|
|
72
|
+
* Returns the resolved config values (existing or newly captured).
|
|
73
|
+
*/
|
|
74
|
+
async function ensureTelegramConfig(options = {}) {
|
|
75
|
+
const existing = (0, utils_1.readConfig)();
|
|
76
|
+
const resolved = {};
|
|
77
|
+
const missing = [];
|
|
78
|
+
for (const key of REQUIRED_ENV_KEYS) {
|
|
79
|
+
const value = existing[key];
|
|
80
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
81
|
+
resolved[key] = value.trim();
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
missing.push(key);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (missing.length === 0) {
|
|
88
|
+
return resolved;
|
|
89
|
+
}
|
|
90
|
+
if (options.nonInteractive) {
|
|
91
|
+
throw new Error(`Missing required Telegram config in ${(0, utils_1.getConfigPath)()}: ${missing.join(', ')}`);
|
|
92
|
+
}
|
|
93
|
+
console.log('\nTelegram client configuration required.');
|
|
94
|
+
console.log('See telegram/README.md for how to create a bot with @BotFather and find your chat id.\n');
|
|
95
|
+
const toPersist = {};
|
|
96
|
+
if (missing.includes('TELEGRAM_BOT_TOKEN')) {
|
|
97
|
+
const { token } = await inquirer_1.default.prompt([
|
|
98
|
+
{
|
|
99
|
+
type: 'password',
|
|
100
|
+
name: 'token',
|
|
101
|
+
mask: '*',
|
|
102
|
+
message: 'Enter your Telegram bot token (from @BotFather):',
|
|
103
|
+
validate: (input) => /^\d+:[A-Za-z0-9_-]{20,}$/.test(input.trim()) ||
|
|
104
|
+
'Token should look like 123456789:ABC... (digits, colon, 20+ chars)',
|
|
105
|
+
},
|
|
106
|
+
]);
|
|
107
|
+
try {
|
|
108
|
+
const username = await verifyToken(token.trim());
|
|
109
|
+
console.log(`Token validated for @${username}.`);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
throw new Error(`Telegram rejected the token: ${err instanceof Error ? err.message : String(err)}`);
|
|
113
|
+
}
|
|
114
|
+
resolved.TELEGRAM_BOT_TOKEN = token.trim();
|
|
115
|
+
toPersist.TELEGRAM_BOT_TOKEN = token.trim();
|
|
116
|
+
}
|
|
117
|
+
if (missing.includes('TELEGRAM_CHAT_ID')) {
|
|
118
|
+
const { chatId } = await inquirer_1.default.prompt([
|
|
119
|
+
{
|
|
120
|
+
type: 'input',
|
|
121
|
+
name: 'chatId',
|
|
122
|
+
message: 'Enter the chat id to receive notifications (curl https://api.telegram.org/bot<token>/getUpdates):',
|
|
123
|
+
validate: (input) => /^-?\d+$/.test(input.trim()) || 'Chat id must be a numeric value (groups are negative)',
|
|
124
|
+
},
|
|
125
|
+
]);
|
|
126
|
+
resolved.TELEGRAM_CHAT_ID = chatId.trim();
|
|
127
|
+
toPersist.TELEGRAM_CHAT_ID = chatId.trim();
|
|
128
|
+
}
|
|
129
|
+
if (Object.keys(toPersist).length > 0) {
|
|
130
|
+
persistConfig(toPersist);
|
|
131
|
+
console.log(`Saved Telegram config to ${(0, utils_1.getConfigPath)()}.`);
|
|
132
|
+
}
|
|
133
|
+
return resolved;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Spawn the bundled telegram-bot server as a long-lived child process.
|
|
137
|
+
* The bot reads PORT from process.env (defaults to 7072 in the app), so we
|
|
138
|
+
* inject the CLI's chosen port that way to keep the upstream code untouched.
|
|
139
|
+
*/
|
|
140
|
+
function spawnTelegramClient(port, env) {
|
|
141
|
+
const bundleRoot = resolveBundleRoot();
|
|
142
|
+
const entry = resolveBundledEntry();
|
|
143
|
+
if (!fs_1.default.existsSync(entry)) {
|
|
144
|
+
throw new Error(`Bundled telegram-client not found at ${entry}. ` +
|
|
145
|
+
'Reinstall omnikey-cli or run `npm run build` from the cli/ directory.');
|
|
146
|
+
}
|
|
147
|
+
const configDir = (0, utils_1.getConfigDir)();
|
|
148
|
+
fs_1.default.mkdirSync(configDir, { recursive: true });
|
|
149
|
+
const logPath = path_1.default.join(configDir, 'telegram-client.log');
|
|
150
|
+
const errorLogPath = path_1.default.join(configDir, 'telegram-client-error.log');
|
|
151
|
+
const { out, err } = (0, utils_1.initLogFiles)(logPath, errorLogPath);
|
|
152
|
+
const child = (0, child_process_1.spawn)(process.execPath, [entry], {
|
|
153
|
+
detached: false,
|
|
154
|
+
stdio: ['ignore', out, err],
|
|
155
|
+
// cwd = bundle root so the bot's dotenv.config() and relative paths line up
|
|
156
|
+
cwd: bundleRoot,
|
|
157
|
+
env: {
|
|
158
|
+
...process.env,
|
|
159
|
+
...env,
|
|
160
|
+
PORT: String(port),
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
child.on('exit', (code, signal) => {
|
|
164
|
+
fs_1.default.closeSync(out);
|
|
165
|
+
fs_1.default.closeSync(err);
|
|
166
|
+
if (code !== 0) {
|
|
167
|
+
console.error(`telegram-client exited with code=${code} signal=${signal ?? 'none'}`);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
return child;
|
|
171
|
+
}
|
|
172
|
+
/** Top-level command: `omnikey telegram-client [--port <port>]`. */
|
|
173
|
+
async function startTelegramClientCommand(port) {
|
|
174
|
+
const cfg = await ensureTelegramConfig();
|
|
175
|
+
const child = spawnTelegramClient(port, cfg);
|
|
176
|
+
console.log(`telegram-client started (pid=${child.pid}) on port ${port}. ` +
|
|
177
|
+
`Logs: ${path_1.default.join((0, utils_1.getConfigDir)(), 'telegram-client.log')}`);
|
|
178
|
+
// Keep the CLI process alive until the child exits so users can Ctrl+C.
|
|
179
|
+
await new Promise((resolve) => {
|
|
180
|
+
child.on('exit', () => resolve());
|
|
181
|
+
process.on('SIGINT', () => {
|
|
182
|
+
child.kill('SIGINT');
|
|
183
|
+
});
|
|
184
|
+
process.on('SIGTERM', () => {
|
|
185
|
+
child.kill('SIGTERM');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|