omnikey-cli 1.0.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.
@@ -0,0 +1,3 @@
1
+ Work through each checklist item systematically.
2
+ Keep communication concise and focused.
3
+ Follow development best practices.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # Omnikey CLI
2
+
3
+ A command-line tool for onboarding users to the Omnikey open-source app and configuring their OPENAI_API_KEY.
4
+
5
+ ## About OmnikeyAI (macOS)
6
+
7
+ OmnikeyAI is a productivity tool for macOS that helps you quickly rewrite selected text using OpenAI. It consists of a macOS menu bar app and a TypeScript backend daemon. The CLI allows you to configure and run the backend daemon, onboard users, and manage your OpenAI API key with ease. Once set up, you can select any text on your Mac and trigger rewrite commands directly from your desktop.
8
+
9
+ - For more details about the app and its features, see the [main README](https://github.com/GurinderRawala/OmniKey-AI).
10
+ - Download the latest macOS app here: [Download OmniKeyAI for macOS](https://omnikeyai-saas-fmytqc3dra-uc.a.run.app/macos/download)
11
+
12
+ ## Features
13
+
14
+ - `omnikey onboard`: Interactive onboarding to set up your OPENAI_API_KEY.
15
+ - Accepts the `--open-ai-key` parameter for non-interactive setup.
16
+ - Configure and run the backend daemon for the macOS app.
17
+
18
+ ## Usage
19
+
20
+ ```sh
21
+ # Install CLI globally (from this directory)
22
+ npm install -g .
23
+
24
+ # Onboard interactively (will prompt for OpenAI key and self-hosting)
25
+ omnikey onboard
26
+
27
+ # Or onboard non-interactively
28
+ omnikey onboard --open-ai-key YOUR_KEY
29
+
30
+ # Running the daemon will start the backend
31
+ omnikey daemon --port 7071
32
+
33
+ # Kill the daemon
34
+ omnikey kill-daemon --port 7071
35
+ ```
36
+
37
+ ## Development
38
+
39
+ - Built with Node.js and TypeScript.
40
+ - Uses `commander` for CLI parsing and `inquirer` for prompts.
41
+ - Utilizes Yarn as the package manager.
42
+
43
+ ## License
44
+
45
+ MIT
package/dist/daemon.js ADDED
@@ -0,0 +1,48 @@
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.startDaemon = startDaemon;
7
+ const child_process_1 = require("child_process");
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ /**
11
+ * Start the Omnikey API backend as a daemon on the specified port.
12
+ * @param port The port to run the backend on
13
+ */
14
+ function startDaemon(port = 8080) {
15
+ // Only use ~/.omnikey/config.json for environment variables
16
+ // Path to the backend entry point (now from backend-dist)
17
+ const backendPath = path_1.default.resolve(__dirname, '../backend-dist/index.js');
18
+ // Read and update environment variables from ~/.omnikey/config.json
19
+ const configDir = path_1.default.join(process.env.HOME || process.env.USERPROFILE || '.', '.omnikey');
20
+ const configPath = path_1.default.join(configDir, 'config.json');
21
+ let configVars = {};
22
+ if (fs_1.default.existsSync(configPath)) {
23
+ try {
24
+ configVars = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
25
+ }
26
+ catch (e) {
27
+ console.error('Failed to parse config.json:', e);
28
+ }
29
+ }
30
+ // Ensure both OMNIKEY_PORT and PORT are set for compatibility
31
+ configVars.OMNIKEY_PORT = port;
32
+ // Write the updated configVars back to config.json
33
+ try {
34
+ fs_1.default.mkdirSync(configDir, { recursive: true });
35
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configVars, null, 2), 'utf-8');
36
+ }
37
+ catch (e) {
38
+ console.error('Failed to write updated config.json:', e);
39
+ }
40
+ // Spawn the backend as a detached child process with env vars from config
41
+ const child = (0, child_process_1.spawn)('node', [backendPath], {
42
+ env: { ...process.env, ...configVars, OMNIKEY_PORT: String(port) },
43
+ detached: true,
44
+ stdio: 'ignore',
45
+ });
46
+ child.unref();
47
+ console.log(`Omnikey API backend started as a daemon on port ${port}`);
48
+ }
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const onboard_1 = require("./onboard");
6
+ const daemon_1 = require("./daemon");
7
+ const killDaemon_1 = require("./killDaemon");
8
+ const program = new commander_1.Command();
9
+ program
10
+ .name('omnikey')
11
+ .description('Omnikey CLI for onboarding and configuration')
12
+ .version('1.0.0');
13
+ program
14
+ .command('onboard')
15
+ .description('Onboard and configure your OPENAI_API_KEY')
16
+ .option('--open-ai-key <key>', 'Your OpenAI API Key')
17
+ .action(async (options) => {
18
+ await (0, onboard_1.onboard)(options.openAiKey || options.openAiKey || options['open-ai-key']);
19
+ });
20
+ program
21
+ .command('daemon')
22
+ .description('Start the Omnikey API backend as a daemon on a specified port')
23
+ .option('--port <port>', 'Port to run the backend on', '8080')
24
+ .action((options) => {
25
+ const port = Number(options.port) || 8080;
26
+ (0, daemon_1.startDaemon)(port);
27
+ });
28
+ program
29
+ .command('kill-daemon')
30
+ .description('Kill the Omnikey API backend daemon running on a specified port')
31
+ .option('--port <port>', 'Port to look for the daemon on', '8080')
32
+ .action((options) => {
33
+ const port = Number(options.port) || 8080;
34
+ (0, killDaemon_1.killDaemon)(port);
35
+ });
36
+ program.parseAsync(process.argv);
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.killDaemon = killDaemon;
4
+ const child_process_1 = require("child_process");
5
+ /**
6
+ * Kill the Omnikey API backend daemon running on a given port (default 8080).
7
+ * Looks for node processes running backend-dist/index.js on the specified port and kills them.
8
+ * @param port The port to look for (default 8080)
9
+ */
10
+ function killDaemon(port = 8080) {
11
+ try {
12
+ // Find the PID(s) of node processes running backend-dist/index.js on the given port
13
+ // macOS: lsof -i :PORT -t
14
+ const pids = (0, child_process_1.execSync)(`lsof -i :${port} -t`).toString().split('\n').filter(Boolean);
15
+ if (pids.length === 0) {
16
+ console.log(`No daemon found running on port ${port}.`);
17
+ return;
18
+ }
19
+ for (const pid of pids) {
20
+ try {
21
+ process.kill(Number(pid), 'SIGTERM');
22
+ console.log(`Killed daemon process with PID ${pid} on port ${port}.`);
23
+ }
24
+ catch (e) {
25
+ console.error(`Failed to kill process ${pid}:`, e);
26
+ }
27
+ }
28
+ }
29
+ catch (e) {
30
+ console.log(`No daemon found running on port ${port}.`);
31
+ }
32
+ }
@@ -0,0 +1,46 @@
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.onboard = onboard;
7
+ const inquirer_1 = __importDefault(require("inquirer"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ /**
11
+ * Onboard the user by configuring their OPENAI_API_KEY and generating a .env for self-hosted use.
12
+ * @param openAiKey Optional key provided via CLI
13
+ */
14
+ async function onboard(openAiKey) {
15
+ let apiKey = openAiKey;
16
+ let sqlitePath = 'omnikey-selfhosted.sqlite';
17
+ if (!apiKey) {
18
+ const answers = await inquirer_1.default.prompt([
19
+ {
20
+ type: 'input',
21
+ name: 'apiKey',
22
+ message: 'Enter your OPENAI_API_KEY:',
23
+ validate: (input) => input.trim() !== '' || 'API key cannot be empty',
24
+ },
25
+ {
26
+ type: 'input',
27
+ name: 'sqlitePath',
28
+ message: 'SQLite DB file path:',
29
+ default: 'omnikey-selfhosted.sqlite',
30
+ },
31
+ ]);
32
+ apiKey = answers.apiKey;
33
+ sqlitePath = answers.sqlitePath;
34
+ }
35
+ // Save all environment variables to ~/.omnikey/config.json
36
+ const configDir = path_1.default.join(process.env.HOME || process.env.USERPROFILE || '.', '.omnikey');
37
+ const configPath = path_1.default.join(configDir, 'config.json');
38
+ fs_1.default.mkdirSync(configDir, { recursive: true });
39
+ const configVars = {
40
+ OPENAI_API_KEY: apiKey,
41
+ IS_SELF_HOSTED: true,
42
+ SQLITE_PATH: sqlitePath,
43
+ };
44
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configVars, null, 2));
45
+ console.log(`Environment variables saved to ${configPath}`);
46
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "omnikey-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI for onboarding users to Omnikey and configuring OPENAI_API_KEY. Use Yarn for install/build.",
5
+ "engines": {
6
+ "node": ">=14.0.0",
7
+ "yarn": ">=1.22.0"
8
+ },
9
+ "bin": {
10
+ "omnikey": "dist/index.js"
11
+ },
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "start": "node dist/index.js",
15
+ "copy-backend": "rm -rf backend-dist && mkdir -p backend-dist && cp -R ../dist/* backend-dist/"
16
+ },
17
+ "keywords": [
18
+ "cli",
19
+ "omnikey",
20
+ "onboarding",
21
+ "openai"
22
+ ],
23
+ "author": "Gurinder Rawala",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "commander": "^11.0.0",
27
+ "inquirer": "^9.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/inquirer": "^9.0.9",
31
+ "typescript": "^5.0.0"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/GurinderRawala/OmniKey-AI"
36
+ }
37
+ }
package/src/daemon.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { spawn } from 'child_process';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+
5
+ /**
6
+ * Start the Omnikey API backend as a daemon on the specified port.
7
+ * @param port The port to run the backend on
8
+ */
9
+ export function startDaemon(port: number = 7071) {
10
+ // Only use ~/.omnikey/config.json for environment variables
11
+
12
+ // Path to the backend entry point (now from backend-dist)
13
+ const backendPath = path.resolve(__dirname, '../backend-dist/index.js');
14
+
15
+ // Read and update environment variables from ~/.omnikey/config.json
16
+ const configDir = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.omnikey');
17
+ const configPath = path.join(configDir, 'config.json');
18
+ let configVars: Record<string, any> = {};
19
+ if (fs.existsSync(configPath)) {
20
+ try {
21
+ configVars = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
22
+ } catch (e) {
23
+ console.error('Failed to parse config.json:', e);
24
+ }
25
+ }
26
+ // Ensure both OMNIKEY_PORT and PORT are set for compatibility
27
+ configVars.OMNIKEY_PORT = port;
28
+ // Write the updated configVars back to config.json
29
+ try {
30
+ fs.mkdirSync(configDir, { recursive: true });
31
+ fs.writeFileSync(configPath, JSON.stringify(configVars, null, 2), 'utf-8');
32
+ } catch (e) {
33
+ console.error('Failed to write updated config.json:', e);
34
+ }
35
+ // Spawn the backend as a detached child process with env vars from config
36
+ const child = spawn('node', [backendPath], {
37
+ env: { ...process.env, ...configVars, OMNIKEY_PORT: String(port) },
38
+ detached: true,
39
+ stdio: 'ignore',
40
+ });
41
+ child.unref();
42
+ console.log(`Omnikey API backend started as a daemon on port ${port}`);
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { onboard } from './onboard';
4
+
5
+ import { startDaemon } from './daemon';
6
+ import { killDaemon } from './killDaemon';
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name('omnikey')
12
+ .description('Omnikey CLI for onboarding and configuration')
13
+ .version('1.0.0');
14
+
15
+ program
16
+ .command('onboard')
17
+ .description('Onboard and configure your OPENAI_API_KEY')
18
+ .option('--open-ai-key <key>', 'Your OpenAI API Key')
19
+ .action(async (options) => {
20
+ await onboard(options.openAiKey || options.openAiKey || options['open-ai-key']);
21
+ });
22
+
23
+ program
24
+ .command('daemon')
25
+ .description('Start the Omnikey API backend as a daemon on a specified port')
26
+ .option('--port <port>', 'Port to run the backend on', '7071')
27
+ .action((options) => {
28
+ const port = Number(options.port) || 7071;
29
+ startDaemon(port);
30
+ });
31
+
32
+ program
33
+ .command('kill-daemon')
34
+ .description('Kill the Omnikey API backend daemon running on a specified port')
35
+ .option('--port <port>', 'Port to look for the daemon on', '7071')
36
+ .action((options) => {
37
+ const port = Number(options.port) || 7071;
38
+ killDaemon(port);
39
+ });
40
+
41
+ program.parseAsync(process.argv);
@@ -0,0 +1,28 @@
1
+ import { execSync } from 'child_process';
2
+
3
+ /**
4
+ * Kill the Omnikey API backend daemon running on a given port (default 7071).
5
+ * Looks for node processes running backend-dist/index.js on the specified port and kills them.
6
+ * @param port The port to look for (default 7071)
7
+ */
8
+ export function killDaemon(port: number = 7071) {
9
+ try {
10
+ // Find the PID(s) of node processes running backend-dist/index.js on the given port
11
+ // macOS: lsof -i :PORT -t
12
+ const pids = execSync(`lsof -i :${port} -t`).toString().split('\n').filter(Boolean);
13
+ if (pids.length === 0) {
14
+ console.log(`No daemon found running on port ${port}.`);
15
+ return;
16
+ }
17
+ for (const pid of pids) {
18
+ try {
19
+ process.kill(Number(pid), 'SIGTERM');
20
+ console.log(`Killed daemon process with PID ${pid} on port ${port}.`);
21
+ } catch (e) {
22
+ console.error(`Failed to kill process ${pid}:`, e);
23
+ }
24
+ }
25
+ } catch (e) {
26
+ console.log(`No daemon found running on port ${port}.`);
27
+ }
28
+ }
package/src/onboard.ts ADDED
@@ -0,0 +1,43 @@
1
+ import inquirer from 'inquirer';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ /**
6
+ * Onboard the user by configuring their OPENAI_API_KEY and generating a .env for self-hosted use.
7
+ * @param openAiKey Optional key provided via CLI
8
+ */
9
+ export async function onboard(openAiKey?: string) {
10
+ let apiKey = openAiKey;
11
+ let sqlitePath = 'omnikey-selfhosted.sqlite';
12
+
13
+ if (!apiKey) {
14
+ const answers = await inquirer.prompt([
15
+ {
16
+ type: 'input',
17
+ name: 'apiKey',
18
+ message: 'Enter your OPENAI_API_KEY:',
19
+ validate: (input: string) => input.trim() !== '' || 'API key cannot be empty',
20
+ },
21
+ {
22
+ type: 'input',
23
+ name: 'sqlitePath',
24
+ message: 'SQLite DB file path:',
25
+ default: 'omnikey-selfhosted.sqlite',
26
+ },
27
+ ]);
28
+ apiKey = answers.apiKey;
29
+ sqlitePath = answers.sqlitePath;
30
+ }
31
+
32
+ // Save all environment variables to ~/.omnikey/config.json
33
+ const configDir = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.omnikey');
34
+ const configPath = path.join(configDir, 'config.json');
35
+ fs.mkdirSync(configDir, { recursive: true });
36
+ const configVars = {
37
+ OPENAI_API_KEY: apiKey,
38
+ IS_SELF_HOSTED: true,
39
+ SQLITE_PATH: sqlitePath,
40
+ };
41
+ fs.writeFileSync(configPath, JSON.stringify(configVars, null, 2));
42
+ console.log(`Environment variables saved to ${configPath}`);
43
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "CommonJS",
5
+ "outDir": "dist",
6
+ "rootDir": "src",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "forceConsistentCasingInFileNames": true
10
+ },
11
+ "include": ["src/**/*"]
12
+ }