omnikey-cli 1.0.5 → 1.0.7

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
@@ -24,11 +24,15 @@ else if (config_1.config.databaseUrl) {
24
24
  async function initDatabase(logger) {
25
25
  try {
26
26
  await sequelize.authenticate();
27
- // Use `alter: true` so schema changes to models (like new
28
- // subscription fields) are reflected in the database automatically
29
- // without requiring manual migrations for this small service.
30
- await sequelize.sync({ alter: true });
31
- logger.info('Database connection established and models synchronized.');
27
+ // Use `alter: true` only for Postgres, not for SQLite
28
+ if (sequelize.getDialect() === 'sqlite') {
29
+ await sequelize.sync();
30
+ logger.info('Database connection established and models synchronized (SQLite, no alter).');
31
+ }
32
+ else {
33
+ await sequelize.sync({ alter: true });
34
+ logger.info('Database connection established and models synchronized (alter: true).');
35
+ }
32
36
  }
33
37
  catch (err) {
34
38
  logger.error('Unable to connect to the database:', err);
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/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 = {
@@ -6,25 +6,45 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.removeConfigAndDb = removeConfigAndDb;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const child_process_1 = require("child_process");
9
11
  /**
10
- * Removes the ~/.omnikey config directory and the default SQLite database file in the user's home directory.
12
+ * Removes the ~/.omnikey config directory and the SQLite database file specified in config.json.
11
13
  */
12
14
  function removeConfigAndDb() {
13
- const homeDir = process.env.HOME || process.env.USERPROFILE || '.';
15
+ const homeDir = process.env.HOME || process.env.USERPROFILE || os_1.default.homedir();
14
16
  const configDir = path_1.default.join(homeDir, '.omnikey');
15
- const sqlitePath = path_1.default.join(homeDir, 'omnikey-selfhosted.sqlite');
16
- // Remove .omnikey directory
17
- if (fs_1.default.existsSync(configDir)) {
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)) {
18
21
  try {
19
- fs_1.default.rmSync(configDir, { recursive: true, force: true });
20
- console.log(`Removed config directory: ${configDir}`);
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
+ }
21
28
  }
22
29
  catch (e) {
23
- console.error(`Failed to remove config directory: ${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}`);
24
44
  }
25
45
  }
26
46
  else {
27
- console.log(`Config directory does not exist: ${configDir}`);
47
+ console.log(`Launchd agent does not exist: ${plistPath}`);
28
48
  }
29
49
  // Remove SQLite database
30
50
  if (fs_1.default.existsSync(sqlitePath)) {
@@ -39,4 +59,17 @@ function removeConfigAndDb() {
39
59
  else {
40
60
  console.log(`SQLite database does not exist: ${sqlitePath}`);
41
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
+ }
42
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.5",
7
+ "version": "1.0.7",
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/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 = {
@@ -1,24 +1,44 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import os from 'os';
4
+ import { execSync } from 'child_process';
3
5
 
4
6
  /**
5
- * Removes the ~/.omnikey config directory and the default SQLite database file in the user's home directory.
7
+ * Removes the ~/.omnikey config directory and the SQLite database file specified in config.json.
6
8
  */
7
9
  export function removeConfigAndDb() {
8
- const homeDir = process.env.HOME || process.env.USERPROFILE || '.';
10
+ const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir();
9
11
  const configDir = path.join(homeDir, '.omnikey');
10
- const sqlitePath = path.join(homeDir, 'omnikey-selfhosted.sqlite');
12
+ const configPath = path.join(configDir, 'config.json');
13
+ let sqlitePath = path.join(homeDir, 'omnikey-selfhosted.sqlite');
11
14
 
12
- // Remove .omnikey directory
13
- if (fs.existsSync(configDir)) {
15
+ // Try to read SQLITE_PATH from config.json
16
+ if (fs.existsSync(configPath)) {
14
17
  try {
15
- fs.rmSync(configDir, { recursive: true, force: true });
16
- console.log(`Removed config directory: ${configDir}`);
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
+ }
17
24
  } catch (e) {
18
- console.error(`Failed to remove config directory: ${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}`);
19
39
  }
20
40
  } else {
21
- console.log(`Config directory does not exist: ${configDir}`);
41
+ console.log(`Launchd agent does not exist: ${plistPath}`);
22
42
  }
23
43
 
24
44
  // Remove SQLite database
@@ -32,4 +52,16 @@ export function removeConfigAndDb() {
32
52
  } else {
33
53
  console.log(`SQLite database does not exist: ${sqlitePath}`);
34
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
+ }
35
67
  }