omnikey-cli 1.5.8 → 1.6.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.
@@ -20,12 +20,12 @@ const LABEL = `com.${os_1.default.userInfo().username}.telegram`;
20
20
  const PLIST_NAME = `${LABEL}.plist`;
21
21
  const WINDOWS_SERVICE_NAME = 'OmnikeyTelegram';
22
22
  // At runtime __dirname is cli/dist/. The bundled telegram app is copied into
23
- // cli/telegram-client-dist/ by the build:telegram-client script, so one level
24
- // up from dist/ lands at the package root, then into the bundle directory.
23
+ // cli/telegram-client-dist/ by the build:telegram-client script (flat layout,
24
+ // matching backend-dist/), so one level up from dist/ lands at the package
25
25
  // This matches resolveBundleRoot() in telegramClient.ts and works correctly
26
26
  // both in the monorepo and after `npm install -g omnikey-cli`.
27
27
  const TELEGRAM_BOT_ROOT = path_1.default.resolve(__dirname, '..', 'telegram-client-dist');
28
- const ENTRY_POINT = path_1.default.join(TELEGRAM_BOT_ROOT, 'dist', 'index.js');
28
+ const ENTRY_POINT = path_1.default.join(TELEGRAM_BOT_ROOT, 'index.js');
29
29
  const HOME = (0, utils_1.getHomeDir)();
30
30
  // macOS — launchd LaunchAgent paths
31
31
  const LAUNCH_AGENTS_DIR = path_1.default.join(HOME, 'Library', 'LaunchAgents');
@@ -289,7 +289,9 @@ async function startWindows() {
289
289
  (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'Start', 'SERVICE_AUTO_START'], {
290
290
  stdio: 'pipe',
291
291
  });
292
- (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'DisplayName', 'Omnikey Telegram'], { stdio: 'pipe' });
292
+ (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'DisplayName', 'Omnikey Telegram'], {
293
+ stdio: 'pipe',
294
+ });
293
295
  (0, child_process_1.execFileSync)(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'Description', 'Omnikey Telegram Daemon'], { stdio: 'pipe' });
294
296
  (0, child_process_1.execFileSync)(nssmPath, ['start', WINDOWS_SERVICE_NAME], { stdio: 'pipe' });
295
297
  console.log(`NSSM service installed and started: ${WINDOWS_SERVICE_NAME}`);
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "registry": "https://registry.npmjs.org/"
6
6
  },
7
- "version": "1.5.8",
7
+ "version": "1.6.0",
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",
@@ -14,10 +14,14 @@
14
14
  "omnikey": "dist/index.js"
15
15
  },
16
16
  "scripts": {
17
- "build": "tsc && npm run copy-backend && npm run build:telegram-client",
17
+ "build": "tsc && yarn run copy-backend && yarn run build:telegram-client",
18
18
  "start": "node dist/index.js",
19
- "copy-backend": "rm -rf backend-dist && mkdir -p backend-dist && cp -R ../dist/* backend-dist/",
20
- "build:telegram-client": "rm -rf telegram-client-dist && mkdir -p telegram-client-dist/dist && cp -R ../telegram/dist/* telegram-client-dist/dist/"
19
+ "copy-backend": "rm -rf backend-dist && mkdir -p backend-dist && cp -R ../api/dist/* backend-dist/",
20
+ "build:telegram-client": "rm -rf telegram-client-dist && mkdir -p telegram-client-dist && cp -R ../telegram/dist/* telegram-client-dist/",
21
+ "clean": "rm -rf dist backend-dist telegram-client-dist",
22
+ "format": "prettier --write \"src/**/*.ts\"",
23
+ "test": "echo \"(no tests in this workspace)\" && exit 0",
24
+ "lint": "echo \"(no lint in this workspace)\" && exit 0"
21
25
  },
22
26
  "keywords": [
23
27
  "cli",
@@ -33,7 +37,6 @@
33
37
  "@google/genai": "^1.46.0",
34
38
  "@modelcontextprotocol/sdk": "^1.29.0",
35
39
  "axios": "^1.13.5",
36
- "better-sqlite3": "^12.10.0",
37
40
  "commander": "^11.0.0",
38
41
  "cors": "^2.8.5",
39
42
  "cron-parser": "^4.9.0",
@@ -54,7 +57,6 @@
54
57
  "zod": "^4.3.6"
55
58
  },
56
59
  "devDependencies": {
57
- "@types/better-sqlite3": "^7.6.13",
58
60
  "@types/inquirer": "^9.0.9",
59
61
  "@types/node-telegram-bot-api": "^0.64.0",
60
62
  "typescript": "^5.0.0"
package/src/index.ts CHANGED
@@ -26,7 +26,7 @@ const program = new Command();
26
26
  program
27
27
  .name('omnikey')
28
28
  .description('Omnikey CLI for onboarding and configuration')
29
- .version('1.5.4');
29
+ .version('1.6.0');
30
30
 
31
31
  program
32
32
  .command('onboard')
package/src/onboard.ts CHANGED
@@ -7,6 +7,10 @@ const AI_PROVIDERS = [
7
7
  { name: 'OpenAI (gpt-4o-mini / gpt-5.5)', value: 'openai' },
8
8
  { name: 'Anthropic — Claude (claude-haiku / claude-opus)', value: 'anthropic' },
9
9
  { name: 'Google Gemini (gemini-2.5-flash / gemini-2.5-pro)', value: 'gemini' },
10
+ {
11
+ name: 'NVIDIA Nemotron (nemotron-3-nano / nemotron-3-ultra) — open weights',
12
+ value: 'nemotron',
13
+ },
10
14
  ];
11
15
 
12
16
  const SEARCH_PROVIDERS = [
@@ -22,12 +26,14 @@ const AI_PROVIDER_KEY_ENV: Record<string, string> = {
22
26
  openai: 'OPENAI_API_KEY',
23
27
  anthropic: 'ANTHROPIC_API_KEY',
24
28
  gemini: 'GEMINI_API_KEY',
29
+ nemotron: 'NVIDIA_API_KEY',
25
30
  };
26
31
 
27
32
  const AI_PROVIDER_KEY_LABEL: Record<string, string> = {
28
33
  openai: 'OpenAI API key (from platform.openai.com)',
29
34
  anthropic: 'Anthropic API key (from console.anthropic.com)',
30
35
  gemini: 'Google Gemini API key (from ai.google.dev)',
36
+ nemotron: 'NVIDIA API key (from build.nvidia.com — used by NIM/Nemotron)',
31
37
  };
32
38
 
33
39
  /**
@@ -57,6 +63,37 @@ export async function onboard() {
57
63
  },
58
64
  ]);
59
65
 
66
+ // Provider-specific extras
67
+ const providerExtras: Record<string, string> = {};
68
+
69
+ if (aiProvider === 'nemotron') {
70
+ // Nemotron is served either via the public NVIDIA NIM gateway or a
71
+ // self-hosted NIM microservice. Always ask the user for the base URL so
72
+ // they can point at either. The default value matches the public gateway
73
+ // so pressing Enter "just works" for build.nvidia.com keys.
74
+ const DEFAULT_NEMOTRON_URL = 'https://integrate.api.nvidia.com/v1';
75
+ const { nemotronBaseUrl } = await inquirer.prompt([
76
+ {
77
+ type: 'input',
78
+ name: 'nemotronBaseUrl',
79
+ message: 'Enter the Nemotron / NVIDIA NIM base URL (press Enter for the public gateway):',
80
+ default: DEFAULT_NEMOTRON_URL,
81
+ validate: (input: string) => {
82
+ const trimmed = input.trim();
83
+ if (trimmed === '') return 'URL cannot be empty';
84
+ try {
85
+ // eslint-disable-next-line no-new
86
+ new URL(trimmed);
87
+ return true;
88
+ } catch {
89
+ return 'Please enter a valid URL (including the scheme, e.g. https://...)';
90
+ }
91
+ },
92
+ },
93
+ ]);
94
+ providerExtras['NEMOTRON_BASE_URL'] = nemotronBaseUrl.trim();
95
+ }
96
+
60
97
  // Web search provider (optional)
61
98
  const { provider } = await inquirer.prompt([
62
99
  {
@@ -122,6 +159,7 @@ export async function onboard() {
122
159
  [AI_PROVIDER_KEY_ENV[aiProvider]]: apiKey,
123
160
  IS_SELF_HOSTED: true,
124
161
  SQLITE_PATH: sqlitePath,
162
+ ...providerExtras,
125
163
  ...searchConfig,
126
164
  };
127
165
  fs.writeFileSync(configPath, JSON.stringify(configVars, null, 2));
@@ -24,7 +24,7 @@ function resolveBundleRoot(): string {
24
24
  }
25
25
 
26
26
  function resolveBundledEntry(): string {
27
- return path.join(resolveBundleRoot(), 'dist', 'index.js');
27
+ return path.join(resolveBundleRoot(), 'index.js');
28
28
  }
29
29
 
30
30
  function persistConfig(values: Record<string, string>): void {
@@ -11,12 +11,12 @@ const PLIST_NAME = `${LABEL}.plist`;
11
11
  const WINDOWS_SERVICE_NAME = 'OmnikeyTelegram';
12
12
 
13
13
  // At runtime __dirname is cli/dist/. The bundled telegram app is copied into
14
- // cli/telegram-client-dist/ by the build:telegram-client script, so one level
15
- // up from dist/ lands at the package root, then into the bundle directory.
14
+ // cli/telegram-client-dist/ by the build:telegram-client script (flat layout,
15
+ // matching backend-dist/), so one level up from dist/ lands at the package
16
16
  // This matches resolveBundleRoot() in telegramClient.ts and works correctly
17
17
  // both in the monorepo and after `npm install -g omnikey-cli`.
18
18
  const TELEGRAM_BOT_ROOT = path.resolve(__dirname, '..', 'telegram-client-dist');
19
- const ENTRY_POINT = path.join(TELEGRAM_BOT_ROOT, 'dist', 'index.js');
19
+ const ENTRY_POINT = path.join(TELEGRAM_BOT_ROOT, 'index.js');
20
20
 
21
21
  const HOME = getHomeDir();
22
22
 
@@ -300,11 +300,9 @@ async function startWindows(): Promise<void> {
300
300
  execFileSync(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'Start', 'SERVICE_AUTO_START'], {
301
301
  stdio: 'pipe',
302
302
  });
303
- execFileSync(
304
- nssmPath,
305
- ['set', WINDOWS_SERVICE_NAME, 'DisplayName', 'Omnikey Telegram'],
306
- { stdio: 'pipe' },
307
- );
303
+ execFileSync(nssmPath, ['set', WINDOWS_SERVICE_NAME, 'DisplayName', 'Omnikey Telegram'], {
304
+ stdio: 'pipe',
305
+ });
308
306
  execFileSync(
309
307
  nssmPath,
310
308
  ['set', WINDOWS_SERVICE_NAME, 'Description', 'Omnikey Telegram Daemon'],
@@ -70,7 +70,7 @@ async function setDefaultTaskTemplate(logger, templateId) {
70
70
  timeout: 10000,
71
71
  headers: { Authorization: `Bearer ${token}` },
72
72
  });
73
- logger.info("Set default task template", { templateId });
73
+ logger.info('Set default task template', { templateId });
74
74
  }
75
75
  async function listProjectGroups(logger) {
76
76
  const token = await (0, omnikeyAuth_1.fetchJwtToken)(logger);
@@ -82,67 +82,63 @@ async function listProjectGroups(logger) {
82
82
  return resp.data?.groups ?? [];
83
83
  }
84
84
  class AgentAbortError extends Error {
85
- constructor(message = "Agent run aborted") {
85
+ constructor(message = 'Agent run aborted') {
86
86
  super(message);
87
- this.name = "AgentAbortError";
87
+ this.name = 'AgentAbortError';
88
88
  }
89
89
  }
90
90
  exports.AgentAbortError = AgentAbortError;
91
91
  function extractTagged(content, tag) {
92
- const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "i");
92
+ const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'i');
93
93
  const m = content.match(re);
94
94
  return m?.[1]?.trim() || null;
95
95
  }
96
96
  function stripTagged(content, tag) {
97
- return content.replace(new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`, "gi"), "");
97
+ return content.replace(new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`, 'gi'), '');
98
98
  }
99
99
  function cleanReasoning(content) {
100
100
  return content
101
- .replace(/<\/?shell_function_calls>/gi, "")
102
- .replace(/<final_answer>([\s\S]*?)<\/final_answer>/gi, "$1")
101
+ .replace(/<\/?shell_function_calls>/gi, '')
102
+ .replace(/<final_answer>([\s\S]*?)<\/final_answer>/gi, '$1')
103
103
  .trim();
104
104
  }
105
105
  const SHELL_TIMEOUT_MS = 5 * 60 * 1000;
106
106
  const SHELL_OUTPUT_MAX = 64 * 1024;
107
107
  // Mirrors WINDOWS_SHELL_CANDIDATES in src/agent/mcpRuntime.ts
108
108
  const WINDOWS_SHELL_CANDIDATES = [
109
- "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
110
- "C:\\Program Files\\PowerShell\\6\\pwsh.exe",
111
- "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
112
- "C:\\Windows\\System32\\cmd.exe",
113
- "C:\\Windows\\cmd.exe",
109
+ 'C:\\Program Files\\PowerShell\\7\\pwsh.exe',
110
+ 'C:\\Program Files\\PowerShell\\6\\pwsh.exe',
111
+ 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
112
+ 'C:\\Windows\\System32\\cmd.exe',
113
+ 'C:\\Windows\\cmd.exe',
114
114
  ];
115
115
  // Resolve the Windows shell: COMSPEC → SystemRoot\System32\cmd.exe → candidate list.
116
116
  // Mirrors resolveLoginShell() in src/agent/mcpRuntime.ts, with SystemRoot used to
117
117
  // locate cmd.exe from the Win32 system root rather than a hardcoded drive letter.
118
118
  function resolveWindowsShell() {
119
- const comspec = process.env.COMSPEC ?? "";
119
+ const comspec = process.env.COMSPEC ?? '';
120
120
  if (comspec && (0, fs_1.existsSync)(comspec))
121
121
  return comspec;
122
- const systemRoot = process.env.SystemRoot ?? "C:\\Windows";
123
- const cmdFromRoot = path_1.default.join(systemRoot, "System32", "cmd.exe");
122
+ const systemRoot = process.env.SystemRoot ?? 'C:\\Windows';
123
+ const cmdFromRoot = path_1.default.join(systemRoot, 'System32', 'cmd.exe');
124
124
  if ((0, fs_1.existsSync)(cmdFromRoot))
125
125
  return cmdFromRoot;
126
126
  for (const candidate of WINDOWS_SHELL_CANDIDATES) {
127
127
  if ((0, fs_1.existsSync)(candidate))
128
128
  return candidate;
129
129
  }
130
- return "cmd.exe";
130
+ return 'cmd.exe';
131
131
  }
132
132
  // Build shell args for the resolved shell — mirrors wrapWithLoginShell() in
133
133
  // src/agent/mcpRuntime.ts. PowerShell/pwsh use -NoProfile -Command; cmd uses /c.
134
134
  function buildWindowsShellArgs(shell, script) {
135
135
  const name = path_1.default.basename(shell).toLowerCase();
136
- if (name === "pwsh.exe" || name === "powershell.exe") {
137
- return ["-NoProfile", "-Command", script];
136
+ if (name === 'pwsh.exe' || name === 'powershell.exe') {
137
+ return ['-NoProfile', '-Command', script];
138
138
  }
139
- return ["/c", script];
139
+ return ['/c', script];
140
140
  }
141
- const PLATFORM = process.platform === "win32"
142
- ? "windows"
143
- : process.platform === "darwin"
144
- ? "macos"
145
- : "linux";
141
+ const PLATFORM = process.platform === 'win32' ? 'windows' : process.platform === 'darwin' ? 'macos' : 'linux';
146
142
  /**
147
143
  * Execute a shell script locally and capture combined stdout+stderr.
148
144
  * On macOS/Linux: invoke the login shell with `-l -c <script>` (mirrors the
@@ -156,15 +152,15 @@ function runShellScript(script, logger) {
156
152
  return new Promise((resolve) => {
157
153
  let shell;
158
154
  let shellArgs;
159
- if (process.platform !== "darwin" && process.platform === "win32") {
155
+ if (process.platform !== 'darwin' && process.platform === 'win32') {
160
156
  shell = resolveWindowsShell();
161
157
  shellArgs = buildWindowsShellArgs(shell, script);
162
158
  }
163
159
  else {
164
- shell = process.env.SHELL || "/bin/zsh";
165
- shellArgs = ["-l", "-c", script];
160
+ shell = process.env.SHELL || '/bin/zsh';
161
+ shellArgs = ['-l', '-c', script];
166
162
  }
167
- logger.info("Executing shell script from agent", {
163
+ logger.info('Executing shell script from agent', {
168
164
  shell,
169
165
  platform: PLATFORM,
170
166
  length: script.length,
@@ -173,7 +169,7 @@ function runShellScript(script, logger) {
173
169
  cwd: process.env.HOME ?? process.env.USERPROFILE ?? process.cwd(),
174
170
  env: process.env,
175
171
  });
176
- let buf = "";
172
+ let buf = '';
177
173
  let truncated = false;
178
174
  const append = (chunk) => {
179
175
  if (truncated)
@@ -183,7 +179,7 @@ function runShellScript(script, logger) {
183
179
  truncated = true;
184
180
  return;
185
181
  }
186
- const text = chunk.toString("utf8");
182
+ const text = chunk.toString('utf8');
187
183
  if (text.length <= room) {
188
184
  buf += text;
189
185
  }
@@ -192,33 +188,31 @@ function runShellScript(script, logger) {
192
188
  truncated = true;
193
189
  }
194
190
  };
195
- child.stdout.on("data", append);
196
- child.stderr.on("data", append);
191
+ child.stdout.on('data', append);
192
+ child.stderr.on('data', append);
197
193
  const timeout = setTimeout(() => {
198
- logger.warn("Shell script timed out; sending SIGTERM", {
194
+ logger.warn('Shell script timed out; sending SIGTERM', {
199
195
  timeoutMs: SHELL_TIMEOUT_MS,
200
196
  });
201
197
  try {
202
- child.kill("SIGTERM");
198
+ child.kill('SIGTERM');
203
199
  }
204
200
  catch {
205
201
  /* noop */
206
202
  }
207
203
  }, SHELL_TIMEOUT_MS);
208
- child.on("error", (err) => {
204
+ child.on('error', (err) => {
209
205
  clearTimeout(timeout);
210
206
  resolve({
211
207
  output: `${buf}\n[shell spawn error: ${err.message}]`,
212
208
  status: -1,
213
209
  });
214
210
  });
215
- child.on("close", (code, signal) => {
211
+ child.on('close', (code, signal) => {
216
212
  clearTimeout(timeout);
217
- const status = typeof code === "number" ? code : signal ? 1 : 0;
218
- const finalOutput = truncated
219
- ? `${buf}\n... [truncated to ${SHELL_OUTPUT_MAX} bytes]`
220
- : buf;
221
- logger.info("Shell script finished", {
213
+ const status = typeof code === 'number' ? code : signal ? 1 : 0;
214
+ const finalOutput = truncated ? `${buf}\n... [truncated to ${SHELL_OUTPUT_MAX} bytes]` : buf;
215
+ logger.info('Shell script finished', {
222
216
  status,
223
217
  signal,
224
218
  outputLength: finalOutput.length,
@@ -239,7 +233,7 @@ function runShellScript(script, logger) {
239
233
  async function runAgentTurn(logger, opts) {
240
234
  const token = await (0, omnikeyAuth_1.fetchJwtToken)(logger);
241
235
  const sessionId = opts.sessionId || (0, crypto_1.randomUUID)();
242
- const url = (0, config_1.omnikeyWsUrl)("/ws/omni-agent");
236
+ const url = (0, config_1.omnikeyWsUrl)('/ws/omni-agent');
243
237
  return new Promise((resolve, reject) => {
244
238
  if (opts.signal?.aborted) {
245
239
  reject(new AgentAbortError());
@@ -254,7 +248,7 @@ async function runAgentTurn(logger, opts) {
254
248
  return;
255
249
  settled = true;
256
250
  if (opts.signal && onAbort) {
257
- opts.signal.removeEventListener("abort", onAbort);
251
+ opts.signal.removeEventListener('abort', onAbort);
258
252
  }
259
253
  try {
260
254
  ws.close();
@@ -269,12 +263,12 @@ async function runAgentTurn(logger, opts) {
269
263
  };
270
264
  const onAbort = opts.signal
271
265
  ? () => {
272
- logger.info("Agent run aborted by caller", { sessionId });
266
+ logger.info('Agent run aborted by caller', { sessionId });
273
267
  finish(new AgentAbortError());
274
268
  }
275
269
  : null;
276
270
  if (opts.signal && onAbort) {
277
- opts.signal.addEventListener("abort", onAbort, { once: true });
271
+ opts.signal.addEventListener('abort', onAbort, { once: true });
278
272
  }
279
273
  const send = (msg) => {
280
274
  ws.send(JSON.stringify(msg), (err) => {
@@ -282,11 +276,11 @@ async function runAgentTurn(logger, opts) {
282
276
  finish(err);
283
277
  });
284
278
  };
285
- ws.on("open", () => {
286
- logger.info("Agent WebSocket open", { sessionId });
279
+ ws.on('open', () => {
280
+ logger.info('Agent WebSocket open', { sessionId });
287
281
  send({
288
282
  session_id: sessionId,
289
- sender: "client",
283
+ sender: 'client',
290
284
  content: opts.prompt,
291
285
  is_terminal_output: false,
292
286
  is_error: false,
@@ -294,56 +288,56 @@ async function runAgentTurn(logger, opts) {
294
288
  group_name: opts.groupName,
295
289
  });
296
290
  });
297
- ws.on("message", async (data) => {
291
+ ws.on('message', async (data) => {
298
292
  let msg;
299
293
  try {
300
294
  msg = JSON.parse(data.toString());
301
295
  }
302
296
  catch (e) {
303
- logger.warn("Failed to parse agent ws message", {
297
+ logger.warn('Failed to parse agent ws message', {
304
298
  error: e.message,
305
299
  });
306
300
  return;
307
301
  }
308
- const content = msg.content || "";
302
+ const content = msg.content || '';
309
303
  if (msg.is_error) {
310
- finish(new Error(content || "Agent reported an error"));
304
+ finish(new Error(content || 'Agent reported an error'));
311
305
  return;
312
306
  }
313
307
  if (msg.is_web_call) {
314
- await opts.onBlock({ kind: "webCall", text: content });
308
+ await opts.onBlock({ kind: 'webCall', text: content });
315
309
  return;
316
310
  }
317
311
  if (msg.is_image_rendering) {
318
- await opts.onBlock({ kind: "imageRendering", text: content });
312
+ await opts.onBlock({ kind: 'imageRendering', text: content });
319
313
  return;
320
314
  }
321
315
  if (msg.is_mcp_call) {
322
- await opts.onBlock({ kind: "mcpCall", text: content });
316
+ await opts.onBlock({ kind: 'mcpCall', text: content });
323
317
  return;
324
318
  }
325
- const finalAnswer = extractTagged(content, "final_answer");
319
+ const finalAnswer = extractTagged(content, 'final_answer');
326
320
  if (finalAnswer) {
327
- await opts.onBlock({ kind: "finalAnswer", text: finalAnswer });
321
+ await opts.onBlock({ kind: 'finalAnswer', text: finalAnswer });
328
322
  finish(null, { sessionId, finalAnswer });
329
323
  return;
330
324
  }
331
- const shellScript = extractTagged(content, "shell_script");
325
+ const shellScript = extractTagged(content, 'shell_script');
332
326
  if (shellScript) {
333
- const reasoning = cleanReasoning(stripTagged(content, "shell_script"));
327
+ const reasoning = cleanReasoning(stripTagged(content, 'shell_script'));
334
328
  if (reasoning)
335
- await opts.onBlock({ kind: "reasoning", text: reasoning });
336
- await opts.onBlock({ kind: "shellCommand", text: shellScript });
329
+ await opts.onBlock({ kind: 'reasoning', text: reasoning });
330
+ await opts.onBlock({ kind: 'shellCommand', text: shellScript });
337
331
  try {
338
332
  const { output, status } = await runShellScript(shellScript, logger);
339
- const statusLabel = status === 0 ? "success" : `error (exit code: ${status})`;
333
+ const statusLabel = status === 0 ? 'success' : `error (exit code: ${status})`;
340
334
  await opts.onBlock({
341
- kind: "terminalOutput",
335
+ kind: 'terminalOutput',
342
336
  text: `[terminal ${statusLabel}]\n${output}`,
343
337
  });
344
338
  send({
345
339
  session_id: sessionId,
346
- sender: "client",
340
+ sender: 'client',
347
341
  content: output,
348
342
  is_terminal_output: true,
349
343
  is_error: status !== 0,
@@ -352,14 +346,14 @@ async function runAgentTurn(logger, opts) {
352
346
  }
353
347
  catch (err) {
354
348
  const message = err.message;
355
- logger.error("Shell execution failed", { error: message });
349
+ logger.error('Shell execution failed', { error: message });
356
350
  await opts.onBlock({
357
- kind: "terminalOutput",
351
+ kind: 'terminalOutput',
358
352
  text: `[terminal error]\n${message}`,
359
353
  });
360
354
  send({
361
355
  session_id: sessionId,
362
- sender: "client",
356
+ sender: 'client',
363
357
  content: `Failed to execute shell script: ${message}`,
364
358
  is_terminal_output: true,
365
359
  is_error: true,
@@ -370,16 +364,16 @@ async function runAgentTurn(logger, opts) {
370
364
  }
371
365
  const reasoning = cleanReasoning(content);
372
366
  if (reasoning) {
373
- await opts.onBlock({ kind: "reasoning", text: reasoning });
367
+ await opts.onBlock({ kind: 'reasoning', text: reasoning });
374
368
  }
375
369
  });
376
- ws.on("error", (err) => {
377
- logger.error("Agent WebSocket error", { error: err.message });
370
+ ws.on('error', (err) => {
371
+ logger.error('Agent WebSocket error', { error: err.message });
378
372
  finish(err);
379
373
  });
380
- ws.on("close", () => {
374
+ ws.on('close', () => {
381
375
  if (!settled)
382
- finish(new Error("Agent WebSocket closed before final answer"));
376
+ finish(new Error('Agent WebSocket closed before final answer'));
383
377
  });
384
378
  });
385
379
  }
@@ -391,10 +385,10 @@ function extractFinalAnswerFromHistory(historyJson) {
391
385
  const history = JSON.parse(historyJson);
392
386
  for (let i = history.length - 1; i >= 0; i--) {
393
387
  const entry = history[i];
394
- if (entry.role !== "assistant")
388
+ if (entry.role !== 'assistant')
395
389
  continue;
396
- const content = typeof entry.content === "string" ? entry.content : "";
397
- const fa = extractTagged(content, "final_answer");
390
+ const content = typeof entry.content === 'string' ? entry.content : '';
391
+ const fa = extractTagged(content, 'final_answer');
398
392
  if (fa)
399
393
  return fa;
400
394
  }
@@ -9,23 +9,21 @@ exports.omnikeyWsUrl = omnikeyWsUrl;
9
9
  const fs_1 = __importDefault(require("fs"));
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const os_1 = __importDefault(require("os"));
12
- const DEFAULT_HOST = "127.0.0.1";
12
+ const DEFAULT_HOST = '127.0.0.1';
13
13
  const DEFAULT_PORT = 7071;
14
- const DEFAULT_SQLITE = path_1.default.join(os_1.default.homedir(), ".omnikey", "omnikey-selfhosted.sqlite");
15
- const CONFIG_PATH = path_1.default.join(os_1.default.homedir(), ".omnikey", "config.json");
14
+ const DEFAULT_SQLITE = path_1.default.join(os_1.default.homedir(), '.omnikey', 'omnikey-selfhosted.sqlite');
15
+ const CONFIG_PATH = path_1.default.join(os_1.default.homedir(), '.omnikey', 'config.json');
16
16
  let cached = null;
17
17
  function resolveSqlitePath(raw) {
18
- if (typeof raw === "string" && raw.trim()) {
19
- return path_1.default.isAbsolute(raw)
20
- ? raw
21
- : path_1.default.join(os_1.default.homedir(), ".omnikey", raw);
18
+ if (typeof raw === 'string' && raw.trim()) {
19
+ return path_1.default.isAbsolute(raw) ? raw : path_1.default.join(os_1.default.homedir(), '.omnikey', raw);
22
20
  }
23
21
  return DEFAULT_SQLITE;
24
22
  }
25
23
  function resolvePort(raw) {
26
- if (typeof raw === "number" && Number.isFinite(raw))
24
+ if (typeof raw === 'number' && Number.isFinite(raw))
27
25
  return raw;
28
- if (typeof raw === "string" && raw.trim()) {
26
+ if (typeof raw === 'string' && raw.trim()) {
29
27
  const n = Number(raw);
30
28
  if (Number.isFinite(n))
31
29
  return n;
@@ -38,7 +36,7 @@ function loadOmnikeyConfig() {
38
36
  let parsed = {};
39
37
  try {
40
38
  if (fs_1.default.existsSync(CONFIG_PATH)) {
41
- const raw = fs_1.default.readFileSync(CONFIG_PATH, "utf-8");
39
+ const raw = fs_1.default.readFileSync(CONFIG_PATH, 'utf-8');
42
40
  parsed = JSON.parse(raw);
43
41
  }
44
42
  }
@@ -50,7 +48,7 @@ function loadOmnikeyConfig() {
50
48
  cached = {
51
49
  sqlitePath: resolveSqlitePath(parsed.SQLITE_PATH),
52
50
  omnikeyPort: resolvePort(parsed.OMNIKEY_PORT),
53
- omnikeyHost: typeof parsed.OMNIKEY_HOST === "string" && parsed.OMNIKEY_HOST.trim()
51
+ omnikeyHost: typeof parsed.OMNIKEY_HOST === 'string' && parsed.OMNIKEY_HOST.trim()
54
52
  ? parsed.OMNIKEY_HOST
55
53
  : DEFAULT_HOST,
56
54
  };
@@ -62,6 +60,6 @@ function omnikeyBaseUrl() {
62
60
  }
63
61
  function omnikeyWsUrl(path) {
64
62
  const { omnikeyHost, omnikeyPort } = loadOmnikeyConfig();
65
- const suffix = path.startsWith("/") ? path : `/${path}`;
63
+ const suffix = path.startsWith('/') ? path : `/${path}`;
66
64
  return `ws://${omnikeyHost}:${omnikeyPort}${suffix}`;
67
65
  }