@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.
- package/dist/agentSession.js +4 -0
- package/dist/index.js +23 -117
- package/dist/moduleMcpServer.js +8 -3
- package/package.json +2 -2
- package/dist/app-child.js +0 -2589
- package/dist/ttc-cli.tar.gz +0 -0
- package/dist/updater.js +0 -425
package/dist/agentSession.js
CHANGED
|
@@ -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
|
-
|
|
204
|
-
const __dirname = path.dirname(
|
|
205
|
-
|
|
206
|
-
|
|
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
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
519
|
+
checkForNpmUpdate().then(info => {
|
|
594
520
|
if (info?.updateAvailable)
|
|
595
|
-
console.log(
|
|
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 =
|
|
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
|
package/dist/moduleMcpServer.js
CHANGED
|
@@ -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', '
|
|
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(
|
|
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\
|
|
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.
|
|
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
|
|
17
|
+
"build": "tsc",
|
|
18
18
|
"build:tsc": "tsc",
|
|
19
19
|
"typecheck": "tsc --noEmit",
|
|
20
20
|
"prepublishOnly": "node -e \"require('fs').chmodSync('bin/exk', 0o755)\""
|