@exreve/exk 1.0.55 → 1.0.56

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.
@@ -1,271 +0,0 @@
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
- }
@@ -1,117 +0,0 @@
1
- import { readFileSync, readdirSync, existsSync } from 'fs';
2
- import * as path from 'path';
3
- import { fileURLToPath } from 'url';
4
- import { dirname } from 'path';
5
- const SKILLS_DIR = dirname(fileURLToPath(import.meta.url)); // This file is in the skills directory
6
- const skillCache = new Map();
7
- /**
8
- * Parse frontmatter and content from a skill markdown file
9
- */
10
- function parseSkillFile(content) {
11
- // Check for YAML frontmatter between --- markers
12
- const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
13
- const match = content.match(frontmatterRegex);
14
- if (!match) {
15
- // No frontmatter, use filename as name
16
- return null;
17
- }
18
- const frontmatter = match[1];
19
- const skillContent = match[2].trim();
20
- // Parse name and description from frontmatter
21
- const nameMatch = frontmatter.match(/name:\s*(.+)/);
22
- const descriptionMatch = frontmatter.match(/description:\s*(.+)/);
23
- const name = nameMatch ? nameMatch[1].trim() : '';
24
- const description = descriptionMatch ? descriptionMatch[1].trim() : '';
25
- if (!name) {
26
- return null;
27
- }
28
- return { name, description, content: skillContent };
29
- }
30
- /**
31
- * Load a single skill by name
32
- */
33
- export function loadSkill(name) {
34
- // Check cache first
35
- if (skillCache.has(name)) {
36
- return skillCache.get(name);
37
- }
38
- const skillPath = path.join(SKILLS_DIR, `${name}.md`);
39
- if (!existsSync(skillPath)) {
40
- return null;
41
- }
42
- try {
43
- const content = readFileSync(skillPath, 'utf-8');
44
- const parsed = parseSkillFile(content);
45
- if (!parsed) {
46
- return null;
47
- }
48
- const skill = {
49
- name: parsed.name,
50
- description: parsed.description,
51
- content: parsed.content
52
- };
53
- skillCache.set(name, skill);
54
- return skill;
55
- }
56
- catch (error) {
57
- console.error(`Failed to load skill ${name}:`, error);
58
- return null;
59
- }
60
- }
61
- /**
62
- * Load all available skills from the skills directory
63
- */
64
- export function loadAllSkills() {
65
- const skills = [];
66
- if (!existsSync(SKILLS_DIR)) {
67
- return skills;
68
- }
69
- try {
70
- const files = readdirSync(SKILLS_DIR);
71
- for (const file of files) {
72
- if (!file.endsWith('.md')) {
73
- continue;
74
- }
75
- const name = file.replace('.md', '');
76
- const skill = loadSkill(name);
77
- if (skill) {
78
- skills.push(skill);
79
- }
80
- }
81
- }
82
- catch (error) {
83
- console.error('Failed to load skills:', error);
84
- }
85
- return skills;
86
- }
87
- /**
88
- * Get skill content for injection into prompts
89
- */
90
- export function getSkillContent(names) {
91
- if (names.length === 0) {
92
- return '';
93
- }
94
- const contents = [];
95
- for (const name of names) {
96
- const skill = loadSkill(name);
97
- if (skill) {
98
- contents.push(`# Skill: ${skill.name}\n\n${skill.content}`);
99
- }
100
- }
101
- if (contents.length === 0) {
102
- return '';
103
- }
104
- return `## Active Skills\n\n${contents.join('\n\n---\n\n')}\n\n---\n\n`;
105
- }
106
- /**
107
- * List all available skill names
108
- */
109
- export function listSkillNames() {
110
- return loadAllSkills().map(s => s.name);
111
- }
112
- /**
113
- * Get skill metadata (name, description) for all skills
114
- */
115
- export function getSkillMetadata() {
116
- return loadAllSkills().map(s => ({ name: s.name, description: s.description }));
117
- }
@@ -1,284 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import { mkdir, unlink } from 'fs/promises';
3
- import fsSync from 'fs';
4
- import path from 'path';
5
- import os from 'os';
6
- import { createHash } from 'crypto';
7
- import { Readable, Transform } from 'stream';
8
- // ============ Transfer Service (CLI side) ============
9
- // Two-phase transfer via HTTP:
10
- // Phase 1 (source): tar selected items → HTTP POST to backend
11
- // Phase 2 (dest): HTTP GET from backend → tar extract
12
- // Socket.IO carries control events + progress/log.
13
- // ---- Sender ----
14
- export function startSending(socket, transfer, foreground) {
15
- const { transferId, sourcePath, selectedItems } = transfer;
16
- const startTime = Date.now();
17
- const apiUrl = getApiUrl(socket);
18
- const log = (message) => {
19
- if (foreground)
20
- console.log(`[transfer:send] ${message}`);
21
- socket.emit('transfer:log', { transferId, side: 'source', level: 'info', message });
22
- };
23
- const sendProgress = (phase, bytesTransferred, totalBytes) => {
24
- const elapsed = (Date.now() - startTime) / 1000;
25
- const speed = elapsed > 0 ? bytesTransferred / elapsed : 0;
26
- const eta = speed > 0 ? (totalBytes - bytesTransferred) / speed : 0;
27
- socket.emit('transfer:progress', {
28
- transferId,
29
- side: 'source',
30
- phase,
31
- bytesTransferred,
32
- totalBytes,
33
- speed: Math.round(speed),
34
- eta: Math.round(eta),
35
- });
36
- };
37
- const sendError = (message) => {
38
- console.error(`[transfer:send] Error: ${message}`);
39
- socket.emit('transfer:error', { transferId, side: 'source', message });
40
- };
41
- const items = selectedItems || ['.'];
42
- log(`Packing ${items.length} item(s) from ${sourcePath}`);
43
- // Estimate size first
44
- let totalBytes = 0;
45
- const sizeEstimate = spawn('du', ['-sb', ...items.map(i => path.join(sourcePath, i))], { cwd: sourcePath });
46
- let duOutput = '';
47
- sizeEstimate.stdout.on('data', (d) => { duOutput += d.toString(); });
48
- sizeEstimate.on('close', (code) => {
49
- if (code === 0) {
50
- const lines = duOutput.trim().split('\n');
51
- for (const line of lines) {
52
- const match = line.match(/^(\d+)/);
53
- if (match)
54
- totalBytes += parseInt(match[1], 10);
55
- }
56
- }
57
- log(`Estimated size: ${formatBytes(totalBytes)}`);
58
- doPackAndUpload();
59
- });
60
- sizeEstimate.on('error', () => doPackAndUpload());
61
- function doPackAndUpload() {
62
- // Create tar with selected items
63
- const tarArgs = ['cf', '-', '-C', sourcePath, ...items];
64
- const tar = spawn('tar', tarArgs);
65
- const hash = createHash('sha256');
66
- let bytesTransferred = 0;
67
- let lastProgressTime = Date.now();
68
- let fileCount = 0;
69
- // Count files as we go (approximate from tar output)
70
- tar.stderr.on('data', (data) => {
71
- const msg = data.toString().trim();
72
- if (msg)
73
- log(`tar: ${msg}`);
74
- // Count lines in tar stderr as approximate file count
75
- fileCount += msg.split('\n').length;
76
- });
77
- tar.on('error', (err) => {
78
- sendError(`tar process error: ${err.message}`);
79
- });
80
- tar.on('close', (code) => {
81
- if (code !== 0) {
82
- sendError(`tar exited with code ${code}`);
83
- return;
84
- }
85
- log(`Packing complete. Final size: ${formatBytes(bytesTransferred)}`);
86
- });
87
- // Stream tar output through hash, then HTTP POST
88
- const uploadUrl = `${apiUrl}/transfer/${transferId}/upload`;
89
- // Pipe tar stdout → hash transform → HTTP upload
90
- const hashTransform = new Transform({
91
- transform(chunk, _encoding, callback) {
92
- hash.update(chunk);
93
- bytesTransferred += chunk.length;
94
- if (Date.now() - lastProgressTime > 500) {
95
- sendProgress('uploading', bytesTransferred, totalBytes || bytesTransferred);
96
- lastProgressTime = Date.now();
97
- }
98
- this.push(chunk);
99
- callback();
100
- },
101
- });
102
- // Use fetch with streaming body for the upload
103
- const tarStream = tar.stdout.pipe(hashTransform);
104
- // Node 18+ supports ReadableStream in fetch body
105
- const nodeStream = Readable.toWeb(tarStream);
106
- fetch(uploadUrl, {
107
- method: 'POST',
108
- body: nodeStream,
109
- headers: {
110
- 'Content-Type': 'application/x-tar',
111
- 'Transfer-Encoding': 'chunked',
112
- },
113
- duplex: 'half',
114
- }).then(async (res) => {
115
- if (!res.ok) {
116
- const text = await res.text();
117
- sendError(`Upload failed: HTTP ${res.status} ${text}`);
118
- return;
119
- }
120
- const sha256 = hash.digest('hex');
121
- const duration = Date.now() - startTime;
122
- // Notify backend that upload is done (only after HTTP confirms success)
123
- socket.emit('transfer:uploaded', {
124
- transferId,
125
- totalBytes: bytesTransferred,
126
- sha256,
127
- fileCount,
128
- });
129
- log(`Upload complete: ${formatBytes(bytesTransferred)} in ${(duration / 1000).toFixed(1)}s (sha256: ${sha256.slice(0, 12)}...)`);
130
- }).catch((err) => {
131
- sendError(`Upload network error: ${err.message}`);
132
- });
133
- // Handle cancellation
134
- const cancelHandler = (data) => {
135
- if (data.transferId === transferId) {
136
- tar.kill('SIGKILL');
137
- socket.off('transfer:cancel', cancelHandler);
138
- log('Transfer cancelled');
139
- }
140
- };
141
- socket.on('transfer:cancel', cancelHandler);
142
- }
143
- }
144
- // ---- Receiver ----
145
- export function startReceiving(socket, transfer, foreground) {
146
- const { transferId, destPath } = transfer;
147
- const startTime = Date.now();
148
- const apiUrl = getApiUrl(socket);
149
- const expectedBytes = transfer.totalBytes || 0;
150
- const expectedSha256 = transfer.sha256 || '';
151
- const log = (message) => {
152
- if (foreground)
153
- console.log(`[transfer:recv] ${message}`);
154
- socket.emit('transfer:log', { transferId, side: 'dest', level: 'info', message });
155
- };
156
- const sendProgress = (phase, bytesTransferred, totalBytes) => {
157
- const elapsed = (Date.now() - startTime) / 1000;
158
- const speed = elapsed > 0 ? bytesTransferred / elapsed : 0;
159
- const eta = speed > 0 ? (totalBytes - bytesTransferred) / speed : 0;
160
- socket.emit('transfer:progress', {
161
- transferId,
162
- side: 'dest',
163
- phase,
164
- bytesTransferred,
165
- totalBytes,
166
- speed: Math.round(speed),
167
- eta: Math.round(eta),
168
- });
169
- };
170
- const sendError = (message) => {
171
- console.error(`[transfer:recv] Error: ${message}`);
172
- socket.emit('transfer:error', { transferId, side: 'dest', message });
173
- };
174
- const tmpFile = path.join(os.tmpdir(), `ttc-transfer-${transferId}.tar`);
175
- log(`Downloading from backend...`);
176
- mkdir(destPath, { recursive: true }).then(async () => {
177
- try {
178
- const downloadUrl = `${apiUrl}/transfer/${transferId}/download`;
179
- const res = await fetch(downloadUrl);
180
- if (!res.ok) {
181
- sendError(`Download failed: HTTP ${res.status}`);
182
- return;
183
- }
184
- if (!res.body) {
185
- sendError('Download failed: no response body');
186
- return;
187
- }
188
- // Stream download to temp file while hashing
189
- const writeStream = fsSync.createWriteStream(tmpFile);
190
- const hash = createHash('sha256');
191
- let bytesReceived = 0;
192
- let lastProgressTime = Date.now();
193
- const reader = res.body.getReader();
194
- // Read chunks from the response stream
195
- while (true) {
196
- const { done, value } = await reader.read();
197
- if (done)
198
- break;
199
- writeStream.write(value);
200
- hash.update(value);
201
- bytesReceived += value.length;
202
- if (Date.now() - lastProgressTime > 500) {
203
- sendProgress('downloading', bytesReceived, expectedBytes || bytesReceived);
204
- lastProgressTime = Date.now();
205
- }
206
- }
207
- writeStream.end();
208
- log(`Downloaded ${formatBytes(bytesReceived)}. Verifying...`);
209
- // Verify hash
210
- const receivedHash = hash.digest('hex');
211
- if (expectedSha256 && receivedHash !== expectedSha256) {
212
- sendError(`Hash mismatch! Expected ${expectedSha256.slice(0, 16)}... got ${receivedHash.slice(0, 16)}...`);
213
- try {
214
- await unlink(tmpFile);
215
- }
216
- catch { }
217
- return;
218
- }
219
- log(`Hash verified ✓. Extracting to ${destPath}...`);
220
- sendProgress('extracting', bytesReceived, bytesReceived);
221
- // Extract tar
222
- const extractProc = spawn('tar', ['xf', tmpFile, '-C', destPath]);
223
- await new Promise((resolve, reject) => {
224
- extractProc.on('close', (code) => {
225
- if (code !== 0)
226
- reject(new Error(`tar extraction failed with code ${code}`));
227
- else
228
- resolve();
229
- });
230
- extractProc.on('error', reject);
231
- });
232
- // Cleanup temp file
233
- try {
234
- await unlink(tmpFile);
235
- }
236
- catch { }
237
- const duration = Date.now() - startTime;
238
- socket.emit('transfer:complete', {
239
- transferId,
240
- totalBytes: bytesReceived,
241
- sha256: receivedHash,
242
- duration,
243
- fileCount: 0,
244
- });
245
- log(`Complete! ${formatBytes(bytesReceived)} extracted to ${destPath} in ${(duration / 1000).toFixed(1)}s`);
246
- }
247
- catch (err) {
248
- sendError(`Download/extract error: ${err.message}`);
249
- try {
250
- await unlink(tmpFile);
251
- }
252
- catch { }
253
- }
254
- }).catch((err) => {
255
- sendError(`Failed to create destination directory: ${err.message}`);
256
- });
257
- }
258
- // ---- Helpers ----
259
- function getApiUrl(socket) {
260
- // Extract the base URL from the socket connection
261
- const io = socket.io;
262
- const opts = io.opts;
263
- if (opts?.hostname) {
264
- const protocol = opts.secure !== false ? 'https' : 'http';
265
- const port = opts.port ? `:${opts.port}` : '';
266
- return `${protocol}://${opts.hostname}${port}`;
267
- }
268
- // Fallback: read from config
269
- try {
270
- const configPath = path.join(os.homedir(), '.talk-to-code', 'config.json');
271
- const config = JSON.parse(fsSync.readFileSync(configPath, 'utf-8'));
272
- return config.apiUrl || 'https://api.talk-to-code.com';
273
- }
274
- catch {
275
- return 'https://api.talk-to-code.com';
276
- }
277
- }
278
- function formatBytes(bytes) {
279
- if (bytes === 0)
280
- return '0 B';
281
- const units = ['B', 'KB', 'MB', 'GB', 'TB'];
282
- const i = Math.floor(Math.log(bytes) / Math.log(1024));
283
- return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
284
- }