vibecodingmachine-core 2025.12.1-534 → 2025.12.22-2230
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/package.json +4 -1
- package/scripts/setup-database.js +108 -0
- package/src/auth/shared-auth-storage.js +43 -6
- package/src/compliance/compliance-manager.js +249 -0
- package/src/compliance/compliance-prompt.js +183 -0
- package/src/database/migrations.js +289 -0
- package/src/database/user-database-client.js +266 -0
- package/src/database/user-schema.js +118 -0
- package/src/ide-integration/applescript-manager.cjs +199 -145
- package/src/ide-integration/applescript-manager.js +234 -96
- package/src/ide-integration/claude-code-cli-manager.cjs +120 -1
- package/src/ide-integration/provider-manager.cjs +67 -1
- package/src/index.cjs +2 -0
- package/src/index.js +6 -0
- package/src/llm/direct-llm-manager.cjs +110 -73
- package/src/quota-management/index.js +108 -0
- package/src/sync/aws-setup.js +445 -0
- package/src/sync/sync-engine.js +410 -0
- package/src/utils/download-with-progress.js +92 -0
- package/src/utils/electron-update-checker.js +7 -0
- package/src/utils/env-helpers.js +54 -0
- package/src/utils/requirement-helpers.js +865 -100
- package/src/utils/requirements-parser.js +324 -0
- package/src/utils/update-checker.js +7 -0
- package/test-quota-system.js +67 -0
- package/test-requirement-stats.js +66 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const EventEmitter = require('events');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SyncEngine - Handles bidirectional sync between local files and AWS DynamoDB
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Real-time change detection using file watchers
|
|
12
|
+
* - Conflict resolution strategies
|
|
13
|
+
* - Offline queue for disconnected state
|
|
14
|
+
* - Change history tracking
|
|
15
|
+
* - Delta sync for efficiency
|
|
16
|
+
*/
|
|
17
|
+
class SyncEngine extends EventEmitter {
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
super();
|
|
20
|
+
|
|
21
|
+
this.options = {
|
|
22
|
+
syncInterval: options.syncInterval || 30000, // 30 seconds
|
|
23
|
+
conflictStrategy: options.conflictStrategy || 'last-write-wins',
|
|
24
|
+
offlineMode: options.offlineMode || false,
|
|
25
|
+
...options
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
this.computerId = this._getComputerId();
|
|
29
|
+
this.isOnline = !this.offlineMode;
|
|
30
|
+
this.isSyncing = false;
|
|
31
|
+
this.offlineQueue = [];
|
|
32
|
+
this.lastSyncTime = null;
|
|
33
|
+
this.changeHistory = [];
|
|
34
|
+
|
|
35
|
+
// AWS clients (will be initialized when needed)
|
|
36
|
+
this.dynamoClient = null;
|
|
37
|
+
this.apiClient = null;
|
|
38
|
+
this.wsClient = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get unique computer ID
|
|
43
|
+
*/
|
|
44
|
+
_getComputerId() {
|
|
45
|
+
const hostname = os.hostname();
|
|
46
|
+
return hostname;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initialize AWS clients
|
|
51
|
+
*/
|
|
52
|
+
async initialize() {
|
|
53
|
+
try {
|
|
54
|
+
// Initialize DynamoDB client
|
|
55
|
+
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
|
|
56
|
+
const { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');
|
|
57
|
+
|
|
58
|
+
const region = process.env.AWS_REGION || 'us-east-1';
|
|
59
|
+
const client = new DynamoDBClient({ region });
|
|
60
|
+
this.dynamoClient = DynamoDBDocumentClient.from(client);
|
|
61
|
+
|
|
62
|
+
// Initialize WebSocket client for real-time updates
|
|
63
|
+
await this._initializeWebSocket();
|
|
64
|
+
|
|
65
|
+
this.emit('initialized');
|
|
66
|
+
return true;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
// If AWS SDK is not installed in this environment (common in local dev),
|
|
69
|
+
// fall back to offline mode instead of throwing so the CLI remains usable.
|
|
70
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && /@aws-sdk\//.test(error.message)) {
|
|
71
|
+
this.options.offlineMode = true;
|
|
72
|
+
this.isOnline = false;
|
|
73
|
+
this.emit('warning', { type: 'offline-fallback', message: 'AWS SDK not available, running in offline mode', error });
|
|
74
|
+
this.emit('initialized');
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.emit('error', { type: 'initialization', error });
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Initialize WebSocket connection for real-time updates
|
|
85
|
+
*/
|
|
86
|
+
async _initializeWebSocket() {
|
|
87
|
+
// TODO: Implement WebSocket connection to AWS IoT Core or API Gateway WebSocket
|
|
88
|
+
// For now, we'll use polling
|
|
89
|
+
this.emit('websocket-status', { connected: false, reason: 'not-implemented' });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Start sync engine
|
|
94
|
+
*/
|
|
95
|
+
async start() {
|
|
96
|
+
if (!this.dynamoClient) {
|
|
97
|
+
await this.initialize();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Start periodic sync
|
|
101
|
+
this.syncInterval = setInterval(() => {
|
|
102
|
+
this.sync().catch(error => {
|
|
103
|
+
this.emit('error', { type: 'sync', error });
|
|
104
|
+
});
|
|
105
|
+
}, this.options.syncInterval);
|
|
106
|
+
|
|
107
|
+
// Do initial sync
|
|
108
|
+
await this.sync();
|
|
109
|
+
|
|
110
|
+
this.emit('started');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Stop sync engine
|
|
115
|
+
*/
|
|
116
|
+
stop() {
|
|
117
|
+
if (this.syncInterval) {
|
|
118
|
+
clearInterval(this.syncInterval);
|
|
119
|
+
this.syncInterval = null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.emit('stopped');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Perform sync operation
|
|
127
|
+
*/
|
|
128
|
+
async sync() {
|
|
129
|
+
if (this.isSyncing) {
|
|
130
|
+
return; // Already syncing
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.isSyncing = true;
|
|
134
|
+
this.emit('sync-start');
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
// Check if online
|
|
138
|
+
if (!this.isOnline) {
|
|
139
|
+
this.emit('sync-complete', { status: 'offline', queued: this.offlineQueue.length });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Process offline queue first
|
|
144
|
+
if (this.offlineQueue.length > 0) {
|
|
145
|
+
await this._processOfflineQueue();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Fetch remote changes
|
|
149
|
+
const remoteChanges = await this._fetchRemoteChanges();
|
|
150
|
+
|
|
151
|
+
// Detect local changes
|
|
152
|
+
const localChanges = await this._detectLocalChanges();
|
|
153
|
+
|
|
154
|
+
// Resolve conflicts
|
|
155
|
+
const conflicts = this._detectConflicts(localChanges, remoteChanges);
|
|
156
|
+
if (conflicts.length > 0) {
|
|
157
|
+
await this._resolveConflicts(conflicts);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Apply remote changes locally
|
|
161
|
+
if (remoteChanges.length > 0) {
|
|
162
|
+
await this._applyRemoteChanges(remoteChanges);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Push local changes to remote
|
|
166
|
+
if (localChanges.length > 0) {
|
|
167
|
+
await this._pushLocalChanges(localChanges);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.lastSyncTime = Date.now();
|
|
171
|
+
this.emit('sync-complete', {
|
|
172
|
+
status: 'success',
|
|
173
|
+
remoteChanges: remoteChanges.length,
|
|
174
|
+
localChanges: localChanges.length,
|
|
175
|
+
conflicts: conflicts.length
|
|
176
|
+
});
|
|
177
|
+
} catch (error) {
|
|
178
|
+
this.emit('sync-complete', { status: 'error', error: error.message });
|
|
179
|
+
// Don't throw - just emit error event
|
|
180
|
+
this.emit('error', { type: 'sync', error: error.message });
|
|
181
|
+
} finally {
|
|
182
|
+
this.isSyncing = false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Fetch remote changes from DynamoDB
|
|
188
|
+
*/
|
|
189
|
+
async _fetchRemoteChanges() {
|
|
190
|
+
if (!this.dynamoClient) {
|
|
191
|
+
this.emit('warning', { type: 'no-dynamo', message: 'DynamoDB client not initialized, skipping remote fetch' });
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const { ScanCommand } = require('@aws-sdk/lib-dynamodb');
|
|
196
|
+
|
|
197
|
+
const tableName = process.env.DYNAMODB_TABLE_NAME || 'vibecodingmachine-requirements';
|
|
198
|
+
const lastSync = this.lastSyncTime || 0;
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
// Use Scan with filter instead of Query since we need to check all items
|
|
202
|
+
// In production, consider using DynamoDB Streams for real-time updates
|
|
203
|
+
const command = new ScanCommand({
|
|
204
|
+
TableName: tableName,
|
|
205
|
+
FilterExpression: '#ts > :lastSync',
|
|
206
|
+
ExpressionAttributeNames: {
|
|
207
|
+
'#ts': 'timestamp'
|
|
208
|
+
},
|
|
209
|
+
ExpressionAttributeValues: {
|
|
210
|
+
':lastSync': lastSync
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const response = await this.dynamoClient.send(command);
|
|
215
|
+
return response.Items || [];
|
|
216
|
+
} catch (error) {
|
|
217
|
+
this.emit('error', { type: 'fetch-remote', error });
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Detect local changes
|
|
224
|
+
*/
|
|
225
|
+
async _detectLocalChanges() {
|
|
226
|
+
// TODO: Implement file watching and change detection
|
|
227
|
+
// For now, return empty array
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Detect conflicts between local and remote changes
|
|
233
|
+
*/
|
|
234
|
+
_detectConflicts(localChanges, remoteChanges) {
|
|
235
|
+
const conflicts = [];
|
|
236
|
+
|
|
237
|
+
for (const local of localChanges) {
|
|
238
|
+
for (const remote of remoteChanges) {
|
|
239
|
+
if (local.requirementId === remote.requirementId) {
|
|
240
|
+
// Same requirement modified both locally and remotely
|
|
241
|
+
if (local.timestamp !== remote.timestamp) {
|
|
242
|
+
conflicts.push({ local, remote });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return conflicts;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Resolve conflicts based on strategy
|
|
253
|
+
*/
|
|
254
|
+
async _resolveConflicts(conflicts) {
|
|
255
|
+
for (const conflict of conflicts) {
|
|
256
|
+
this.emit('conflict', conflict);
|
|
257
|
+
|
|
258
|
+
let resolution;
|
|
259
|
+
switch (this.options.conflictStrategy) {
|
|
260
|
+
case 'last-write-wins':
|
|
261
|
+
resolution = conflict.local.timestamp > conflict.remote.timestamp
|
|
262
|
+
? conflict.local
|
|
263
|
+
: conflict.remote;
|
|
264
|
+
break;
|
|
265
|
+
|
|
266
|
+
case 'manual':
|
|
267
|
+
// Emit event for manual resolution
|
|
268
|
+
resolution = await new Promise((resolve) => {
|
|
269
|
+
this.once('conflict-resolved', resolve);
|
|
270
|
+
});
|
|
271
|
+
break;
|
|
272
|
+
|
|
273
|
+
case 'auto-merge':
|
|
274
|
+
resolution = this._autoMerge(conflict.local, conflict.remote);
|
|
275
|
+
break;
|
|
276
|
+
|
|
277
|
+
default:
|
|
278
|
+
resolution = conflict.remote; // Default to remote
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Log conflict resolution
|
|
282
|
+
this.changeHistory.push({
|
|
283
|
+
type: 'conflict-resolution',
|
|
284
|
+
timestamp: Date.now(),
|
|
285
|
+
conflict,
|
|
286
|
+
resolution
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Auto-merge non-overlapping changes
|
|
293
|
+
*/
|
|
294
|
+
_autoMerge(local, remote) {
|
|
295
|
+
// Simple merge: combine changes from both
|
|
296
|
+
return {
|
|
297
|
+
...remote,
|
|
298
|
+
...local,
|
|
299
|
+
timestamp: Math.max(local.timestamp, remote.timestamp)
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Apply remote changes locally
|
|
305
|
+
*/
|
|
306
|
+
async _applyRemoteChanges(changes) {
|
|
307
|
+
for (const change of changes) {
|
|
308
|
+
try {
|
|
309
|
+
// TODO: Apply change to local requirements file
|
|
310
|
+
this.emit('remote-change-applied', change);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
this.emit('error', { type: 'apply-remote', change, error });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Push local changes to remote
|
|
319
|
+
*/
|
|
320
|
+
async _pushLocalChanges(changes) {
|
|
321
|
+
if (!this.dynamoClient) {
|
|
322
|
+
this.emit('warning', { type: 'no-dynamo', message: 'DynamoDB client not initialized, queuing local changes' });
|
|
323
|
+
// Queue all changes for later push
|
|
324
|
+
this.offlineQueue.push(...changes);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const { PutCommand } = require('@aws-sdk/lib-dynamodb');
|
|
329
|
+
|
|
330
|
+
const tableName = process.env.DYNAMODB_TABLE_NAME || 'vibecodingmachine-requirements';
|
|
331
|
+
|
|
332
|
+
for (const change of changes) {
|
|
333
|
+
try {
|
|
334
|
+
const command = new PutCommand({
|
|
335
|
+
TableName: tableName,
|
|
336
|
+
Item: {
|
|
337
|
+
computerId: this.computerId,
|
|
338
|
+
timestamp: Date.now(),
|
|
339
|
+
...change
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
await this.dynamoClient.send(command);
|
|
344
|
+
this.emit('local-change-pushed', change);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
this.emit('error', { type: 'push-local', change, error });
|
|
347
|
+
|
|
348
|
+
// Add to offline queue if push fails
|
|
349
|
+
if (!this.isOnline) {
|
|
350
|
+
this.offlineQueue.push(change);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Process offline queue
|
|
358
|
+
*/
|
|
359
|
+
async _processOfflineQueue() {
|
|
360
|
+
const queue = [...this.offlineQueue];
|
|
361
|
+
this.offlineQueue = [];
|
|
362
|
+
|
|
363
|
+
for (const change of queue) {
|
|
364
|
+
try {
|
|
365
|
+
await this._pushLocalChanges([change]);
|
|
366
|
+
} catch (error) {
|
|
367
|
+
// Re-add to queue if still failing
|
|
368
|
+
this.offlineQueue.push(change);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Get sync status
|
|
375
|
+
*/
|
|
376
|
+
getStatus() {
|
|
377
|
+
return {
|
|
378
|
+
computerId: this.computerId,
|
|
379
|
+
isOnline: this.isOnline,
|
|
380
|
+
isSyncing: this.isSyncing,
|
|
381
|
+
lastSyncTime: this.lastSyncTime,
|
|
382
|
+
queuedChanges: this.offlineQueue.length,
|
|
383
|
+
conflictStrategy: this.options.conflictStrategy
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Get change history
|
|
389
|
+
*/
|
|
390
|
+
getHistory(limit = 100) {
|
|
391
|
+
return this.changeHistory.slice(-limit);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Set online/offline mode
|
|
396
|
+
*/
|
|
397
|
+
setOnlineMode(isOnline) {
|
|
398
|
+
this.isOnline = isOnline;
|
|
399
|
+
this.emit('online-status-changed', { isOnline });
|
|
400
|
+
|
|
401
|
+
if (isOnline && this.offlineQueue.length > 0) {
|
|
402
|
+
// Trigger sync to process offline queue
|
|
403
|
+
this.sync().catch(error => {
|
|
404
|
+
this.emit('error', { type: 'sync', error });
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
module.exports = SyncEngine;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { pipeline } = require('stream');
|
|
3
|
+
const { promisify } = require('util');
|
|
4
|
+
const streamPipeline = promisify(pipeline);
|
|
5
|
+
const ora = require('ora');
|
|
6
|
+
|
|
7
|
+
function progressBar(percent, width) {
|
|
8
|
+
const fill = Math.round((percent / 100) * width);
|
|
9
|
+
return '█'.repeat(fill) + '-'.repeat(Math.max(0, width - fill));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function formatEta(sec) {
|
|
13
|
+
if (!isFinite(sec) || sec === null) return '--:--';
|
|
14
|
+
const s = Math.max(0, Math.round(sec));
|
|
15
|
+
const m = Math.floor(s / 60);
|
|
16
|
+
const ss = s % 60;
|
|
17
|
+
return `${m}:${ss.toString().padStart(2, '0')}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function downloadWithProgress(url, dest, opts = {}) {
|
|
21
|
+
const fetch = require('node-fetch');
|
|
22
|
+
const spinner = opts.spinner || ora();
|
|
23
|
+
const label = opts.label || 'Downloading...';
|
|
24
|
+
const onProgress = typeof opts.onProgress === 'function' ? opts.onProgress : null;
|
|
25
|
+
|
|
26
|
+
spinner.start(label);
|
|
27
|
+
|
|
28
|
+
const res = await fetch(url);
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
spinner.fail(`Download failed: ${res.status} ${res.statusText}`);
|
|
31
|
+
throw new Error(`Failed to download ${url}: ${res.status}`);
|
|
32
|
+
}
|
|
33
|
+
// Stop the spinner so progress prints are not overwritten
|
|
34
|
+
try { spinner.stop(); } catch (e) {}
|
|
35
|
+
try { process.stdout.write('\r\x1b[2KDownloading: 0.0 MB'); } catch (e) {}
|
|
36
|
+
|
|
37
|
+
const total = Number(res.headers.get('content-length')) || 0;
|
|
38
|
+
const fileStream = fs.createWriteStream(dest);
|
|
39
|
+
|
|
40
|
+
return await new Promise((resolve, reject) => {
|
|
41
|
+
let downloaded = 0;
|
|
42
|
+
const start = Date.now();
|
|
43
|
+
let lastPercent = -1;
|
|
44
|
+
|
|
45
|
+
res.body.on('data', (chunk) => {
|
|
46
|
+
downloaded += chunk.length;
|
|
47
|
+
if (total) {
|
|
48
|
+
const percent = Math.round((downloaded / total) * 100);
|
|
49
|
+
if (percent !== lastPercent) {
|
|
50
|
+
lastPercent = percent;
|
|
51
|
+
const mbDownloaded = (downloaded / (1024 * 1024)).toFixed(1);
|
|
52
|
+
const mbTotal = (total / (1024 * 1024)).toFixed(1);
|
|
53
|
+
const elapsed = Math.max(0.001, (Date.now() - start) / 1000);
|
|
54
|
+
const speed = downloaded / elapsed; // bytes/sec
|
|
55
|
+
const etaSec = (total - downloaded) / (speed || 1);
|
|
56
|
+
const eta = formatEta(etaSec);
|
|
57
|
+
const bar = progressBar(percent, 30);
|
|
58
|
+
// CLI/UI progress
|
|
59
|
+
if (onProgress) {
|
|
60
|
+
onProgress({ percent, downloaded, total, mbDownloaded, mbTotal, eta, bar });
|
|
61
|
+
} else {
|
|
62
|
+
process.stdout.write(`\r\x1b[2K[${bar}] ${percent}% ${mbDownloaded}MB / ${mbTotal}MB ETA: ${eta}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
const mbDownloaded = (downloaded / (1024 * 1024)).toFixed(1);
|
|
67
|
+
if (onProgress) onProgress({ percent: null, downloaded, total, mbDownloaded });
|
|
68
|
+
else process.stdout.write(`\r\x1b[2K${label} ${mbDownloaded} MB`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
res.body.on('error', (err) => {
|
|
73
|
+
spinner.fail('Download error');
|
|
74
|
+
reject(err);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
fileStream.on('error', (err) => {
|
|
78
|
+
spinner.fail('File write error');
|
|
79
|
+
reject(err);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
fileStream.on('finish', () => {
|
|
83
|
+
process.stdout.write('\n');
|
|
84
|
+
spinner.succeed('Download complete');
|
|
85
|
+
resolve();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
res.body.pipe(fileStream);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { downloadWithProgress };
|
|
@@ -39,12 +39,19 @@ function formatVersionTimestamp(versionString, date) {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
const { shouldCheckUpdates } = require('./env-helpers');
|
|
43
|
+
|
|
42
44
|
/**
|
|
43
45
|
* Check for Electron app updates from S3 version manifest
|
|
44
46
|
* @param {string} currentVersion - Current version (e.g., '2025.11.26-0519')
|
|
45
47
|
* @returns {Promise<Object>} Update info or null if no update available
|
|
46
48
|
*/
|
|
47
49
|
async function checkForElectronUpdates(currentVersion) {
|
|
50
|
+
if (!shouldCheckUpdates()) {
|
|
51
|
+
console.log('ℹ️ [UPDATE CHECK] Skipped in development environment');
|
|
52
|
+
return { hasUpdate: false, currentVersion };
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
return new Promise((resolve, reject) => {
|
|
49
56
|
const manifestUrl = `https://d3fh7zgi8horze.cloudfront.net/downloads/version.json?t=${Date.now()}`;
|
|
50
57
|
console.log(`🔍 [UPDATE CHECK] Current version: ${currentVersion}`);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Check if running in development environment
|
|
6
|
+
* @returns {boolean} True if in development
|
|
7
|
+
*/
|
|
8
|
+
function isDevelopment() {
|
|
9
|
+
// Check standard environment variables
|
|
10
|
+
if (process.env.NODE_ENV === 'development' ||
|
|
11
|
+
process.env.VIBECODINGMACHINE_ENV === 'development' ||
|
|
12
|
+
process.env.ELECTRON_IS_DEV === '1') {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Check for --dev flag in arguments
|
|
17
|
+
if (process.argv.includes('--dev')) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Heuristics for local development
|
|
22
|
+
// 1. Check if we are in a git repository that looks like the source code
|
|
23
|
+
// Root of the monorepo usually has 'packages' directory and 'lerna.json' or 'pnpm-workspace.yaml'
|
|
24
|
+
// But this might be too aggressive if user installs via git clone.
|
|
25
|
+
|
|
26
|
+
// 2. Check if electron is running from default app (not packaged)
|
|
27
|
+
if (process.versions && process.versions.electron) {
|
|
28
|
+
const electron = require('electron');
|
|
29
|
+
if (electron.app && !electron.app.isPackaged) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if we should perform update checks
|
|
39
|
+
* @returns {boolean} True if update checks are allowed
|
|
40
|
+
*/
|
|
41
|
+
function shouldCheckUpdates() {
|
|
42
|
+
// Don't check updates in development unless explicitly forced (not implemented yet)
|
|
43
|
+
if (isDevelopment()) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Can add more logic here (e.g. disable updates via config)
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
isDevelopment,
|
|
53
|
+
shouldCheckUpdates
|
|
54
|
+
};
|