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.
- package/.github/copilot-instructions.md +3 -0
- package/README.md +45 -0
- package/dist/daemon.js +48 -0
- package/dist/index.js +36 -0
- package/dist/killDaemon.js +32 -0
- package/dist/onboard.js +46 -0
- package/package.json +37 -0
- package/src/daemon.ts +43 -0
- package/src/index.ts +41 -0
- package/src/killDaemon.ts +28 -0
- package/src/onboard.ts +43 -0
- package/tsconfig.json +12 -0
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
|
+
}
|
package/dist/onboard.js
ADDED
|
@@ -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