vibecodingmachine-core 2025.12.6-1702 → 2025.12.24-2348

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.
@@ -0,0 +1,108 @@
1
+ const sharedAuth = require('../auth/shared-auth-storage');
2
+ const ProviderManager = require('../ide-integration/provider-manager.cjs');
3
+
4
+ // In-memory cache for quota data.
5
+ const quotaCache = new Map();
6
+
7
+ /**
8
+ * Represents the quota details for a specific agent.
9
+ */
10
+ class Quota {
11
+ /**
12
+ * @param {string} agentId - The unique identifier for the agent (e.g., 'openai:gpt-4').
13
+ * @param {number} limit - The total quota limit.
14
+ * @param {number} remaining - The remaining quota.
15
+ * @param {Date} resetsAt - The timestamp when the quota resets.
16
+ * @param {string} type - The type of quota ('global', 'rate-limit', 'infinite').
17
+ */
18
+ constructor(agentId, limit, remaining, resetsAt, type = 'rate-limit') {
19
+ this.agentId = agentId;
20
+ this.limit = limit;
21
+ this.remaining = remaining;
22
+ this.resetsAt = resetsAt;
23
+ this.lastUpdated = new Date();
24
+ this.type = type;
25
+ }
26
+
27
+ /**
28
+ * Checks if the quota has been exceeded.
29
+ * @returns {boolean} True if the remaining quota is zero or less.
30
+ */
31
+ isExceeded() {
32
+ return this.remaining <= 0;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Fetches quota information for a given agent.
38
+ *
39
+ * @param {string} agentId - The ID of the agent to fetch quota for (e.g., 'anthropic:claude-3').
40
+ * @returns {Promise<Quota>} A promise that resolves to a Quota object.
41
+ */
42
+ async function fetchQuotaForAgent(agentId) {
43
+ // 1. Handle Global Daily Iteration Quota
44
+ if (agentId === 'global:iterations') {
45
+ const quotaInfo = await sharedAuth.canRunAutoMode();
46
+ const today = new Date();
47
+ const tonight = new Date(today);
48
+ tonight.setHours(24, 0, 0, 0);
49
+
50
+ const quota = new Quota(
51
+ 'global:iterations',
52
+ quotaInfo.maxIterations || 10,
53
+ quotaInfo.todayUsage !== undefined ? Math.max(0, (quotaInfo.maxIterations || 10) - quotaInfo.todayUsage) : 10,
54
+ tonight,
55
+ 'global'
56
+ );
57
+ quotaCache.set(agentId, quota);
58
+ return quota;
59
+ }
60
+
61
+ // 2. Handle Local Agent (Infinite Quota)
62
+ if (agentId.startsWith('local-') || agentId.includes('ollama')) {
63
+ const quota = new Quota(agentId, Infinity, Infinity, null, 'infinite');
64
+ quotaCache.set(agentId, quota);
65
+ return quota;
66
+ }
67
+
68
+ // 3. Handle Provider Rate Limits via ProviderManager
69
+ try {
70
+ const providerManager = new ProviderManager();
71
+ const parts = agentId.split(':');
72
+ const provider = parts[0];
73
+ const model = parts[1] || provider;
74
+
75
+ const isLimited = providerManager.isRateLimited(provider, model);
76
+ const timeUntilReset = providerManager.getTimeUntilReset(provider, model);
77
+
78
+ const quota = new Quota(
79
+ agentId,
80
+ 1, // Binary limit for rate limits (1 if available, 0 if limited)
81
+ isLimited ? 0 : 1,
82
+ timeUntilReset ? new Date(Date.now() + timeUntilReset) : null,
83
+ 'rate-limit'
84
+ );
85
+
86
+ quotaCache.set(agentId, quota);
87
+ return quota;
88
+ } catch (error) {
89
+ console.error(`Error fetching provider quota for ${agentId}:`, error);
90
+ return new Quota(agentId, 1, 1, null, 'rate-limit');
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Retrieves the cached quota for a given agent.
96
+ *
97
+ * @param {string} agentId - The ID of the agent.
98
+ * @returns {Quota | undefined} The cached Quota object or undefined if not found.
99
+ */
100
+ function getCachedQuota(agentId) {
101
+ return quotaCache.get(agentId);
102
+ }
103
+
104
+ module.exports = {
105
+ Quota,
106
+ fetchQuotaForAgent,
107
+ getCachedQuota,
108
+ };
@@ -54,17 +54,27 @@ class SyncEngine extends EventEmitter {
54
54
  // Initialize DynamoDB client
55
55
  const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
56
56
  const { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');
57
-
57
+
58
58
  const region = process.env.AWS_REGION || 'us-east-1';
59
59
  const client = new DynamoDBClient({ region });
60
60
  this.dynamoClient = DynamoDBDocumentClient.from(client);
61
-
61
+
62
62
  // Initialize WebSocket client for real-time updates
63
63
  await this._initializeWebSocket();
64
-
64
+
65
65
  this.emit('initialized');
66
66
  return true;
67
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
+
68
78
  this.emit('error', { type: 'initialization', error });
69
79
  throw error;
70
80
  }
@@ -177,11 +187,16 @@ class SyncEngine extends EventEmitter {
177
187
  * Fetch remote changes from DynamoDB
178
188
  */
179
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
+
180
195
  const { ScanCommand } = require('@aws-sdk/lib-dynamodb');
181
-
196
+
182
197
  const tableName = process.env.DYNAMODB_TABLE_NAME || 'vibecodingmachine-requirements';
183
198
  const lastSync = this.lastSyncTime || 0;
184
-
199
+
185
200
  try {
186
201
  // Use Scan with filter instead of Query since we need to check all items
187
202
  // In production, consider using DynamoDB Streams for real-time updates
@@ -195,7 +210,7 @@ class SyncEngine extends EventEmitter {
195
210
  ':lastSync': lastSync
196
211
  }
197
212
  });
198
-
213
+
199
214
  const response = await this.dynamoClient.send(command);
200
215
  return response.Items || [];
201
216
  } catch (error) {
@@ -303,10 +318,17 @@ class SyncEngine extends EventEmitter {
303
318
  * Push local changes to remote
304
319
  */
305
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
+
306
328
  const { PutCommand } = require('@aws-sdk/lib-dynamodb');
307
-
329
+
308
330
  const tableName = process.env.DYNAMODB_TABLE_NAME || 'vibecodingmachine-requirements';
309
-
331
+
310
332
  for (const change of changes) {
311
333
  try {
312
334
  const command = new PutCommand({
@@ -317,12 +339,12 @@ class SyncEngine extends EventEmitter {
317
339
  ...change
318
340
  }
319
341
  });
320
-
342
+
321
343
  await this.dynamoClient.send(command);
322
344
  this.emit('local-change-pushed', change);
323
345
  } catch (error) {
324
346
  this.emit('error', { type: 'push-local', change, error });
325
-
347
+
326
348
  // Add to offline queue if push fails
327
349
  if (!this.isOnline) {
328
350
  this.offlineQueue.push(change);
@@ -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
+ };