@exreve/exk 1.0.39 → 1.0.40

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.
@@ -197,6 +197,18 @@ function loadAiConfig() {
197
197
  export function getOpenrouterApiKey() {
198
198
  return loadAiConfig().openrouterApiKey || process.env.OPENROUTER_API_KEY || '';
199
199
  }
200
+ /** Get the backend API URL from config file */
201
+ export function getApiUrl() {
202
+ const configPath = path.join(os.homedir(), '.talk-to-code', 'config.json');
203
+ try {
204
+ const data = readFileSync(configPath, 'utf-8');
205
+ const config = JSON.parse(data);
206
+ return config.apiUrl || 'https://api.talk-to-code.com';
207
+ }
208
+ catch {
209
+ return process.env.API_URL || 'https://api.talk-to-code.com';
210
+ }
211
+ }
200
212
  /** Create (or reuse) an empty directory to use as CLAUDE_CONFIG_DIR.
201
213
  * Setting this prevents the spawned Claude CLI from reading ~/.claude/settings.json,
202
214
  * which may contain env.ANTHROPIC_BASE_URL pointing to z.ai and would override our
@@ -1244,6 +1256,7 @@ export class AgentSessionManager {
1244
1256
  console.log(`[buildMcpServer] Session ${sessionId}: attachmentDir=${attachmentDir || 'none'}`);
1245
1257
  return createModuleMcpServer({
1246
1258
  attachmentDir,
1259
+ sessionId,
1247
1260
  });
1248
1261
  }
1249
1262
  }
@@ -9,7 +9,7 @@ import { z } from 'zod';
9
9
  import * as fs from 'fs';
10
10
  import * as path from 'path';
11
11
  import * as os from 'os';
12
- import { getOpenrouterApiKey } from './agentSession.js';
12
+ import { getOpenrouterApiKey, getApiUrl } from './agentSession.js';
13
13
  const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
14
14
  /** Comprehensive MIME type map for file extension detection */
15
15
  const MIME_MAP = {
@@ -175,6 +175,92 @@ function createSendFileTool(attachmentDir) {
175
175
  }
176
176
  });
177
177
  }
178
+ /**
179
+ * Create the browser_query tool for web automation via the backend.
180
+ * The agent can fire multiple queries concurrently — screenshots stream
181
+ * to the frontend independently while the tool blocks until completion.
182
+ */
183
+ function createBrowserQueryTool(config) {
184
+ return tool('browser_query', 'Launch a headless browser to automate web tasks such as searching, reading pages, filling forms, extracting data, etc. ' +
185
+ 'Returns the answer, optionally structured data, and step count. ' +
186
+ 'IMPORTANT: This tool is slow (30-120 seconds per query). You CAN and SHOULD call browser_query multiple times concurrently ' +
187
+ '— the browser handles each in a separate session. While waiting for results, continue with other work (file edits, analysis, etc.). ' +
188
+ 'Do NOT wait for one browser query to finish before starting another if you need multiple lookups.', {
189
+ query: z.string().describe('Natural language task for the browser agent (e.g. "Go to google.com and search for the price of Bitcoin")'),
190
+ schema: z.string().optional().describe('JSON schema for structured output, as a JSON string (e.g. \'{"type":"object","properties":{"price":{"type":"number"}}}\')'),
191
+ maxSteps: z.number().optional().describe('Max automation steps, default 20. Use lower values for simple tasks.'),
192
+ country: z.string().optional().describe('2-letter country code for proxy and locale (e.g. "US", "GB", "DE"). Uses direct connection if omitted.'),
193
+ }, async (args) => {
194
+ const apiUrl = getApiUrl();
195
+ // Read device ID for CLI auth
196
+ let deviceId = '';
197
+ try {
198
+ const deviceIdPath = path.join(os.homedir(), '.talk-to-code', 'device-id.json');
199
+ const data = fs.readFileSync(deviceIdPath, 'utf-8');
200
+ deviceId = JSON.parse(data).deviceId || '';
201
+ }
202
+ catch {
203
+ // No device ID file — will still work if backend has relaxed auth
204
+ }
205
+ try {
206
+ const body = {
207
+ query: args.query,
208
+ maxSteps: args.maxSteps || 20,
209
+ };
210
+ if (args.schema) {
211
+ try {
212
+ body.schema = JSON.parse(args.schema);
213
+ }
214
+ catch {
215
+ body.schema = args.schema;
216
+ }
217
+ }
218
+ if (args.country)
219
+ body.country = args.country;
220
+ if (config.sessionId)
221
+ body.sessionId = config.sessionId;
222
+ if (config.promptId)
223
+ body.promptId = config.promptId;
224
+ const res = await fetch(`${apiUrl}/api/browser/query`, {
225
+ method: 'POST',
226
+ headers: {
227
+ 'Content-Type': 'application/json',
228
+ ...(deviceId ? { 'X-Device-ID': deviceId } : {}),
229
+ },
230
+ body: JSON.stringify(body),
231
+ signal: AbortSignal.timeout(10 * 60 * 1000), // 10 min timeout
232
+ });
233
+ const raw = await res.text();
234
+ if (!res.ok) {
235
+ return {
236
+ content: [{ type: 'text', text: `Error from browser agent (${res.status}): ${raw.slice(0, 500)}` }],
237
+ isError: true,
238
+ };
239
+ }
240
+ const result = JSON.parse(raw);
241
+ // Format a nice summary for the agent
242
+ const summary = [
243
+ `Browser query completed in ${result.steps} steps.`,
244
+ result.answer ? `\n\n**Answer:** ${result.answer}` : '',
245
+ result.data ? `\n\n**Structured Data:**\n\`\`\`json\n${JSON.stringify(result.data, null, 2)}\n\`\`\`` : '',
246
+ result.logs?.length ? `\n\n**Log:**\n${result.logs.slice(-5).join('\n')}` : '',
247
+ ].join('');
248
+ return { content: [{ type: 'text', text: summary }] };
249
+ }
250
+ catch (error) {
251
+ if (error.name === 'TimeoutError') {
252
+ return {
253
+ content: [{ type: 'text', text: 'Browser query timed out after 10 minutes. Try reducing maxSteps or simplifying the query.' }],
254
+ isError: true,
255
+ };
256
+ }
257
+ return {
258
+ content: [{ type: 'text', text: `Error running browser query: ${error.message}` }],
259
+ isError: true,
260
+ };
261
+ }
262
+ });
263
+ }
178
264
  /**
179
265
  * Create the MCP server with built-in tools
180
266
  */
@@ -184,6 +270,8 @@ export function createModuleMcpServer(config) {
184
270
  tools.push(createAnalyzeImageTool(config.attachmentDir));
185
271
  // Add send_file tool for displaying files to the user in chat
186
272
  tools.push(createSendFileTool(config.attachmentDir));
273
+ // Add browser_query tool for web automation
274
+ tools.push(createBrowserQueryTool(config));
187
275
  const server = createSdkMcpServer({
188
276
  name: 'claude-voice-modules',
189
277
  version: '1.0.0',
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exreve/exk",
3
- "version": "1.0.39",
3
+ "version": "1.0.40",
4
4
  "description": "exk - Control Claude CLI with voice and programmable interfaces",
5
5
  "type": "module",
6
6
  "bin": {