@exreve/exk 1.0.43 → 1.0.45

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.
@@ -135,6 +135,9 @@ function lookupToolNameFromHistory(messages, toolUseId) {
135
135
  // (Do not read ANTHROPIC_* / CLAUDE_MODEL from the host environment — only this file + code default model.)
136
136
  const AI_CONFIG_PATH = path.join(os.homedir(), '.talk-to-code', 'ai-config.json');
137
137
  const DEFAULT_AI_MODEL = 'glm-5.1';
138
+ /** TTL cache for ai-config.json reads to avoid hitting disk on every call */
139
+ let _aiConfigCache = null;
140
+ const AI_CONFIG_TTL_MS = 5_000;
138
141
  const PROVIDERS = {
139
142
  zai: {
140
143
  apiKey: process.env.ZHIPU_API_KEY || '',
@@ -149,7 +152,7 @@ const PROVIDERS = {
149
152
  openrouter: {
150
153
  apiKey: '', // Populated from ai-config.json openrouterApiKey (served by backend)
151
154
  baseUrl: 'https://openrouter.ai/api',
152
- models: ['gpt-oss-120b:grok'],
155
+ models: ['gpt-oss-120b:cerebras'],
153
156
  },
154
157
  };
155
158
  /** Resolve which provider to use based on model name or explicit provider ID.
@@ -184,6 +187,10 @@ function resolveProvider(model, providerId) {
184
187
  };
185
188
  }
186
189
  function loadAiConfig() {
190
+ const now = Date.now();
191
+ if (_aiConfigCache && (now - _aiConfigCache.ts) < AI_CONFIG_TTL_MS) {
192
+ return _aiConfigCache.data;
193
+ }
187
194
  try {
188
195
  const data = readFileSync(AI_CONFIG_PATH, 'utf-8');
189
196
  const config = JSON.parse(data);
@@ -193,10 +200,14 @@ function loadAiConfig() {
193
200
  const proxy = typeof config.proxy === 'string' ? config.proxy.trim() : '';
194
201
  const minimaxApiKey = typeof config.minimaxApiKey === 'string' ? config.minimaxApiKey.trim() : '';
195
202
  const openrouterApiKey = typeof config.openrouterApiKey === 'string' ? config.openrouterApiKey.trim() : '';
196
- return { apiKey, baseUrl, model, proxy, minimaxApiKey, openrouterApiKey };
203
+ const result = { apiKey, baseUrl, model, proxy, minimaxApiKey, openrouterApiKey };
204
+ _aiConfigCache = { data: result, ts: now };
205
+ return result;
197
206
  }
198
207
  catch {
199
- return { apiKey: '', baseUrl: '', model: DEFAULT_AI_MODEL, proxy: '', minimaxApiKey: '', openrouterApiKey: '' };
208
+ const fallback = { apiKey: '', baseUrl: '', model: DEFAULT_AI_MODEL, proxy: '', minimaxApiKey: '', openrouterApiKey: '' };
209
+ _aiConfigCache = { data: fallback, ts: now };
210
+ return fallback;
200
211
  }
201
212
  }
202
213
  /** Get OpenRouter API key from ai-config.json (served by backend) */
package/dist/app-child.js CHANGED
@@ -104,9 +104,27 @@ async function fetchAiConfig(authToken) {
104
104
  return false;
105
105
  }
106
106
  }
107
- function hasAiCredentials() {
107
+ /** TTL cache for ai-config.json reads */
108
+ let _aiCfgCache = null;
109
+ const _AI_CFG_TTL = 5_000;
110
+ function readAiConfigCached() {
111
+ const now = Date.now();
112
+ if (_aiCfgCache && (now - _aiCfgCache.ts) < _AI_CFG_TTL)
113
+ return _aiCfgCache.raw;
108
114
  try {
109
115
  const raw = fsSync.readFileSync(AI_CONFIG_FILE, 'utf-8');
116
+ _aiCfgCache = { raw, ts: now };
117
+ return raw;
118
+ }
119
+ catch {
120
+ return '';
121
+ }
122
+ }
123
+ function hasAiCredentials() {
124
+ try {
125
+ const raw = readAiConfigCached();
126
+ if (!raw)
127
+ return false;
110
128
  const j = JSON.parse(raw);
111
129
  return typeof j.authToken === 'string' && j.authToken.trim().length > 0;
112
130
  }
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Benchmark: cold query() vs warm startup() + query()
3
+ *
4
+ * Measures time-to-first-token for:
5
+ * 1. Cold query() (current behavior - spawns subprocess each time)
6
+ * 2. Warm startup() -> query() (pre-warmed subprocess)
7
+ *
8
+ * Usage: cd cli && npx tsx benchmark-startup.ts
9
+ */
10
+ import { query, startup } from '@anthropic-ai/claude-agent-sdk';
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import os from 'os';
14
+ import { execSync } from 'child_process';
15
+ import { createRequire } from 'module';
16
+ // Resolve Claude Code executable (mirrors agentSession.ts logic)
17
+ function resolveClaudeExe() {
18
+ // Try glibc native binary first (most Linux distros)
19
+ for (const pkg of [
20
+ '@anthropic-ai/claude-agent-sdk-linux-x64',
21
+ '@anthropic-ai/claude-agent-sdk-linux-x64-musl',
22
+ ]) {
23
+ try {
24
+ const req = createRequire(import.meta.url);
25
+ const nativePkgPath = req.resolve(`${pkg}/package.json`);
26
+ const nativePath = path.join(path.dirname(nativePkgPath), 'claude');
27
+ if (fs.existsSync(nativePath)) {
28
+ // Test if it's actually executable
29
+ try {
30
+ execSync(`${nativePath} --version`, { stdio: 'pipe', timeout: 5000 });
31
+ return nativePath;
32
+ }
33
+ catch {
34
+ // Binary can't execute (e.g. musl on glibc), skip
35
+ }
36
+ }
37
+ }
38
+ catch { }
39
+ }
40
+ // Fallback: cli.js (Node.js-based)
41
+ try {
42
+ const req = createRequire(import.meta.url);
43
+ const pkgPath = req.resolve('@anthropic-ai/claude-agent-sdk/package.json');
44
+ const cliPath = path.join(path.dirname(pkgPath), 'cli.js');
45
+ if (fs.existsSync(cliPath))
46
+ return cliPath;
47
+ }
48
+ catch { }
49
+ return undefined;
50
+ }
51
+ const CLAUDE_EXE = resolveClaudeExe();
52
+ // ── Config ──────────────────────────────────────────────────────────
53
+ const AI_CONFIG_PATH = path.join(os.homedir(), '.talk-to-code', 'ai-config.json');
54
+ function loadConfig() {
55
+ try {
56
+ return JSON.parse(fs.readFileSync(AI_CONFIG_PATH, 'utf-8'));
57
+ }
58
+ catch {
59
+ return {};
60
+ }
61
+ }
62
+ // ── Helpers ─────────────────────────────────────────────────────────
63
+ const GREEN = '\x1b[32m';
64
+ const RED = '\x1b[31m';
65
+ const CYAN = '\x1b[36m';
66
+ const YELLOW = '\x1b[33m';
67
+ const BOLD = '\x1b[1m';
68
+ const RESET = '\x1b[0m';
69
+ function hr() {
70
+ console.log(`${'─'.repeat(60)}`);
71
+ }
72
+ async function measureColdQuery(apiKey, baseUrl, model, env, settingsEnv, label) {
73
+ const queryStart = Date.now();
74
+ let firstTokenMs = 0;
75
+ let initMs = 0;
76
+ let totalMs = 0;
77
+ try {
78
+ const q = query({
79
+ prompt: 'Say exactly: "Hello, I am ready." Nothing else.',
80
+ options: {
81
+ apiKey,
82
+ model,
83
+ cwd: '/tmp',
84
+ permissionMode: 'bypassPermissions',
85
+ allowDangerouslySkipPermissions: true,
86
+ maxTurns: 1,
87
+ env,
88
+ settings: { env: settingsEnv },
89
+ ...(CLAUDE_EXE ? { pathToClaudeCodeExecutable: CLAUDE_EXE } : {}),
90
+ },
91
+ });
92
+ for await (const event of q) {
93
+ const now = Date.now();
94
+ if (event.type === 'system' && event.subtype === 'init') {
95
+ initMs = now - queryStart;
96
+ }
97
+ if (event.type === 'assistant') {
98
+ if (firstTokenMs === 0) {
99
+ firstTokenMs = now - queryStart;
100
+ }
101
+ }
102
+ if (event.type === 'result') {
103
+ totalMs = now - queryStart;
104
+ }
105
+ }
106
+ }
107
+ catch (err) {
108
+ return {
109
+ firstTokenMs: firstTokenMs || -1,
110
+ totalMs: Date.now() - queryStart,
111
+ startupMs: initMs || -1,
112
+ error: err.message,
113
+ };
114
+ }
115
+ console.log(` ${label}: init=${initMs}ms, first_token=${firstTokenMs}ms, total=${totalMs}ms`);
116
+ return { firstTokenMs, totalMs, startupMs: initMs };
117
+ }
118
+ async function measureWarmQuery(apiKey, baseUrl, model, env, settingsEnv, label) {
119
+ // Phase 1: Pre-warm
120
+ const prewarmStart = Date.now();
121
+ let warmQuery;
122
+ try {
123
+ warmQuery = await startup({
124
+ options: {
125
+ apiKey,
126
+ model,
127
+ cwd: '/tmp',
128
+ permissionMode: 'bypassPermissions',
129
+ allowDangerouslySkipPermissions: true,
130
+ maxTurns: 1,
131
+ env,
132
+ settings: { env: settingsEnv },
133
+ ...(CLAUDE_EXE ? { pathToClaudeCodeExecutable: CLAUDE_EXE } : {}),
134
+ },
135
+ });
136
+ }
137
+ catch (err) {
138
+ return {
139
+ prewarmMs: Date.now() - prewarmStart,
140
+ firstTokenMs: -1,
141
+ totalMs: Date.now() - prewarmStart,
142
+ error: `startup() failed: ${err.message}`,
143
+ };
144
+ }
145
+ const prewarmMs = Date.now() - prewarmStart;
146
+ console.log(` ${label} prewarm: ${prewarmMs}ms`);
147
+ // Phase 2: Query on warm subprocess
148
+ const queryStart = Date.now();
149
+ let firstTokenMs = 0;
150
+ let totalMs = 0;
151
+ try {
152
+ const q = warmQuery.query('Say exactly: "Hello, I am ready." Nothing else.');
153
+ for await (const event of q) {
154
+ const now = Date.now();
155
+ if (event.type === 'assistant') {
156
+ if (firstTokenMs === 0) {
157
+ firstTokenMs = now - queryStart;
158
+ }
159
+ }
160
+ if (event.type === 'result') {
161
+ totalMs = now - queryStart;
162
+ }
163
+ }
164
+ }
165
+ catch (err) {
166
+ return {
167
+ prewarmMs,
168
+ firstTokenMs: firstTokenMs || -1,
169
+ totalMs: Date.now() - queryStart,
170
+ error: `warm query() failed: ${err.message}`,
171
+ };
172
+ }
173
+ console.log(` ${label} query: first_token=${firstTokenMs}ms, total=${totalMs}ms`);
174
+ return { prewarmMs, firstTokenMs, totalMs };
175
+ }
176
+ // ── Main ────────────────────────────────────────────────────────────
177
+ async function main() {
178
+ console.log(`\n${BOLD}╔══════════════════════════════════════════════════════════╗${RESET}`);
179
+ console.log(`${BOLD}║ startup() Benchmark: Cold vs Warm Time-to-First-Token ║${RESET}`);
180
+ console.log(`${BOLD}╚══════════════════════════════════════════════════════════╝${RESET}\n`);
181
+ const config = loadConfig();
182
+ const minimaxKey = config.minimaxApiKey || process.env.MINIMAX_API_KEY || '';
183
+ if (!minimaxKey) {
184
+ console.log(`${RED}ERROR: No MiniMax API key found. Set minimaxApiKey in ai-config.json${RESET}`);
185
+ process.exit(1);
186
+ }
187
+ const MINIMAX_BASE_URL = 'https://api.minimax.io/anthropic';
188
+ const MINIMAX_MODEL = 'MiniMax-M2.7-highspeed';
189
+ const env = {
190
+ ...process.env,
191
+ ANTHROPIC_API_KEY: minimaxKey,
192
+ ANTHROPIC_BASE_URL: MINIMAX_BASE_URL,
193
+ ANTHROPIC_MODEL: MINIMAX_MODEL,
194
+ ANTHROPIC_DEFAULT_SONNET_MODEL: MINIMAX_MODEL,
195
+ ANTHROPIC_DEFAULT_OPUS_MODEL: MINIMAX_MODEL,
196
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: MINIMAX_MODEL,
197
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
198
+ };
199
+ const settingsEnv = {
200
+ ANTHROPIC_API_KEY: minimaxKey,
201
+ ANTHROPIC_BASE_URL: MINIMAX_BASE_URL,
202
+ ANTHROPIC_MODEL: MINIMAX_MODEL,
203
+ ANTHROPIC_DEFAULT_SONNET_MODEL: MINIMAX_MODEL,
204
+ ANTHROPIC_DEFAULT_OPUS_MODEL: MINIMAX_MODEL,
205
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: MINIMAX_MODEL,
206
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
207
+ };
208
+ console.log(`${CYAN}Provider:${RESET} MiniMax`);
209
+ console.log(`${CYAN}Model:${RESET} ${MINIMAX_MODEL}`);
210
+ console.log(`${CYAN}Base URL:${RESET} ${MINIMAX_BASE_URL}`);
211
+ console.log(`${CYAN}Key:${RESET} ${minimaxKey.slice(0, 8)}...`);
212
+ console.log(`${CYAN}Exe:${RESET} ${CLAUDE_EXE || '(not found)'}\n`);
213
+ if (!CLAUDE_EXE) {
214
+ console.log(`${RED}ERROR: Could not resolve Claude Code executable${RESET}`);
215
+ process.exit(1);
216
+ }
217
+ // ============== TEST 1: Cold Query ==============
218
+ console.log(`${BOLD}TEST 1: Cold query() — current behavior${RESET}`);
219
+ hr();
220
+ const coldResults = [];
221
+ for (let i = 0; i < 3; i++) {
222
+ const result = await measureColdQuery(minimaxKey, MINIMAX_BASE_URL, MINIMAX_MODEL, env, settingsEnv, ` Run ${i + 1}`);
223
+ coldResults.push(result);
224
+ if (result.error) {
225
+ console.log(` ${RED}Error: ${result.error}${RESET}`);
226
+ }
227
+ }
228
+ // ============== TEST 2: Warm startup() + query() ==============
229
+ console.log(`\n${BOLD}TEST 2: Warm startup() → query() — pre-warmed subprocess${RESET}`);
230
+ hr();
231
+ const warmResults = [];
232
+ for (let i = 0; i < 3; i++) {
233
+ const result = await measureWarmQuery(minimaxKey, MINIMAX_BASE_URL, MINIMAX_MODEL, env, settingsEnv, ` Run ${i + 1}`);
234
+ warmResults.push(result);
235
+ if (result.error) {
236
+ console.log(` ${RED}Error: ${result.error}${RESET}`);
237
+ }
238
+ }
239
+ // ============== Summary ==============
240
+ console.log(`\n${BOLD}══════════════════════════════════════════════════════════${RESET}`);
241
+ console.log(`${BOLD} SUMMARY${RESET}`);
242
+ console.log(`${BOLD}══════════════════════════════════════════════════════════${RESET}\n`);
243
+ const validCold = coldResults.filter(r => !r.error && r.firstTokenMs > 0);
244
+ const validWarm = warmResults.filter(r => !r.error && r.firstTokenMs > 0);
245
+ if (validCold.length > 0) {
246
+ const avgColdFirst = Math.round(validCold.reduce((s, r) => s + r.firstTokenMs, 0) / validCold.length);
247
+ const avgColdTotal = Math.round(validCold.reduce((s, r) => s + r.totalMs, 0) / validCold.length);
248
+ const avgColdInit = Math.round(validCold.reduce((s, r) => s + r.startupMs, 0) / validCold.length);
249
+ console.log(` ${YELLOW}Cold query() (avg of ${validCold.length}):${RESET}`);
250
+ console.log(` Init/subprocess spawn: ${avgColdInit}ms`);
251
+ console.log(` Time to first token: ${avgColdFirst}ms`);
252
+ console.log(` Total query time: ${avgColdTotal}ms`);
253
+ }
254
+ else {
255
+ console.log(` ${RED}Cold query: ALL RUNS FAILED${RESET}`);
256
+ coldResults.forEach((r, i) => r.error && console.log(` Run ${i + 1}: ${r.error}`));
257
+ }
258
+ console.log();
259
+ if (validWarm.length > 0) {
260
+ const avgWarmPrem = Math.round(validWarm.reduce((s, r) => s + r.prewarmMs, 0) / validWarm.length);
261
+ const avgWarmFirst = Math.round(validWarm.reduce((s, r) => s + r.firstTokenMs, 0) / validWarm.length);
262
+ const avgWarmTotal = Math.round(validWarm.reduce((s, r) => s + r.totalMs, 0) / validWarm.length);
263
+ console.log(` ${GREEN}Warm startup() → query() (avg of ${validWarm.length}):${RESET}`);
264
+ console.log(` Prewarm (startup()): ${avgWarmPrem}ms`);
265
+ console.log(` Time to first token: ${avgWarmFirst}ms`);
266
+ console.log(` Total query time: ${avgWarmTotal}ms`);
267
+ }
268
+ else {
269
+ console.log(` ${RED}Warm query: ALL RUNS FAILED${RESET}`);
270
+ warmResults.forEach((r, i) => r.error && console.log(` Run ${i + 1}: ${r.error}`));
271
+ }
272
+ console.log();
273
+ if (validCold.length > 0 && validWarm.length > 0) {
274
+ const avgColdFirst = Math.round(validCold.reduce((s, r) => s + r.firstTokenMs, 0) / validCold.length);
275
+ const avgWarmFirst = Math.round(validWarm.reduce((s, r) => s + r.firstTokenMs, 0) / validWarm.length);
276
+ const speedup = avgColdFirst > 0 ? (avgColdFirst / avgWarmFirst).toFixed(1) : 'N/A';
277
+ const saved = avgColdFirst - avgWarmFirst;
278
+ console.log(` ${BOLD}Speedup (first token):${RESET} ${speedup}x faster with startup()`);
279
+ console.log(` ${BOLD}Time saved:${RESET} ${saved > 0 ? saved : 0}ms per query`);
280
+ console.log();
281
+ if (parseFloat(speedup) >= 5) {
282
+ console.log(` ${GREEN}${BOLD}✓ Significant improvement! startup() is highly beneficial here.${RESET}`);
283
+ }
284
+ else if (parseFloat(speedup) >= 2) {
285
+ console.log(` ${YELLOW}! Moderate improvement. startup() helps but not 20x as advertised.${RESET}`);
286
+ }
287
+ else {
288
+ console.log(` ${RED}✗ Minimal improvement. startup() overhead may not be worth it here.${RESET}`);
289
+ }
290
+ }
291
+ console.log(`\n${BOLD}══════════════════════════════════════════════════════════${RESET}\n`);
292
+ // ============== Bonus: Can we reuse startup()? ==============
293
+ console.log(`${BOLD}TEST 3: Can one WarmQuery be reused for multiple queries?${RESET}`);
294
+ hr();
295
+ try {
296
+ const warmHandle = await startup({
297
+ options: {
298
+ apiKey: minimaxKey,
299
+ model: MINIMAX_MODEL,
300
+ cwd: '/tmp',
301
+ permissionMode: 'bypassPermissions',
302
+ allowDangerouslySkipPermissions: true,
303
+ maxTurns: 1,
304
+ env,
305
+ settings: { env: settingsEnv },
306
+ ...(CLAUDE_EXE ? { pathToClaudeCodeExecutable: CLAUDE_EXE } : {}),
307
+ },
308
+ });
309
+ console.log(' startup() succeeded, attempting first query()...');
310
+ const q1Start = Date.now();
311
+ let q1FirstToken = 0;
312
+ const q1 = warmHandle.query('Say "one"');
313
+ for await (const event of q1) {
314
+ if (event.type === 'assistant' && q1FirstToken === 0) {
315
+ q1FirstToken = Date.now() - q1Start;
316
+ }
317
+ if (event.type === 'result') {
318
+ console.log(` Query 1: first_token=${q1FirstToken}ms, total=${Date.now() - q1Start}ms`);
319
+ }
320
+ }
321
+ console.log(' Attempting second query() on same WarmQuery...');
322
+ try {
323
+ const q2 = warmHandle.query('Say "two"');
324
+ for await (const _event of q2) {
325
+ // drain
326
+ }
327
+ console.log(` ${GREEN}Second query succeeded — WarmQuery CAN be reused!${RESET}`);
328
+ }
329
+ catch (reuseErr) {
330
+ console.log(` ${RED}Second query FAILED: ${reuseErr.message}${RESET}`);
331
+ console.log(` ${YELLOW}→ WarmQuery is single-use, must call startup() again for each query.${RESET}`);
332
+ }
333
+ try {
334
+ warmHandle.close();
335
+ }
336
+ catch { }
337
+ }
338
+ catch (err) {
339
+ console.log(` ${RED}startup() failed: ${err.message}${RESET}`);
340
+ }
341
+ console.log();
342
+ process.exit(0);
343
+ }
344
+ main().catch((err) => {
345
+ console.error('Unhandled error:', err);
346
+ process.exit(1);
347
+ });
package/dist/index.js CHANGED
@@ -83,10 +83,28 @@ async function writeProxyConfig(cfg) {
83
83
  await fs.mkdir(CONFIG_DIR, { recursive: true });
84
84
  await fs.writeFile(PROXY_CONFIG_FILE, JSON.stringify(cfg, null, 2));
85
85
  }
86
+ /** TTL cache for ai-config.json reads (shared by getProxyUrl / hasAiCredentials) */
87
+ let _aiCfgCache = null;
88
+ const _AI_CFG_TTL = 5_000;
89
+ function readAiConfigCached() {
90
+ const now = Date.now();
91
+ if (_aiCfgCache && (now - _aiCfgCache.ts) < _AI_CFG_TTL)
92
+ return _aiCfgCache.raw;
93
+ try {
94
+ const raw = fsSync.readFileSync(AI_CONFIG_FILE, 'utf-8');
95
+ _aiCfgCache = { raw, ts: now };
96
+ return raw;
97
+ }
98
+ catch {
99
+ return '';
100
+ }
101
+ }
86
102
  /** Get the proxy URL from ai-config.json (saved from backend) */
87
103
  function getProxyUrl() {
88
104
  try {
89
- const raw = fsSync.readFileSync(AI_CONFIG_FILE, 'utf-8');
105
+ const raw = readAiConfigCached();
106
+ if (!raw)
107
+ return '';
90
108
  const j = JSON.parse(raw);
91
109
  return typeof j.proxy === 'string' ? j.proxy.trim() : '';
92
110
  }
@@ -97,7 +115,9 @@ function getProxyUrl() {
97
115
  /** True if ai-config.json has a model API key (not read from host ANTHROPIC_* env). */
98
116
  function hasAiCredentials() {
99
117
  try {
100
- const raw = fsSync.readFileSync(AI_CONFIG_FILE, 'utf-8');
118
+ const raw = readAiConfigCached();
119
+ if (!raw)
120
+ return false;
101
121
  const j = JSON.parse(raw);
102
122
  return typeof j.authToken === 'string' && j.authToken.trim().length > 0;
103
123
  }
@@ -190,6 +190,7 @@ function createBrowserQueryTool(config) {
190
190
  schema: z.string().optional().describe('JSON schema for structured output, as a JSON string (e.g. \'{"type":"object","properties":{"price":{"type":"number"}}}\')'),
191
191
  maxSteps: z.number().optional().describe('Max automation steps, default 20. Use lower values for simple tasks.'),
192
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
+ mobile: z.boolean().optional().describe('If true, use mobile viewport (390x844 — iPhone 14 dimensions) instead of desktop.'),
193
194
  }, async (args) => {
194
195
  const apiUrl = getApiUrl();
195
196
  // Read device ID for CLI auth
@@ -217,6 +218,8 @@ function createBrowserQueryTool(config) {
217
218
  }
218
219
  if (args.country)
219
220
  body.country = args.country;
221
+ if (args.mobile)
222
+ body.mobile = args.mobile;
220
223
  if (config.sessionId)
221
224
  body.sessionId = config.sessionId;
222
225
  if (config.promptId)
@@ -185,10 +185,17 @@ CRITICAL INSTRUCTIONS:
185
185
  }
186
186
  });
187
187
  // Wait for Claude to process (give it time to read files and generate response)
188
- // Check multiple times as Claude may take a while - wait up to 90 seconds
188
+ // Use exponential backoff: start at 500ms, double each time, cap at 5s, timeout at 90s
189
189
  const configFilePath = path.join(projectPath, CONFIG_FILENAME);
190
- for (let i = 0; i < 90; i++) {
191
- await new Promise(resolve => setTimeout(resolve, 1000));
190
+ const POLL_TIMEOUT_MS = 90_000;
191
+ const POLL_START_MS = 500;
192
+ const POLL_MAX_MS = 5_000;
193
+ let elapsed = 0;
194
+ let interval = POLL_START_MS;
195
+ while (elapsed < POLL_TIMEOUT_MS) {
196
+ await new Promise(resolve => setTimeout(resolve, interval));
197
+ elapsed += interval;
198
+ interval = Math.min(interval * 2, POLL_MAX_MS);
192
199
  // Check if Claude wrote the config file directly
193
200
  try {
194
201
  await fs.access(configFilePath);
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exreve/exk",
3
- "version": "1.0.43",
3
+ "version": "1.0.45",
4
4
  "description": "exk - Control Claude CLI with voice and programmable interfaces",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,8 +33,8 @@
33
33
  "url": ""
34
34
  },
35
35
  "dependencies": {
36
- "@anthropic-ai/claude-agent-sdk": "^0.2.87",
37
- "@anthropic-ai/sdk": "^0.80.0",
36
+ "@anthropic-ai/claude-agent-sdk": "^0.2.126",
37
+ "@anthropic-ai/sdk": "^0.92.0",
38
38
  "@fastify/static": "^9.0.0",
39
39
  "@xenova/transformers": "^2.17.2",
40
40
  "anthropic-proxy": "^1.3.0",