@exreve/exk 1.0.23 → 1.0.25

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.
@@ -427,11 +427,15 @@ export class AgentSessionManager {
427
427
  const mcpServer = this.buildMcpServer(sessionId);
428
428
  if (mcpServer) {
429
429
  const toolHint = getModuleToolHint(session.enabledModules || []);
430
+ console.log(`[agentSession] MCP server created: name=${mcpServer.name}, hasHint=${!!toolHint}`);
431
+ console.log(`[agentSession] MCP server keys:`, Object.keys(mcpServer));
432
+ console.log(`[agentSession] MCP server type:`, mcpServer.type);
430
433
  return {
431
434
  mcpServers: { [mcpServer.name]: mcpServer },
432
435
  ...(toolHint ? { systemPrompt: { type: 'preset', preset: 'claude_code', append: toolHint } } : {})
433
436
  };
434
437
  }
438
+ console.log(`[agentSession] No MCP server created (enabledModules=${JSON.stringify(session.enabledModules)})`);
435
439
  return {};
436
440
  })(),
437
441
  ...(pathToClaudeCodeExecutable ? { pathToClaudeCodeExecutable } : {}),
package/dist/index.js CHANGED
@@ -16,7 +16,6 @@ import { startApp, stopApp, restartApp, getAppStatuses, getAppLogs } from './app
16
16
  import { spawn, execSync } from 'child_process';
17
17
  import readline from 'readline';
18
18
  import { fileURLToPath } from 'url';
19
- import { createHash } from 'crypto';
20
19
  // ============ Constants ============
21
20
  const CONFIG_DIR = path.join(os.homedir(), '.talk-to-code');
22
21
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
@@ -200,97 +199,24 @@ function getCliVersion() {
200
199
  return '0.0.0';
201
200
  }
202
201
  }
203
- const CURRENT_FILE = fileURLToPath(import.meta.url);
204
- const __dirname = path.dirname(CURRENT_FILE);
205
- function getCliHash() {
206
- return createHash('sha256').update(fsSync.readFileSync(CURRENT_FILE)).digest('hex');
207
- }
208
- async function checkForUpdate() {
202
+ // ============ Update Helpers ============
203
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
204
+ let isUpdating = false;
205
+ /** Check if a newer version is available on npm */
206
+ async function checkForNpmUpdate() {
209
207
  try {
210
- const config = await readConfig();
211
- const res = await fetch(`${config.apiUrl}/update/check`, {
212
- method: 'POST',
213
- headers: { 'Content-Type': 'application/json' },
214
- body: JSON.stringify({ hash: getCliHash(), platform: os.platform() })
215
- });
216
- if (!res.ok)
217
- return null;
218
- return await res.json();
208
+ const currentVersion = getCliVersion();
209
+ const latestVersion = execSync('npm view @exreve/exk version', { encoding: 'utf-8', timeout: 10000 }).trim();
210
+ return {
211
+ updateAvailable: currentVersion !== latestVersion,
212
+ currentVersion,
213
+ latestVersion
214
+ };
219
215
  }
220
216
  catch {
221
217
  return null;
222
218
  }
223
219
  }
224
- async function replaceSelf(tarballBuffer) {
225
- const extractDir = path.join(os.tmpdir(), `ttc-update-${Date.now()}`);
226
- await fs.mkdir(extractDir, { recursive: true });
227
- const tarPath = path.join(extractDir, 'ttc-cli.tar.gz');
228
- await fs.writeFile(tarPath, tarballBuffer);
229
- execSync(`tar -xzf "${tarPath}" -C "${extractDir}"`);
230
- await fs.unlink(tarPath);
231
- // Preserve user config/token files (never overwrite on update)
232
- const preserveFiles = ['device-config.json', 'device-id.json', 'config.json'];
233
- const preserved = {};
234
- for (const f of preserveFiles) {
235
- try {
236
- preserved[f] = await fs.readFile(path.join(CONFIG_DIR, f));
237
- }
238
- catch {
239
- /* file may not exist */
240
- }
241
- }
242
- // Replace cli/ and shared/ in CONFIG_DIR
243
- const cliDest = path.join(CONFIG_DIR, 'cli');
244
- const sharedDest = path.join(CONFIG_DIR, 'shared');
245
- await fs.rm(cliDest, { recursive: true, force: true });
246
- await fs.rm(sharedDest, { recursive: true, force: true });
247
- await fs.cp(path.join(extractDir, 'cli'), cliDest, { recursive: true });
248
- await fs.cp(path.join(extractDir, 'shared'), sharedDest, { recursive: true });
249
- // Update package.json and npm install
250
- await fs.copyFile(path.join(extractDir, 'package.json'), path.join(CONFIG_DIR, 'package.json'));
251
- await fs.rm(extractDir, { recursive: true, force: true });
252
- // Restore preserved config
253
- for (const f of preserveFiles) {
254
- if (preserved[f])
255
- await fs.writeFile(path.join(CONFIG_DIR, f), preserved[f]);
256
- }
257
- console.log('✓ CLI updated');
258
- const npmPaths = [path.join(os.homedir(), '.nvm/versions/node/v22/bin/npm'), path.join(os.homedir(), '.nvm/versions/node/v20/bin/npm')];
259
- const npmBin = npmPaths.find(p => fsSync.existsSync(p)) || 'npm';
260
- try {
261
- execSync(`"${npmBin}" install --omit=dev`, { cwd: CONFIG_DIR, stdio: 'inherit' });
262
- console.log('✓ Dependencies updated');
263
- }
264
- catch {
265
- console.warn('⚠ npm install failed');
266
- }
267
- }
268
- async function selfUpdate(force = false) {
269
- const info = await checkForUpdate();
270
- if (!info || !info.updateAvailable) {
271
- console.log('✓ Already up to date');
272
- return;
273
- }
274
- console.log('📦 Update available!');
275
- if (!force && process.stdin.isTTY) {
276
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
277
- const answer = await new Promise(r => rl.question('Update now? [Y/n] ', a => { rl.close(); r(a.trim()); }));
278
- if (answer.toLowerCase().startsWith('n'))
279
- return;
280
- }
281
- if (!info.downloadUrl || !info.hash)
282
- return;
283
- console.log('Downloading...');
284
- const res = await fetch(info.downloadUrl);
285
- if (!res.ok)
286
- throw new Error('Download failed');
287
- const bundle = Buffer.from(await res.arrayBuffer());
288
- if (createHash('sha256').update(bundle).digest('hex') !== info.hash) {
289
- throw new Error('Hash mismatch');
290
- }
291
- await replaceSelf(bundle);
292
- process.exit(0);
293
- }
294
220
  // ============ Commands ============
295
221
  async function registerDevice(name, email) {
296
222
  try {
@@ -590,9 +516,9 @@ async function runDaemon(foreground = false, email) {
590
516
  const ipAddress = getLocalIpAddress();
591
517
  const hostname = getHostname();
592
518
  // Silent update check on startup
593
- checkForUpdate().then(info => {
519
+ checkForNpmUpdate().then(info => {
594
520
  if (info?.updateAvailable)
595
- console.log('📦 Update available: run "ttc update"');
521
+ console.log(`📦 Update available: ${info.currentVersion} → ${info.latestVersion} (run "exk update")`);
596
522
  }).catch(() => { });
597
523
  if (foreground)
598
524
  console.log('=== TalkToCode CLI (Foreground) ===');
@@ -1716,10 +1642,13 @@ async function runDaemon(foreground = false, email) {
1716
1642
  // ========== Version & Update Handlers ==========
1717
1643
  // Respond with CLI version info
1718
1644
  socket.on('version:info', (_data, callback) => {
1645
+ if (isUpdating) {
1646
+ callback({ success: false, error: 'Update in progress' });
1647
+ return;
1648
+ }
1719
1649
  callback({
1720
1650
  success: true,
1721
1651
  version: getCliVersion(),
1722
- hash: getCliHash(),
1723
1652
  date: new Date().toISOString(),
1724
1653
  nodeVersion: process.version,
1725
1654
  platform: os.platform(),
@@ -1728,6 +1657,10 @@ async function runDaemon(foreground = false, email) {
1728
1657
  });
1729
1658
  // Force update: npm update -g @exreve/exk then restart PM2
1730
1659
  socket.on('force-update', (_data, callback) => {
1660
+ if (isUpdating) {
1661
+ callback?.({ success: false, error: 'Update already in progress' });
1662
+ return;
1663
+ }
1731
1664
  if (foreground) {
1732
1665
  console.log('[force-update] Received force update command from server');
1733
1666
  }
@@ -1768,33 +1701,6 @@ async function runDaemon(foreground = false, email) {
1768
1701
  }
1769
1702
  });
1770
1703
  });
1771
- // ========== update:start handler (legacy compatibility) ==========
1772
- socket.on('update:start', (_data, callback) => {
1773
- // Use npm-based self-update
1774
- if (foreground) {
1775
- console.log('[update:start] Starting npm self-update...');
1776
- }
1777
- callback?.({ success: true, message: 'Update started via npm' });
1778
- const npmPaths = [
1779
- path.join(os.homedir(), '.nvm/versions/node/v22/bin/npm'),
1780
- path.join(os.homedir(), '.nvm/versions/node/v20/bin/npm')
1781
- ];
1782
- const npmBin = npmPaths.find(p => fsSync.existsSync(p)) || 'npm';
1783
- const updateProcess = spawn(npmBin, ['update', '-g', '@exreve/exk'], {
1784
- stdio: 'pipe',
1785
- detached: true
1786
- });
1787
- updateProcess.on('close', () => {
1788
- setTimeout(() => {
1789
- try {
1790
- execSync('pm2 restart cli', { stdio: 'inherit' });
1791
- }
1792
- catch {
1793
- process.exit(0);
1794
- }
1795
- }, 2000);
1796
- });
1797
- });
1798
1704
  // Cloudflared handlers
1799
1705
  socket.on('cloudflared:check:request', async () => {
1800
1706
  try {
@@ -2305,7 +2211,7 @@ async function runDaemon(foreground = false, email) {
2305
2211
  const { name, image, ports = [], env = {}, runAsRoot = false } = data;
2306
2212
  // Get CLI directory path (where this script is running from)
2307
2213
  // Use the same pattern as elsewhere in the file
2308
- const cliDir = path.dirname(CURRENT_FILE);
2214
+ const cliDir = __dirname;
2309
2215
  // Build docker run command
2310
2216
  let cmd = `${runtime} run -d --name ${name}`;
2311
2217
  // Security: Running as root INSIDE container is safe - it's still isolated from host
@@ -15,7 +15,7 @@ function createUserChoiceTool(onChoiceRequest) {
15
15
  options: z.array(z.object({ label: z.string(), value: z.string() })),
16
16
  timeout: z.number().optional()
17
17
  };
18
- return tool('user_choice_request', 'Request user input when making decisions. Use this when you need the user to choose between options or provide input on a decision. This tool will present a modal to the user with your question and wait for their response.', schema, async (args, _extra) => {
18
+ return tool('user_choice_request', 'Present a choice modal to the user and wait for their selection. Use this whenever you need a decision, confirmation, or preference from the user. The AskUserQuestion tool is disabled this is your only way to get interactive user input. Always use this before proceeding with ambiguous tasks or destructive actions.', schema, async (args, _extra) => {
19
19
  if (!onChoiceRequest) {
20
20
  return {
21
21
  content: [
@@ -101,10 +101,15 @@ export function getModuleToolHint(enabledModules) {
101
101
  return null;
102
102
  const toolDescriptions = [];
103
103
  if (enabledModules.includes('user-choice')) {
104
- toolDescriptions.push('- user_choice_request: Request user input when making decisions. Presents a modal to the user with options and waits for their response. Use this when you need the user to choose between options or confirm an action.');
104
+ toolDescriptions.push(`- user_choice_request(question: str, options: [{label: str, value: str}], timeout?: int): Present a modal to the remote user with a question and clickable options. Returns the user's selection.
105
+ IMPORTANT: The built-in AskUserQuestion tool is DISABLED. Whenever you need user input, decisions, or confirmations, you MUST use user_choice_request instead.
106
+ Always call this tool when:
107
+ - There are multiple valid approaches and you need the user to pick one
108
+ - You need explicit confirmation before a destructive or significant action
109
+ - The user's preference matters for how to proceed`);
105
110
  }
106
111
  // Add more module tool descriptions here as they are implemented
107
112
  if (toolDescriptions.length === 0)
108
113
  return null;
109
- return `You have access to the following custom MCP tools:\n${toolDescriptions.join('\n')}\n\nUse these tools proactively when they are relevant to the task.`;
114
+ return `You have access to the following custom MCP tools:\n${toolDescriptions.join('\n')}\n\nThese tools are your ONLY way to interact with the user (AskUserQuestion is disabled). Call them directly — do NOT ask the user to type responses in chat.`;
110
115
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exreve/exk",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "exk - Control Claude CLI with voice and programmable interfaces",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  "container-entrypoint.sh"
15
15
  ],
16
16
  "scripts": {
17
- "build": "tsc && node build-cli-tarball.js",
17
+ "build": "tsc",
18
18
  "build:tsc": "tsc",
19
19
  "typecheck": "tsc --noEmit",
20
20
  "prepublishOnly": "node -e \"require('fs').chmodSync('bin/exk', 0o755)\""