omnikey-cli 1.5.7 → 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.7",
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'],
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.AgentAbortError = void 0;
7
+ exports.getSessionMessages = getSessionMessages;
7
8
  exports.listRecentSessions = listRecentSessions;
8
9
  exports.listTaskTemplates = listTaskTemplates;
9
10
  exports.setDefaultTaskTemplate = setDefaultTaskTemplate;
@@ -18,6 +19,27 @@ const path_1 = __importDefault(require("path"));
18
19
  const crypto_1 = require("crypto");
19
20
  const config_1 = require("./config");
20
21
  const omnikeyAuth_1 = require("./omnikeyAuth");
22
+ /**
23
+ * Fetch the typed message transcript for a session.
24
+ * Returns null when the session does not exist (404), throws on other errors.
25
+ */
26
+ async function getSessionMessages(logger, sessionId) {
27
+ const token = await (0, omnikeyAuth_1.fetchJwtToken)(logger);
28
+ const url = `${(0, config_1.omnikeyBaseUrl)()}/api/agent/sessions/${encodeURIComponent(sessionId)}/messages`;
29
+ try {
30
+ const resp = await axios_1.default.get(url, {
31
+ timeout: 10000,
32
+ headers: { Authorization: `Bearer ${token}` },
33
+ });
34
+ return resp.data?.messages ?? [];
35
+ }
36
+ catch (err) {
37
+ if (axios_1.default.isAxiosError(err) && err.response?.status === 404) {
38
+ return null;
39
+ }
40
+ throw err;
41
+ }
42
+ }
21
43
  async function listRecentSessions(logger, limit = 5) {
22
44
  const token = await (0, omnikeyAuth_1.fetchJwtToken)(logger);
23
45
  const url = `${(0, config_1.omnikeyBaseUrl)()}/api/agent/sessions`;
@@ -48,7 +70,7 @@ async function setDefaultTaskTemplate(logger, templateId) {
48
70
  timeout: 10000,
49
71
  headers: { Authorization: `Bearer ${token}` },
50
72
  });
51
- logger.info("Set default task template", { templateId });
73
+ logger.info('Set default task template', { templateId });
52
74
  }
53
75
  async function listProjectGroups(logger) {
54
76
  const token = await (0, omnikeyAuth_1.fetchJwtToken)(logger);
@@ -60,67 +82,63 @@ async function listProjectGroups(logger) {
60
82
  return resp.data?.groups ?? [];
61
83
  }
62
84
  class AgentAbortError extends Error {
63
- constructor(message = "Agent run aborted") {
85
+ constructor(message = 'Agent run aborted') {
64
86
  super(message);
65
- this.name = "AgentAbortError";
87
+ this.name = 'AgentAbortError';
66
88
  }
67
89
  }
68
90
  exports.AgentAbortError = AgentAbortError;
69
91
  function extractTagged(content, tag) {
70
- const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "i");
92
+ const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'i');
71
93
  const m = content.match(re);
72
94
  return m?.[1]?.trim() || null;
73
95
  }
74
96
  function stripTagged(content, tag) {
75
- return content.replace(new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`, "gi"), "");
97
+ return content.replace(new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`, 'gi'), '');
76
98
  }
77
99
  function cleanReasoning(content) {
78
100
  return content
79
- .replace(/<\/?shell_function_calls>/gi, "")
80
- .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')
81
103
  .trim();
82
104
  }
83
105
  const SHELL_TIMEOUT_MS = 5 * 60 * 1000;
84
106
  const SHELL_OUTPUT_MAX = 64 * 1024;
85
107
  // Mirrors WINDOWS_SHELL_CANDIDATES in src/agent/mcpRuntime.ts
86
108
  const WINDOWS_SHELL_CANDIDATES = [
87
- "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
88
- "C:\\Program Files\\PowerShell\\6\\pwsh.exe",
89
- "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
90
- "C:\\Windows\\System32\\cmd.exe",
91
- "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',
92
114
  ];
93
115
  // Resolve the Windows shell: COMSPEC → SystemRoot\System32\cmd.exe → candidate list.
94
116
  // Mirrors resolveLoginShell() in src/agent/mcpRuntime.ts, with SystemRoot used to
95
117
  // locate cmd.exe from the Win32 system root rather than a hardcoded drive letter.
96
118
  function resolveWindowsShell() {
97
- const comspec = process.env.COMSPEC ?? "";
119
+ const comspec = process.env.COMSPEC ?? '';
98
120
  if (comspec && (0, fs_1.existsSync)(comspec))
99
121
  return comspec;
100
- const systemRoot = process.env.SystemRoot ?? "C:\\Windows";
101
- 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');
102
124
  if ((0, fs_1.existsSync)(cmdFromRoot))
103
125
  return cmdFromRoot;
104
126
  for (const candidate of WINDOWS_SHELL_CANDIDATES) {
105
127
  if ((0, fs_1.existsSync)(candidate))
106
128
  return candidate;
107
129
  }
108
- return "cmd.exe";
130
+ return 'cmd.exe';
109
131
  }
110
132
  // Build shell args for the resolved shell — mirrors wrapWithLoginShell() in
111
133
  // src/agent/mcpRuntime.ts. PowerShell/pwsh use -NoProfile -Command; cmd uses /c.
112
134
  function buildWindowsShellArgs(shell, script) {
113
135
  const name = path_1.default.basename(shell).toLowerCase();
114
- if (name === "pwsh.exe" || name === "powershell.exe") {
115
- return ["-NoProfile", "-Command", script];
136
+ if (name === 'pwsh.exe' || name === 'powershell.exe') {
137
+ return ['-NoProfile', '-Command', script];
116
138
  }
117
- return ["/c", script];
139
+ return ['/c', script];
118
140
  }
119
- const PLATFORM = process.platform === "win32"
120
- ? "windows"
121
- : process.platform === "darwin"
122
- ? "macos"
123
- : "linux";
141
+ const PLATFORM = process.platform === 'win32' ? 'windows' : process.platform === 'darwin' ? 'macos' : 'linux';
124
142
  /**
125
143
  * Execute a shell script locally and capture combined stdout+stderr.
126
144
  * On macOS/Linux: invoke the login shell with `-l -c <script>` (mirrors the
@@ -134,15 +152,15 @@ function runShellScript(script, logger) {
134
152
  return new Promise((resolve) => {
135
153
  let shell;
136
154
  let shellArgs;
137
- if (process.platform !== "darwin" && process.platform === "win32") {
155
+ if (process.platform !== 'darwin' && process.platform === 'win32') {
138
156
  shell = resolveWindowsShell();
139
157
  shellArgs = buildWindowsShellArgs(shell, script);
140
158
  }
141
159
  else {
142
- shell = process.env.SHELL || "/bin/zsh";
143
- shellArgs = ["-l", "-c", script];
160
+ shell = process.env.SHELL || '/bin/zsh';
161
+ shellArgs = ['-l', '-c', script];
144
162
  }
145
- logger.info("Executing shell script from agent", {
163
+ logger.info('Executing shell script from agent', {
146
164
  shell,
147
165
  platform: PLATFORM,
148
166
  length: script.length,
@@ -151,7 +169,7 @@ function runShellScript(script, logger) {
151
169
  cwd: process.env.HOME ?? process.env.USERPROFILE ?? process.cwd(),
152
170
  env: process.env,
153
171
  });
154
- let buf = "";
172
+ let buf = '';
155
173
  let truncated = false;
156
174
  const append = (chunk) => {
157
175
  if (truncated)
@@ -161,7 +179,7 @@ function runShellScript(script, logger) {
161
179
  truncated = true;
162
180
  return;
163
181
  }
164
- const text = chunk.toString("utf8");
182
+ const text = chunk.toString('utf8');
165
183
  if (text.length <= room) {
166
184
  buf += text;
167
185
  }
@@ -170,33 +188,31 @@ function runShellScript(script, logger) {
170
188
  truncated = true;
171
189
  }
172
190
  };
173
- child.stdout.on("data", append);
174
- child.stderr.on("data", append);
191
+ child.stdout.on('data', append);
192
+ child.stderr.on('data', append);
175
193
  const timeout = setTimeout(() => {
176
- logger.warn("Shell script timed out; sending SIGTERM", {
194
+ logger.warn('Shell script timed out; sending SIGTERM', {
177
195
  timeoutMs: SHELL_TIMEOUT_MS,
178
196
  });
179
197
  try {
180
- child.kill("SIGTERM");
198
+ child.kill('SIGTERM');
181
199
  }
182
200
  catch {
183
201
  /* noop */
184
202
  }
185
203
  }, SHELL_TIMEOUT_MS);
186
- child.on("error", (err) => {
204
+ child.on('error', (err) => {
187
205
  clearTimeout(timeout);
188
206
  resolve({
189
207
  output: `${buf}\n[shell spawn error: ${err.message}]`,
190
208
  status: -1,
191
209
  });
192
210
  });
193
- child.on("close", (code, signal) => {
211
+ child.on('close', (code, signal) => {
194
212
  clearTimeout(timeout);
195
- const status = typeof code === "number" ? code : signal ? 1 : 0;
196
- const finalOutput = truncated
197
- ? `${buf}\n... [truncated to ${SHELL_OUTPUT_MAX} bytes]`
198
- : buf;
199
- 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', {
200
216
  status,
201
217
  signal,
202
218
  outputLength: finalOutput.length,
@@ -217,7 +233,7 @@ function runShellScript(script, logger) {
217
233
  async function runAgentTurn(logger, opts) {
218
234
  const token = await (0, omnikeyAuth_1.fetchJwtToken)(logger);
219
235
  const sessionId = opts.sessionId || (0, crypto_1.randomUUID)();
220
- const url = (0, config_1.omnikeyWsUrl)("/ws/omni-agent");
236
+ const url = (0, config_1.omnikeyWsUrl)('/ws/omni-agent');
221
237
  return new Promise((resolve, reject) => {
222
238
  if (opts.signal?.aborted) {
223
239
  reject(new AgentAbortError());
@@ -232,7 +248,7 @@ async function runAgentTurn(logger, opts) {
232
248
  return;
233
249
  settled = true;
234
250
  if (opts.signal && onAbort) {
235
- opts.signal.removeEventListener("abort", onAbort);
251
+ opts.signal.removeEventListener('abort', onAbort);
236
252
  }
237
253
  try {
238
254
  ws.close();
@@ -247,12 +263,12 @@ async function runAgentTurn(logger, opts) {
247
263
  };
248
264
  const onAbort = opts.signal
249
265
  ? () => {
250
- logger.info("Agent run aborted by caller", { sessionId });
266
+ logger.info('Agent run aborted by caller', { sessionId });
251
267
  finish(new AgentAbortError());
252
268
  }
253
269
  : null;
254
270
  if (opts.signal && onAbort) {
255
- opts.signal.addEventListener("abort", onAbort, { once: true });
271
+ opts.signal.addEventListener('abort', onAbort, { once: true });
256
272
  }
257
273
  const send = (msg) => {
258
274
  ws.send(JSON.stringify(msg), (err) => {
@@ -260,11 +276,11 @@ async function runAgentTurn(logger, opts) {
260
276
  finish(err);
261
277
  });
262
278
  };
263
- ws.on("open", () => {
264
- logger.info("Agent WebSocket open", { sessionId });
279
+ ws.on('open', () => {
280
+ logger.info('Agent WebSocket open', { sessionId });
265
281
  send({
266
282
  session_id: sessionId,
267
- sender: "client",
283
+ sender: 'client',
268
284
  content: opts.prompt,
269
285
  is_terminal_output: false,
270
286
  is_error: false,
@@ -272,56 +288,56 @@ async function runAgentTurn(logger, opts) {
272
288
  group_name: opts.groupName,
273
289
  });
274
290
  });
275
- ws.on("message", async (data) => {
291
+ ws.on('message', async (data) => {
276
292
  let msg;
277
293
  try {
278
294
  msg = JSON.parse(data.toString());
279
295
  }
280
296
  catch (e) {
281
- logger.warn("Failed to parse agent ws message", {
297
+ logger.warn('Failed to parse agent ws message', {
282
298
  error: e.message,
283
299
  });
284
300
  return;
285
301
  }
286
- const content = msg.content || "";
302
+ const content = msg.content || '';
287
303
  if (msg.is_error) {
288
- finish(new Error(content || "Agent reported an error"));
304
+ finish(new Error(content || 'Agent reported an error'));
289
305
  return;
290
306
  }
291
307
  if (msg.is_web_call) {
292
- await opts.onBlock({ kind: "webCall", text: content });
308
+ await opts.onBlock({ kind: 'webCall', text: content });
293
309
  return;
294
310
  }
295
311
  if (msg.is_image_rendering) {
296
- await opts.onBlock({ kind: "imageRendering", text: content });
312
+ await opts.onBlock({ kind: 'imageRendering', text: content });
297
313
  return;
298
314
  }
299
315
  if (msg.is_mcp_call) {
300
- await opts.onBlock({ kind: "mcpCall", text: content });
316
+ await opts.onBlock({ kind: 'mcpCall', text: content });
301
317
  return;
302
318
  }
303
- const finalAnswer = extractTagged(content, "final_answer");
319
+ const finalAnswer = extractTagged(content, 'final_answer');
304
320
  if (finalAnswer) {
305
- await opts.onBlock({ kind: "finalAnswer", text: finalAnswer });
321
+ await opts.onBlock({ kind: 'finalAnswer', text: finalAnswer });
306
322
  finish(null, { sessionId, finalAnswer });
307
323
  return;
308
324
  }
309
- const shellScript = extractTagged(content, "shell_script");
325
+ const shellScript = extractTagged(content, 'shell_script');
310
326
  if (shellScript) {
311
- const reasoning = cleanReasoning(stripTagged(content, "shell_script"));
327
+ const reasoning = cleanReasoning(stripTagged(content, 'shell_script'));
312
328
  if (reasoning)
313
- await opts.onBlock({ kind: "reasoning", text: reasoning });
314
- await opts.onBlock({ kind: "shellCommand", text: shellScript });
329
+ await opts.onBlock({ kind: 'reasoning', text: reasoning });
330
+ await opts.onBlock({ kind: 'shellCommand', text: shellScript });
315
331
  try {
316
332
  const { output, status } = await runShellScript(shellScript, logger);
317
- const statusLabel = status === 0 ? "success" : `error (exit code: ${status})`;
333
+ const statusLabel = status === 0 ? 'success' : `error (exit code: ${status})`;
318
334
  await opts.onBlock({
319
- kind: "terminalOutput",
335
+ kind: 'terminalOutput',
320
336
  text: `[terminal ${statusLabel}]\n${output}`,
321
337
  });
322
338
  send({
323
339
  session_id: sessionId,
324
- sender: "client",
340
+ sender: 'client',
325
341
  content: output,
326
342
  is_terminal_output: true,
327
343
  is_error: status !== 0,
@@ -330,14 +346,14 @@ async function runAgentTurn(logger, opts) {
330
346
  }
331
347
  catch (err) {
332
348
  const message = err.message;
333
- logger.error("Shell execution failed", { error: message });
349
+ logger.error('Shell execution failed', { error: message });
334
350
  await opts.onBlock({
335
- kind: "terminalOutput",
351
+ kind: 'terminalOutput',
336
352
  text: `[terminal error]\n${message}`,
337
353
  });
338
354
  send({
339
355
  session_id: sessionId,
340
- sender: "client",
356
+ sender: 'client',
341
357
  content: `Failed to execute shell script: ${message}`,
342
358
  is_terminal_output: true,
343
359
  is_error: true,
@@ -348,16 +364,16 @@ async function runAgentTurn(logger, opts) {
348
364
  }
349
365
  const reasoning = cleanReasoning(content);
350
366
  if (reasoning) {
351
- await opts.onBlock({ kind: "reasoning", text: reasoning });
367
+ await opts.onBlock({ kind: 'reasoning', text: reasoning });
352
368
  }
353
369
  });
354
- ws.on("error", (err) => {
355
- logger.error("Agent WebSocket error", { error: err.message });
370
+ ws.on('error', (err) => {
371
+ logger.error('Agent WebSocket error', { error: err.message });
356
372
  finish(err);
357
373
  });
358
- ws.on("close", () => {
374
+ ws.on('close', () => {
359
375
  if (!settled)
360
- finish(new Error("Agent WebSocket closed before final answer"));
376
+ finish(new Error('Agent WebSocket closed before final answer'));
361
377
  });
362
378
  });
363
379
  }
@@ -369,10 +385,10 @@ function extractFinalAnswerFromHistory(historyJson) {
369
385
  const history = JSON.parse(historyJson);
370
386
  for (let i = history.length - 1; i >= 0; i--) {
371
387
  const entry = history[i];
372
- if (entry.role !== "assistant")
388
+ if (entry.role !== 'assistant')
373
389
  continue;
374
- const content = typeof entry.content === "string" ? entry.content : "";
375
- const fa = extractTagged(content, "final_answer");
390
+ const content = typeof entry.content === 'string' ? entry.content : '';
391
+ const fa = extractTagged(content, 'final_answer');
376
392
  if (fa)
377
393
  return fa;
378
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
  }