omnikey-cli 1.0.4 → 1.0.6

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 CHANGED
@@ -27,11 +27,14 @@ omnikey onboard
27
27
  # Or onboard non-interactively
28
28
  omnikey onboard --open-ai-key YOUR_KEY
29
29
 
30
- # Running the daemon will start the backend
30
+ # Running the daemon will set up a launchd agent and keep the backend server running across system restarts
31
31
  omnikey daemon --port 7071
32
32
 
33
33
  # Kill the daemon
34
34
  omnikey kill-daemon --port 7071
35
+
36
+ # Remove the config directory and SQLite database (and launchd agent)
37
+ omnikey remove-config
35
38
  ```
36
39
 
37
40
  ## Development
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.config = void 0;
7
7
  const dotenv_1 = __importDefault(require("dotenv"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
8
10
  dotenv_1.default.config();
9
11
  function getEnv(name, required) {
10
12
  const value = process.env[name];
@@ -33,6 +35,14 @@ function getNumberEnv(name, defaultValue) {
33
35
  }
34
36
  return parsed;
35
37
  }
38
+ function getSqlitePath() {
39
+ const envPath = getEnv('SQLITE_PATH', false);
40
+ const homeDir = process.env.HOME || process.env.USERPROFILE || os_1.default.homedir();
41
+ const defaultPath = path_1.default.join(homeDir, '.omnikey', 'omnikey-selfhosted.sqlite');
42
+ if (!envPath)
43
+ return defaultPath;
44
+ return path_1.default.isAbsolute(envPath) ? envPath : path_1.default.join(homeDir, '.omnikey', envPath);
45
+ }
36
46
  exports.config = {
37
47
  // Server
38
48
  logLevel: getEnv('LOG_LEVEL', false) || 'info',
@@ -42,7 +52,7 @@ exports.config = {
42
52
  // Database
43
53
  databaseUrl: getEnv('DATABASE_URL', getBooleanEnv('IS_SELF_HOSTED', false) ? false : true),
44
54
  dbLogging: getBooleanEnv('DB_LOGGING', false),
45
- sqlitePath: getEnv('SQLITE_PATH', false) || 'omnikey-selfhosted.sqlite',
55
+ sqlitePath: getSqlitePath(),
46
56
  // Crypto
47
57
  appEncryptionKey: getEnv('APP_ENCRYPTION_KEY', false),
48
58
  // JWT / auth
package/dist/daemon.js CHANGED
@@ -7,8 +7,11 @@ exports.startDaemon = startDaemon;
7
7
  const child_process_1 = require("child_process");
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const fs_1 = __importDefault(require("fs"));
10
+ const os_1 = __importDefault(require("os"));
11
+ const child_process_2 = require("child_process");
10
12
  /**
11
13
  * Start the Omnikey API backend as a daemon on the specified port.
14
+ * Also creates and registers a launchd agent for persistence on macOS.
12
15
  * @param port The port to run the backend on
13
16
  */
14
17
  function startDaemon(port = 7071) {
@@ -16,7 +19,8 @@ function startDaemon(port = 7071) {
16
19
  // Path to the backend entry point (now from backend-dist)
17
20
  const backendPath = path_1.default.resolve(__dirname, '../backend-dist/index.js');
18
21
  // Read and update environment variables from ~/.omnikey/config.json
19
- const configDir = path_1.default.join(process.env.HOME || process.env.USERPROFILE || '.', '.omnikey');
22
+ const homeDir = process.env.HOME || process.env.USERPROFILE || os_1.default.homedir();
23
+ const configDir = path_1.default.join(homeDir, '.omnikey');
20
24
  const configPath = path_1.default.join(configDir, 'config.json');
21
25
  let configVars = {};
22
26
  if (fs_1.default.existsSync(configPath)) {
@@ -37,7 +41,56 @@ function startDaemon(port = 7071) {
37
41
  catch (e) {
38
42
  console.error('Failed to write updated config.json:', e);
39
43
  }
40
- // Spawn the backend as a detached child process with env vars from config
44
+ // Create launchd agent for persistence
45
+ const plistName = 'com.omnikey.daemon.plist';
46
+ const plistPath = path_1.default.join(homeDir, 'Library', 'LaunchAgents', plistName);
47
+ const nodePath = process.execPath;
48
+ const envVars = Object.entries({ ...configVars, OMNIKEY_PORT: String(port) })
49
+ .map(([k, v]) => `<key>${k}</key><string>${v}</string>`)
50
+ .join('\n');
51
+ const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
52
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
53
+ <plist version="1.0">
54
+ <dict>
55
+ <key>Label</key>
56
+ <string>com.omnikey.daemon</string>
57
+ <key>ProgramArguments</key>
58
+ <array>
59
+ <string>${nodePath}</string>
60
+ <string>${backendPath}</string>
61
+ </array>
62
+ <key>EnvironmentVariables</key>
63
+ <dict>
64
+ ${envVars}
65
+ </dict>
66
+ <key>RunAtLoad</key>
67
+ <true/>
68
+ <key>KeepAlive</key>
69
+ <true/>
70
+ <key>StandardOutPath</key>
71
+ <string>${path_1.default.join(configDir, 'daemon.log')}</string>
72
+ <key>StandardErrorPath</key>
73
+ <string>${path_1.default.join(configDir, 'daemon-error.log')}</string>
74
+ <key>WorkingDirectory</key>
75
+ <string>${configDir}</string>
76
+ </dict>
77
+ </plist>
78
+ `;
79
+ // Write plist file
80
+ try {
81
+ const launchAgentsDir = path_1.default.join(homeDir, 'Library', 'LaunchAgents');
82
+ fs_1.default.mkdirSync(launchAgentsDir, { recursive: true });
83
+ fs_1.default.writeFileSync(plistPath, plistContent, 'utf-8');
84
+ // Load the launch agent
85
+ (0, child_process_2.execSync)(`launchctl unload "${plistPath}" || true`); // Unload if already loaded
86
+ (0, child_process_2.execSync)(`launchctl load "${plistPath}"`);
87
+ console.log(`Launch agent created and loaded: ${plistPath}`);
88
+ console.log('Omnikey daemon will auto-restart and persist across reboots.');
89
+ }
90
+ catch (e) {
91
+ console.error('Failed to create or load launch agent:', e);
92
+ }
93
+ // Also start the backend immediately for current session
41
94
  const child = (0, child_process_1.spawn)('node', [backendPath], {
42
95
  env: { ...process.env, ...configVars, OMNIKEY_PORT: String(port) },
43
96
  detached: true,
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ const commander_1 = require("commander");
5
5
  const onboard_1 = require("./onboard");
6
6
  const daemon_1 = require("./daemon");
7
7
  const killDaemon_1 = require("./killDaemon");
8
+ const removeConfig_1 = require("./removeConfig");
8
9
  const program = new commander_1.Command();
9
10
  program
10
11
  .name('omnikey')
@@ -33,4 +34,10 @@ program
33
34
  const port = Number(options.port) || 7071;
34
35
  (0, killDaemon_1.killDaemon)(port);
35
36
  });
37
+ program
38
+ .command('remove-config')
39
+ .description('Remove the .omnikey config directory and the SQLite database from your home directory')
40
+ .action(() => {
41
+ (0, removeConfig_1.removeConfigAndDb)();
42
+ });
36
43
  program.parseAsync(process.argv);
package/dist/onboard.js CHANGED
@@ -13,7 +13,9 @@ const path_1 = __importDefault(require("path"));
13
13
  */
14
14
  async function onboard(openAiKey) {
15
15
  let apiKey = openAiKey;
16
- let sqlitePath = 'omnikey-selfhosted.sqlite';
16
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '.';
17
+ const configDir = path_1.default.join(homeDir, '.omnikey');
18
+ const sqlitePath = path_1.default.join(configDir, 'omnikey-selfhosted.sqlite');
17
19
  if (!apiKey) {
18
20
  const answers = await inquirer_1.default.prompt([
19
21
  {
@@ -22,18 +24,10 @@ async function onboard(openAiKey) {
22
24
  message: 'Enter your OPENAI_API_KEY:',
23
25
  validate: (input) => input.trim() !== '' || 'API key cannot be empty',
24
26
  },
25
- {
26
- type: 'input',
27
- name: 'sqlitePath',
28
- message: 'SQLite DB file path:',
29
- default: 'omnikey-selfhosted.sqlite',
30
- },
31
27
  ]);
32
28
  apiKey = answers.apiKey;
33
- sqlitePath = answers.sqlitePath;
34
29
  }
35
30
  // Save all environment variables to ~/.omnikey/config.json
36
- const configDir = path_1.default.join(process.env.HOME || process.env.USERPROFILE || '.', '.omnikey');
37
31
  const configPath = path_1.default.join(configDir, 'config.json');
38
32
  fs_1.default.mkdirSync(configDir, { recursive: true });
39
33
  const configVars = {
@@ -0,0 +1,75 @@
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.removeConfigAndDb = removeConfigAndDb;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const child_process_1 = require("child_process");
11
+ /**
12
+ * Removes the ~/.omnikey config directory and the SQLite database file specified in config.json.
13
+ */
14
+ function removeConfigAndDb() {
15
+ const homeDir = process.env.HOME || process.env.USERPROFILE || os_1.default.homedir();
16
+ const configDir = path_1.default.join(homeDir, '.omnikey');
17
+ const configPath = path_1.default.join(configDir, 'config.json');
18
+ let sqlitePath = path_1.default.join(homeDir, 'omnikey-selfhosted.sqlite');
19
+ // Try to read SQLITE_PATH from config.json
20
+ if (fs_1.default.existsSync(configPath)) {
21
+ try {
22
+ const configData = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
23
+ if (configData.SQLITE_PATH) {
24
+ sqlitePath = path_1.default.isAbsolute(configData.SQLITE_PATH)
25
+ ? configData.SQLITE_PATH
26
+ : path_1.default.join(homeDir, configData.SQLITE_PATH);
27
+ }
28
+ }
29
+ catch (e) {
30
+ console.error(`Failed to read config.json: ${e}`);
31
+ }
32
+ }
33
+ // Remove launchd agent if exists (macOS)
34
+ const plistName = 'com.omnikey.daemon.plist';
35
+ const plistPath = path_1.default.join(homeDir, 'Library', 'LaunchAgents', plistName);
36
+ if (fs_1.default.existsSync(plistPath)) {
37
+ try {
38
+ (0, child_process_1.execSync)(`launchctl unload "${plistPath}"`);
39
+ fs_1.default.rmSync(plistPath);
40
+ console.log(`Removed launchd agent: ${plistPath}`);
41
+ }
42
+ catch (e) {
43
+ console.error(`Failed to remove launchd agent: ${e}`);
44
+ }
45
+ }
46
+ else {
47
+ console.log(`Launchd agent does not exist: ${plistPath}`);
48
+ }
49
+ // Remove SQLite database
50
+ if (fs_1.default.existsSync(sqlitePath)) {
51
+ try {
52
+ fs_1.default.rmSync(sqlitePath);
53
+ console.log(`Removed SQLite database: ${sqlitePath}`);
54
+ }
55
+ catch (e) {
56
+ console.error(`Failed to remove SQLite database: ${e}`);
57
+ }
58
+ }
59
+ else {
60
+ console.log(`SQLite database does not exist: ${sqlitePath}`);
61
+ }
62
+ // Remove .omnikey directory
63
+ if (fs_1.default.existsSync(configDir)) {
64
+ try {
65
+ fs_1.default.rmSync(configDir, { recursive: true, force: true });
66
+ console.log(`Removed config directory: ${configDir}`);
67
+ }
68
+ catch (e) {
69
+ console.error(`Failed to remove config directory: ${e}`);
70
+ }
71
+ }
72
+ else {
73
+ console.log(`Config directory does not exist: ${configDir}`);
74
+ }
75
+ }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "registry": "https://registry.npmjs.org/"
6
6
  },
7
- "version": "1.0.4",
7
+ "version": "1.0.6",
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",
package/src/daemon.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  import { spawn } from 'child_process';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
+ import os from 'os';
5
+ import { execSync } from 'child_process';
4
6
 
5
7
  /**
6
8
  * Start the Omnikey API backend as a daemon on the specified port.
9
+ * Also creates and registers a launchd agent for persistence on macOS.
7
10
  * @param port The port to run the backend on
8
11
  */
9
12
  export function startDaemon(port: number = 7071) {
@@ -13,7 +16,8 @@ export function startDaemon(port: number = 7071) {
13
16
  const backendPath = path.resolve(__dirname, '../backend-dist/index.js');
14
17
 
15
18
  // Read and update environment variables from ~/.omnikey/config.json
16
- const configDir = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.omnikey');
19
+ const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir();
20
+ const configDir = path.join(homeDir, '.omnikey');
17
21
  const configPath = path.join(configDir, 'config.json');
18
22
  let configVars: Record<string, any> = {};
19
23
  if (fs.existsSync(configPath)) {
@@ -32,7 +36,57 @@ export function startDaemon(port: number = 7071) {
32
36
  } catch (e) {
33
37
  console.error('Failed to write updated config.json:', e);
34
38
  }
35
- // Spawn the backend as a detached child process with env vars from config
39
+
40
+ // Create launchd agent for persistence
41
+ const plistName = 'com.omnikey.daemon.plist';
42
+ const plistPath = path.join(homeDir, 'Library', 'LaunchAgents', plistName);
43
+ const nodePath = process.execPath;
44
+ const envVars = Object.entries({ ...configVars, OMNIKEY_PORT: String(port) })
45
+ .map(([k, v]) => `<key>${k}</key><string>${v}</string>`)
46
+ .join('\n');
47
+ const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
48
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
49
+ <plist version="1.0">
50
+ <dict>
51
+ <key>Label</key>
52
+ <string>com.omnikey.daemon</string>
53
+ <key>ProgramArguments</key>
54
+ <array>
55
+ <string>${nodePath}</string>
56
+ <string>${backendPath}</string>
57
+ </array>
58
+ <key>EnvironmentVariables</key>
59
+ <dict>
60
+ ${envVars}
61
+ </dict>
62
+ <key>RunAtLoad</key>
63
+ <true/>
64
+ <key>KeepAlive</key>
65
+ <true/>
66
+ <key>StandardOutPath</key>
67
+ <string>${path.join(configDir, 'daemon.log')}</string>
68
+ <key>StandardErrorPath</key>
69
+ <string>${path.join(configDir, 'daemon-error.log')}</string>
70
+ <key>WorkingDirectory</key>
71
+ <string>${configDir}</string>
72
+ </dict>
73
+ </plist>
74
+ `;
75
+ // Write plist file
76
+ try {
77
+ const launchAgentsDir = path.join(homeDir, 'Library', 'LaunchAgents');
78
+ fs.mkdirSync(launchAgentsDir, { recursive: true });
79
+ fs.writeFileSync(plistPath, plistContent, 'utf-8');
80
+ // Load the launch agent
81
+ execSync(`launchctl unload "${plistPath}" || true`); // Unload if already loaded
82
+ execSync(`launchctl load "${plistPath}"`);
83
+ console.log(`Launch agent created and loaded: ${plistPath}`);
84
+ console.log('Omnikey daemon will auto-restart and persist across reboots.');
85
+ } catch (e) {
86
+ console.error('Failed to create or load launch agent:', e);
87
+ }
88
+
89
+ // Also start the backend immediately for current session
36
90
  const child = spawn('node', [backendPath], {
37
91
  env: { ...process.env, ...configVars, OMNIKEY_PORT: String(port) },
38
92
  detached: true,
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import { onboard } from './onboard';
4
4
 
5
5
  import { startDaemon } from './daemon';
6
6
  import { killDaemon } from './killDaemon';
7
+ import { removeConfigAndDb } from './removeConfig';
7
8
 
8
9
  const program = new Command();
9
10
 
@@ -38,4 +39,13 @@ program
38
39
  killDaemon(port);
39
40
  });
40
41
 
42
+ program
43
+ .command('remove-config')
44
+ .description(
45
+ 'Remove the .omnikey config directory and the SQLite database from your home directory',
46
+ )
47
+ .action(() => {
48
+ removeConfigAndDb();
49
+ });
50
+
41
51
  program.parseAsync(process.argv);
package/src/onboard.ts CHANGED
@@ -8,7 +8,9 @@ import path from 'path';
8
8
  */
9
9
  export async function onboard(openAiKey?: string) {
10
10
  let apiKey = openAiKey;
11
- let sqlitePath = 'omnikey-selfhosted.sqlite';
11
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '.';
12
+ const configDir = path.join(homeDir, '.omnikey');
13
+ const sqlitePath = path.join(configDir, 'omnikey-selfhosted.sqlite');
12
14
 
13
15
  if (!apiKey) {
14
16
  const answers = await inquirer.prompt([
@@ -18,19 +20,11 @@ export async function onboard(openAiKey?: string) {
18
20
  message: 'Enter your OPENAI_API_KEY:',
19
21
  validate: (input: string) => input.trim() !== '' || 'API key cannot be empty',
20
22
  },
21
- {
22
- type: 'input',
23
- name: 'sqlitePath',
24
- message: 'SQLite DB file path:',
25
- default: 'omnikey-selfhosted.sqlite',
26
- },
27
23
  ]);
28
24
  apiKey = answers.apiKey;
29
- sqlitePath = answers.sqlitePath;
30
25
  }
31
26
 
32
27
  // Save all environment variables to ~/.omnikey/config.json
33
- const configDir = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.omnikey');
34
28
  const configPath = path.join(configDir, 'config.json');
35
29
  fs.mkdirSync(configDir, { recursive: true });
36
30
  const configVars = {
@@ -0,0 +1,67 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { execSync } from 'child_process';
5
+
6
+ /**
7
+ * Removes the ~/.omnikey config directory and the SQLite database file specified in config.json.
8
+ */
9
+ export function removeConfigAndDb() {
10
+ const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir();
11
+ const configDir = path.join(homeDir, '.omnikey');
12
+ const configPath = path.join(configDir, 'config.json');
13
+ let sqlitePath = path.join(homeDir, 'omnikey-selfhosted.sqlite');
14
+
15
+ // Try to read SQLITE_PATH from config.json
16
+ if (fs.existsSync(configPath)) {
17
+ try {
18
+ const configData = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
19
+ if (configData.SQLITE_PATH) {
20
+ sqlitePath = path.isAbsolute(configData.SQLITE_PATH)
21
+ ? configData.SQLITE_PATH
22
+ : path.join(homeDir, configData.SQLITE_PATH);
23
+ }
24
+ } catch (e) {
25
+ console.error(`Failed to read config.json: ${e}`);
26
+ }
27
+ }
28
+
29
+ // Remove launchd agent if exists (macOS)
30
+ const plistName = 'com.omnikey.daemon.plist';
31
+ const plistPath = path.join(homeDir, 'Library', 'LaunchAgents', plistName);
32
+ if (fs.existsSync(plistPath)) {
33
+ try {
34
+ execSync(`launchctl unload "${plistPath}"`);
35
+ fs.rmSync(plistPath);
36
+ console.log(`Removed launchd agent: ${plistPath}`);
37
+ } catch (e) {
38
+ console.error(`Failed to remove launchd agent: ${e}`);
39
+ }
40
+ } else {
41
+ console.log(`Launchd agent does not exist: ${plistPath}`);
42
+ }
43
+
44
+ // Remove SQLite database
45
+ if (fs.existsSync(sqlitePath)) {
46
+ try {
47
+ fs.rmSync(sqlitePath);
48
+ console.log(`Removed SQLite database: ${sqlitePath}`);
49
+ } catch (e) {
50
+ console.error(`Failed to remove SQLite database: ${e}`);
51
+ }
52
+ } else {
53
+ console.log(`SQLite database does not exist: ${sqlitePath}`);
54
+ }
55
+
56
+ // Remove .omnikey directory
57
+ if (fs.existsSync(configDir)) {
58
+ try {
59
+ fs.rmSync(configDir, { recursive: true, force: true });
60
+ console.log(`Removed config directory: ${configDir}`);
61
+ } catch (e) {
62
+ console.error(`Failed to remove config directory: ${e}`);
63
+ }
64
+ } else {
65
+ console.log(`Config directory does not exist: ${configDir}`);
66
+ }
67
+ }