@exreve/exk 1.0.45 → 1.0.47

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.
@@ -147,17 +147,6 @@ export async function startOpenAIAdapter(config) {
147
147
  }
148
148
  throw new Error(`Failed to start anthropic-proxy for ${config.targetBaseUrl}`);
149
149
  }
150
- /** Stop all running proxy instances */
151
- export function stopAllAdapters() {
152
- for (const [key, { process }] of runningProxies) {
153
- try {
154
- if (!process.killed)
155
- process.kill('SIGTERM');
156
- }
157
- catch { }
158
- runningProxies.delete(key);
159
- }
160
- }
161
150
  /**
162
151
  * Get the adapter configuration for a local model.
163
152
  * Reads from ~/.talk-to-code/openai-adapters.json or falls back to defaults.
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  const toAbsolutePath = (p) => path.isAbsolute(p) ? p : path.resolve(p);
4
4
  export async function createProject(request) {
5
5
  try {
6
- const { projectId, name, path: projectPath, sourcePath } = request;
6
+ const { path: projectPath, sourcePath } = request;
7
7
  const absolutePath = toAbsolutePath(projectPath);
8
8
  // Check if source path exists (if linking)
9
9
  if (sourcePath) {
@@ -67,26 +67,3 @@ export async function deleteProject(projectPath) {
67
67
  return { success: false, error: error.message };
68
68
  }
69
69
  }
70
- export async function getProjectInfo(projectPath) {
71
- try {
72
- const absolutePath = toAbsolutePath(projectPath);
73
- try {
74
- const stat = await fs.stat(absolutePath);
75
- if (!stat.isDirectory()) {
76
- return null;
77
- }
78
- return {
79
- projectId: '', // Will be set by caller
80
- name: path.basename(absolutePath),
81
- path: absolutePath,
82
- exists: true
83
- };
84
- }
85
- catch {
86
- return null;
87
- }
88
- }
89
- catch {
90
- return null;
91
- }
92
- }
@@ -2,7 +2,6 @@
2
2
  * Generate TypeScript runner code for an app
3
3
  */
4
4
  export function generateRunnerCode(app, projectPath) {
5
- const appName = app.name.replace(/[^a-zA-Z0-9_-]/g, '_'); // Sanitize app name for filename
6
5
  const appType = app.appType || (app.framework?.toLowerCase().includes('react') ||
7
6
  app.framework?.toLowerCase().includes('vue') ||
8
7
  app.framework?.toLowerCase().includes('angular') ||
@@ -14,7 +13,7 @@ export function generateRunnerCode(app, projectPath) {
14
13
  return generateBackendRunner(app, projectPath);
15
14
  }
16
15
  }
17
- function generateStaticFrontendRunner(app, projectPath) {
16
+ function generateStaticFrontendRunner(app, _projectPath) {
18
17
  const appName = app.name.replace(/[^a-zA-Z0-9_-]/g, '_'); // Sanitize app name for filename
19
18
  const port = app.port || 3000;
20
19
  const buildDir = app.buildDir || 'dist';
@@ -99,7 +98,7 @@ process.on('SIGINT', async () => {
99
98
  start()
100
99
  `;
101
100
  }
102
- function generateBackendRunner(app, projectPath) {
101
+ function generateBackendRunner(app, _projectPath) {
103
102
  const appName = app.name.replace(/[^a-zA-Z0-9_-]/g, '_'); // Sanitize app name for filename
104
103
  const appDir = app.directory || '';
105
104
  const startCommand = app.startCommand;
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Session Handlers Module
3
+ *
4
+ * Handles session:create, session:delete, session:prompt,
5
+ * prompt:cancel, emergency:stop, project:config:analyze.
6
+ */
7
+ import fs from 'fs/promises';
8
+ import fsSync from 'fs';
9
+ import path from 'path';
10
+ import { agentSessionManager } from './agentSession.js';
11
+ import { analyzeProjectWithClaude, saveProjectConfig } from './projectAnalyzer.js';
12
+ import { generateRunnerCode } from './runnerGenerator.js';
13
+ /**
14
+ * Register session handlers.
15
+ * `getSocket` is a function that returns the current live socket — this ensures
16
+ * that callbacks registered on a previous socket instance still emit on the
17
+ * active socket after a reconnect (fixes in-flight output loss on disconnect).
18
+ */
19
+ export function registerSessionHandlers(socket, foreground, activeSessions, getSocket) {
20
+ socket.on('session:create', async (data) => {
21
+ try {
22
+ let { sessionId, projectPath } = data;
23
+ // Remap /home/abc to /tmp/abc if /home/abc doesn't exist (container workaround)
24
+ if (projectPath === '/home/abc' && !fsSync.existsSync('/home/abc')) {
25
+ const fallbackPath = '/tmp/abc';
26
+ fsSync.mkdirSync(fallbackPath, { recursive: true });
27
+ projectPath = fallbackPath;
28
+ console.log(`[CLI] Remapped /home/abc -> ${fallbackPath}`);
29
+ }
30
+ activeSessions.set(sessionId, { projectPath });
31
+ if (foreground) {
32
+ console.log(`💬 Session created: ${sessionId}`);
33
+ console.log(` Project: ${projectPath}`);
34
+ }
35
+ else {
36
+ console.log(`Session created: ${sessionId} in project ${projectPath}`);
37
+ }
38
+ }
39
+ catch (error) {
40
+ if (foreground) {
41
+ console.error(`✗ Failed to create session: ${error.message}`);
42
+ }
43
+ else {
44
+ console.error('Failed to create session:', error.message);
45
+ }
46
+ }
47
+ });
48
+ socket.on('session:delete', async (data) => {
49
+ try {
50
+ const { sessionId } = data;
51
+ if (foreground) {
52
+ console.log(`🗑️ Deleting session: ${sessionId}`);
53
+ }
54
+ await agentSessionManager.deleteSession(sessionId);
55
+ activeSessions.delete(sessionId);
56
+ if (foreground) {
57
+ console.log(`✓ Session deleted: ${sessionId}`);
58
+ }
59
+ else {
60
+ console.log(`Session deleted: ${sessionId}`);
61
+ }
62
+ }
63
+ catch (error) {
64
+ if (foreground) {
65
+ console.error(`✗ Failed to delete session: ${error.message}`);
66
+ }
67
+ else {
68
+ console.error('Failed to delete session:', error.message);
69
+ }
70
+ }
71
+ });
72
+ socket.on('project:config:analyze', async (data) => {
73
+ try {
74
+ const { projectId, projectPath, projectName, analysisId } = data;
75
+ if (foreground) {
76
+ console.log(`🔍 Analyzing project: ${projectName} at ${projectPath}`);
77
+ }
78
+ const config = await analyzeProjectWithClaude(projectPath, projectName);
79
+ await saveProjectConfig(projectPath, config);
80
+ for (const app of config.apps) {
81
+ const runnerCode = generateRunnerCode(app, projectPath);
82
+ const runnerFileName = `${app.name}_runner.ts`;
83
+ const runnerPath = path.join(projectPath, app.directory || '', runnerFileName);
84
+ const runnerDir = path.dirname(runnerPath);
85
+ await fs.mkdir(runnerDir, { recursive: true });
86
+ await fs.writeFile(runnerPath, runnerCode, 'utf-8');
87
+ if (foreground) {
88
+ console.log(`✓ Generated runner: ${runnerFileName}`);
89
+ }
90
+ }
91
+ if (foreground) {
92
+ console.log(`✓ Analysis complete: Found ${config.apps.length} apps`);
93
+ console.log(`✓ Generated ${config.apps.length} runner files`);
94
+ }
95
+ socket.emit('project:config:analyzed', {
96
+ projectId,
97
+ config,
98
+ analysisId
99
+ });
100
+ }
101
+ catch (error) {
102
+ if (foreground) {
103
+ console.error(`✗ Analysis error: ${error.message}`);
104
+ }
105
+ socket.emit('project:config:analyze:error', {
106
+ projectId: data.projectId,
107
+ error: error.message,
108
+ analysisId: data.analysisId
109
+ });
110
+ }
111
+ });
112
+ socket.on('session:prompt', async (data) => {
113
+ try {
114
+ const { sessionId, prompt, projectPath: providedProjectPath, promptId, enhancers, model } = data;
115
+ if (!promptId) {
116
+ if (foreground) {
117
+ console.error(`✗ Missing required promptId for session: ${sessionId}`);
118
+ }
119
+ socket.emit('session:error', { sessionId, error: 'Missing required promptId' });
120
+ return;
121
+ }
122
+ let projectPath = providedProjectPath || activeSessions.get(sessionId)?.projectPath;
123
+ if (!projectPath) {
124
+ if (foreground) {
125
+ console.error(`✗ Session not found: ${sessionId} (missing projectPath)`);
126
+ }
127
+ socket.emit('session:error', { sessionId, error: 'Session not found or projectPath missing' });
128
+ return;
129
+ }
130
+ activeSessions.set(sessionId, { projectPath, currentPromptId: promptId, model });
131
+ const capturedPromptId = promptId;
132
+ if (foreground) {
133
+ console.log(`\n[CLI] 📤 Received prompt for session: ${sessionId}, promptId: ${capturedPromptId}`);
134
+ console.log(`[CLI] Prompt: ${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}`);
135
+ }
136
+ await agentSessionManager.createSession({
137
+ sessionId,
138
+ projectPath,
139
+ });
140
+ await agentSessionManager.sendPrompt(sessionId, prompt, enhancers || [], {
141
+ sessionId,
142
+ projectPath,
143
+ promptId: capturedPromptId,
144
+ model: model,
145
+ attachments: data.attachments,
146
+ onStatusUpdate: (status) => {
147
+ if (!capturedPromptId) {
148
+ console.error(`[CLI] Missing promptId for status update, cannot emit prompt:updated`);
149
+ return;
150
+ }
151
+ console.log(`[CLI] 📊 Status update: promptId=${capturedPromptId}, status=${status}, sessionId=${sessionId}`);
152
+ getSocket().emit('prompt:updated', {
153
+ promptId: capturedPromptId,
154
+ sessionId,
155
+ text: prompt,
156
+ status,
157
+ createdAt: new Date().toISOString(),
158
+ ...(status === 'running' ? { startedAt: new Date().toISOString() } : {}),
159
+ ...(status === 'completed' || status === 'error' || status === 'cancelled' ? { completedAt: new Date().toISOString() } : {}),
160
+ messages: []
161
+ });
162
+ },
163
+ onOutput: (output) => {
164
+ const dataString = typeof output.data === 'string' ? output.data : JSON.stringify(output.data);
165
+ if (!capturedPromptId) {
166
+ console.error(`[CLI] Missing promptId for session ${sessionId}, cannot emit prompt:output`);
167
+ return;
168
+ }
169
+ if (foreground && output.type === 'stdout') {
170
+ process.stdout.write(dataString);
171
+ }
172
+ getSocket().emit('prompt:output', {
173
+ sessionId,
174
+ promptId: capturedPromptId,
175
+ type: output.type,
176
+ data: dataString,
177
+ timestamp: output.timestamp,
178
+ metadata: output.metadata
179
+ });
180
+ },
181
+ onError: (error) => {
182
+ if (foreground) {
183
+ console.error(`\n[CLI] ✗ Session error: ${error}`);
184
+ }
185
+ getSocket().emit('session:error', { sessionId, error });
186
+ },
187
+ onComplete: (exitCode) => {
188
+ if (foreground) {
189
+ console.log(`\n[CLI] ✓ Session completed with exit code: ${exitCode ?? 'null'}`);
190
+ }
191
+ if (!capturedPromptId) {
192
+ console.error(`[CLI] Missing promptId for session ${sessionId}, cannot emit session:result`);
193
+ return;
194
+ }
195
+ getSocket().emit('session:result', {
196
+ sessionId,
197
+ promptId: capturedPromptId,
198
+ exitCode
199
+ });
200
+ }
201
+ });
202
+ }
203
+ catch (error) {
204
+ if (foreground) {
205
+ console.error(`✗ Error processing prompt: ${error.message}`);
206
+ }
207
+ socket.emit('session:error', { sessionId: data.sessionId, error: error.message });
208
+ }
209
+ });
210
+ socket.on('prompt:cancel', async (data, callback) => {
211
+ try {
212
+ const { promptId, sessionId } = data;
213
+ if (!promptId || !sessionId) {
214
+ callback?.({ success: false, error: 'Missing promptId or sessionId' });
215
+ return;
216
+ }
217
+ if (foreground) {
218
+ console.log(`[CLI] 🛑 Cancelling prompt: ${promptId}`);
219
+ }
220
+ const cancelled = await agentSessionManager.cancelPrompt(promptId, sessionId, (_status) => {
221
+ getSocket().emit('prompt:updated', {
222
+ promptId,
223
+ sessionId,
224
+ text: '',
225
+ status: 'cancelled',
226
+ createdAt: new Date().toISOString(),
227
+ completedAt: new Date().toISOString(),
228
+ messages: []
229
+ });
230
+ });
231
+ if (cancelled) {
232
+ callback?.({ success: true });
233
+ }
234
+ else {
235
+ callback?.({ success: false, error: 'Prompt not found or already completed' });
236
+ }
237
+ }
238
+ catch (error) {
239
+ if (foreground) {
240
+ console.error(`✗ Error cancelling prompt: ${error.message}`);
241
+ }
242
+ callback?.({ success: false, error: error.message });
243
+ }
244
+ });
245
+ socket.on('emergency:stop', async (data, callback) => {
246
+ try {
247
+ const { sessionId } = data;
248
+ if (!sessionId) {
249
+ callback?.({ success: false, message: 'Missing sessionId' });
250
+ return;
251
+ }
252
+ if (foreground) {
253
+ console.log(`[CLI] ☠️ EMERGENCY STOP for session: ${sessionId}`);
254
+ }
255
+ const result = await agentSessionManager.emergencyStop(sessionId);
256
+ getSocket().emit('emergency:stopped', {
257
+ sessionId,
258
+ success: result.success,
259
+ message: result.message,
260
+ timestamp: new Date().toISOString()
261
+ });
262
+ callback?.(result);
263
+ }
264
+ catch (error) {
265
+ if (foreground) {
266
+ console.error(`✗ Error during emergency stop: ${error.message}`);
267
+ }
268
+ callback?.({ success: false, message: error.message });
269
+ }
270
+ });
271
+ }
Binary file
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Update & Version Handlers Module
3
+ *
4
+ * Handles update:check, update:start, version:info operations.
5
+ */
6
+ import fsSync from 'fs';
7
+ import os from 'os';
8
+ import path from 'path';
9
+ import { createHash } from 'crypto';
10
+ import { fileURLToPath } from 'url';
11
+ export function registerUpdateHandlers(socket, _foreground, readConfig, requestUpdateFromParent) {
12
+ socket.on('update:check', async (_data, callback) => {
13
+ try {
14
+ const config = await readConfig();
15
+ const currentHash = createHash('sha256').update(fsSync.readFileSync(fileURLToPath(import.meta.url))).digest('hex');
16
+ const response = await fetch(`${config.apiUrl}/update/check`, {
17
+ method: 'POST',
18
+ headers: { 'Content-Type': 'application/json' },
19
+ body: JSON.stringify({
20
+ hash: currentHash,
21
+ platform: os.platform(),
22
+ arch: os.arch()
23
+ })
24
+ });
25
+ if (!response.ok) {
26
+ callback?.({ success: false, error: `HTTP ${response.status}` });
27
+ return;
28
+ }
29
+ const info = await response.json();
30
+ callback?.({
31
+ success: true,
32
+ updateAvailable: info.updateAvailable,
33
+ version: info.version,
34
+ changelog: info.changelog,
35
+ size: info.size
36
+ });
37
+ }
38
+ catch (error) {
39
+ callback?.({ success: false, error: error.message });
40
+ }
41
+ });
42
+ socket.on('update:start', async (_data, callback) => {
43
+ try {
44
+ requestUpdateFromParent();
45
+ callback?.({
46
+ success: true,
47
+ message: 'Update initiated. The CLI will restart automatically when complete.'
48
+ });
49
+ }
50
+ catch (error) {
51
+ callback?.({ success: false, error: error.message });
52
+ }
53
+ });
54
+ socket.on('version:info', async (_data, callback) => {
55
+ try {
56
+ const currentHash = createHash('sha256').update(fsSync.readFileSync(fileURLToPath(import.meta.url))).digest('hex');
57
+ let version = 'unknown';
58
+ let date = undefined;
59
+ try {
60
+ const hashesPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'binary-hashes.json');
61
+ const hashes = JSON.parse(fsSync.readFileSync(hashesPath, 'utf-8'));
62
+ if (hashes['js-bundle']) {
63
+ version = hashes['js-bundle'].version || version;
64
+ date = hashes['js-bundle'].date;
65
+ }
66
+ }
67
+ catch { }
68
+ callback?.({
69
+ success: true,
70
+ version,
71
+ hash: currentHash.substring(0, 16) + '...',
72
+ date,
73
+ nodeVersion: process.version,
74
+ platform: os.platform(),
75
+ arch: os.arch()
76
+ });
77
+ }
78
+ catch (error) {
79
+ callback?.({ success: false, error: error.message });
80
+ }
81
+ });
82
+ }
package/dist/updater.js CHANGED
@@ -28,14 +28,11 @@ import { fileURLToPath } from 'url';
28
28
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
29
29
  const CONFIG_DIR = path.join(os.homedir(), '.talk-to-code');
30
30
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
31
- const DEVICE_ID_FILE = path.join(CONFIG_DIR, 'device-id.json');
32
31
  // File paths
33
- const UPDATER_FILE = fileURLToPath(import.meta.url);
34
32
  const APP_BUNDLE = path.join(__dirname, 'dist', 'app-child.js');
35
33
  const APP_BUNDLE_BACKUP = APP_BUNDLE + '.backup';
36
34
  const APP_BUNDLE_OLD = APP_BUNDLE + '.old';
37
35
  const BUNDLE_HASHES_FILE = path.join(__dirname, 'binary-hashes.json');
38
- const UPDATE_LOCK_FILE = path.join(CONFIG_DIR, '.update-lock');
39
36
  // State
40
37
  let childProcess = null;
41
38
  let isUpdating = false;
@@ -207,7 +204,7 @@ function spawnChild() {
207
204
  console.log('✓ Recovered, restarting...');
208
205
  childProcess = spawnChild();
209
206
  })
210
- .catch((err) => {
207
+ .catch((_err) => {
211
208
  console.error('❌ Recovery failed, giving up');
212
209
  process.exit(1);
213
210
  });
@@ -251,7 +248,7 @@ function setupSignalHandlers() {
251
248
  }
252
249
  process.exit(1);
253
250
  });
254
- process.on('unhandledRejection', (reason, promise) => {
251
+ process.on('unhandledRejection', (reason, _promise) => {
255
252
  console.error('❌ Unhandled rejection in updater:', reason);
256
253
  });
257
254
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exreve/exk",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
4
4
  "description": "exk - Control Claude CLI with voice and programmable interfaces",
5
5
  "type": "module",
6
6
  "bin": {