vibecodingmachine-core 2026.1.3-2209 → 2026.1.23-1010

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.
Files changed (32) hide show
  1. package/__tests__/provider-manager-fallback.test.js +43 -0
  2. package/__tests__/provider-manager-rate-limit.test.js +61 -0
  3. package/package.json +1 -1
  4. package/src/compliance/compliance-manager.js +5 -2
  5. package/src/database/migrations.js +135 -12
  6. package/src/database/user-database-client.js +63 -8
  7. package/src/database/user-schema.js +7 -0
  8. package/src/health-tracking/__tests__/ide-health-tracker.test.js +420 -0
  9. package/src/health-tracking/__tests__/interaction-recorder.test.js +392 -0
  10. package/src/health-tracking/errors.js +50 -0
  11. package/src/health-tracking/health-reporter.js +331 -0
  12. package/src/health-tracking/ide-health-tracker.js +446 -0
  13. package/src/health-tracking/interaction-recorder.js +161 -0
  14. package/src/health-tracking/json-storage.js +276 -0
  15. package/src/health-tracking/storage-interface.js +63 -0
  16. package/src/health-tracking/validators.js +277 -0
  17. package/src/ide-integration/applescript-manager.cjs +1062 -4
  18. package/src/ide-integration/applescript-manager.js +560 -11
  19. package/src/ide-integration/provider-manager.cjs +158 -28
  20. package/src/ide-integration/quota-detector.cjs +339 -16
  21. package/src/ide-integration/quota-detector.js +6 -1
  22. package/src/index.cjs +32 -1
  23. package/src/index.js +16 -0
  24. package/src/localization/translations/en.js +13 -1
  25. package/src/localization/translations/es.js +12 -0
  26. package/src/utils/admin-utils.js +33 -0
  27. package/src/utils/error-reporter.js +12 -4
  28. package/src/utils/requirement-helpers.js +34 -4
  29. package/src/utils/requirements-parser.js +3 -3
  30. package/tests/health-tracking/health-reporter.test.js +329 -0
  31. package/tests/health-tracking/ide-health-tracker.test.js +368 -0
  32. package/tests/health-tracking/interaction-recorder.test.js +309 -0
@@ -0,0 +1,43 @@
1
+ const ProviderManager = require('../src/ide-integration/provider-manager.cjs');
2
+
3
+ describe('ProviderManager fallback behavior', () => {
4
+ let pm;
5
+
6
+ beforeEach(() => {
7
+ pm = new ProviderManager();
8
+ pm.clearAllRateLimits();
9
+ });
10
+
11
+ afterEach(() => {
12
+ pm.clearAllRateLimits();
13
+ });
14
+
15
+ test('isRateLimited should return true for provider when only legacy/model-agnostic key exists', () => {
16
+ // Simulate legacy stored key with model undefined (string 'undefined')
17
+ const resetTime = Date.now() + 10 * 60 * 1000; // 10 minutes from now
18
+ pm.rateLimits['antigravity:undefined'] = {
19
+ provider: 'antigravity',
20
+ model: undefined,
21
+ resetTime,
22
+ resetDate: new Date(resetTime).toISOString(),
23
+ reason: 'Quota limit reached',
24
+ markedAt: new Date().toISOString()
25
+ };
26
+
27
+ expect(pm.isRateLimited('antigravity', 'antigravity')).toBe(true);
28
+ const t = pm.getTimeUntilReset('antigravity', 'antigravity');
29
+ expect(t).toBeGreaterThan(0);
30
+ });
31
+
32
+ test('getRateLimitInfo should return earliest reset for provider when model-specific not present', () => {
33
+ const resetA = Date.now() + 60 * 60 * 1000; // 60m
34
+ const resetB = Date.now() + 5 * 60 * 1000; // 5m
35
+
36
+ pm.rateLimits['antigravity:legacy'] = { provider: 'antigravity', model: 'legacy', resetTime: resetA, reason: 'legacy', markedAt: new Date().toISOString() };
37
+ pm.rateLimits['antigravity:other'] = { provider: 'antigravity', model: 'other', resetTime: resetB, reason: 'other', markedAt: new Date().toISOString() };
38
+
39
+ const info = pm.getRateLimitInfo('antigravity', 'antigravity');
40
+ expect(info.isRateLimited).toBe(true);
41
+ expect(info.resetTime).toBe(resetB);
42
+ });
43
+ });
@@ -0,0 +1,61 @@
1
+ const ProviderManager = require('../src/ide-integration/provider-manager.cjs');
2
+
3
+ describe('ProviderManager rate limit handling', () => {
4
+ let pm;
5
+
6
+ beforeEach(() => {
7
+ pm = new ProviderManager();
8
+ pm.clearAllRateLimits();
9
+ });
10
+
11
+ afterEach(() => {
12
+ pm.clearAllRateLimits();
13
+ });
14
+
15
+ test('markRateLimited with undefined model normalizes model to provider', () => {
16
+ // Call with model undefined to simulate IDE provider case
17
+ pm.markRateLimited('antigravity', undefined, 'Quota limit reached');
18
+
19
+ // The provider should be considered rate-limited when queried using provider as model
20
+ expect(pm.isRateLimited('antigravity', 'antigravity')).toBe(true);
21
+
22
+ const info = pm.getRateLimitInfo('antigravity', 'antigravity');
23
+ expect(info.isRateLimited).toBe(true);
24
+ expect(info.resetTime).toBeGreaterThan(Date.now());
25
+
26
+ const ms = pm.getTimeUntilReset('antigravity', 'antigravity');
27
+ expect(ms).toBeGreaterThan(0);
28
+ });
29
+
30
+
31
+ test('parses "resets Month Day at Time" format correctly', () => {
32
+ // Mock Date.now to Jan 13, 2026 10:00 AM
33
+ const mockNow = new Date('2026-01-13T10:00:00').getTime();
34
+ const originalDateNow = Date.now;
35
+ Date.now = jest.fn(() => mockNow);
36
+
37
+ try {
38
+ const errorMessage = 'Spending cap reached resets Jan 17 at 12pm';
39
+
40
+ // Expected: Jan 17 12:00 PM = 12:00 + (17-13)*24 hours
41
+ // Jan 13 10am to Jan 17 12pm
42
+ // Diff: 4 days + 2 hours = 98 hours
43
+ const expectedDuration = (4 * 24 * 60 * 60 * 1000) + (2 * 60 * 60 * 1000);
44
+
45
+ // Verify regex in test env
46
+ const regex = /resets\s+([a-zA-Z]+)\s+(\d{1,2})\s+at\s+(\d{1,2}(?::\d{2})?)\s*(am|pm)?/i;
47
+ const match = errorMessage.match(regex);
48
+ expect(match).not.toBeNull();
49
+
50
+ const duration = pm.parseRateLimitDuration(errorMessage);
51
+
52
+ // Allow for some minor variance if calculation logic differs slightly, but it should be close
53
+ expect(duration).toBeDefined();
54
+ expect(duration).not.toBeNull();
55
+ expect(Math.abs(duration - expectedDuration)).toBeLessThan(1000);
56
+
57
+ } finally {
58
+ Date.now = originalDateNow;
59
+ }
60
+ });
61
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibecodingmachine-core",
3
- "version": "2026.01.03-2209",
3
+ "version": "2026.01.23-1010",
4
4
  "description": "Shared core logic for Vibe Coding Machine IDE integration",
5
5
  "main": "src/index.cjs",
6
6
  "module": "src/index.js",
@@ -10,8 +10,11 @@ const fs = require('fs').promises
10
10
  const path = require('path')
11
11
 
12
12
  class ComplianceManager {
13
- constructor() {
13
+ constructor(authToken = null) {
14
14
  this.userDb = new UserDatabase()
15
+ if (authToken) {
16
+ this.userDb.setAuthToken(authToken)
17
+ }
15
18
  }
16
19
 
17
20
  /**
@@ -19,7 +22,7 @@ class ComplianceManager {
19
22
  * Returns what needs to be acknowledged
20
23
  */
21
24
  async checkComplianceStatus(userId) {
22
- const user = await this.userDb.getUser(userId)
25
+ const user = await this.userDb.getUser()
23
26
 
24
27
  if (!user) {
25
28
  throw new Error('User not found')
@@ -6,11 +6,11 @@
6
6
  */
7
7
 
8
8
  const { DynamoDBClient } = require('@aws-sdk/client-dynamodb')
9
- const {
10
- CreateTableCommand,
11
- DescribeTableCommand,
9
+ const {
10
+ CreateTableCommand,
11
+ DescribeTableCommand,
12
12
  UpdateTableCommand,
13
- ListTablesCommand
13
+ ListTablesCommand
14
14
  } = require('@aws-sdk/client-dynamodb')
15
15
 
16
16
  class DatabaseMigrations {
@@ -25,13 +25,15 @@ class DatabaseMigrations {
25
25
  */
26
26
  async runMigrations() {
27
27
  console.log('🔄 Running database migrations...')
28
-
28
+
29
29
  try {
30
30
  await this.createUsersTable()
31
31
  await this.createComputersTable()
32
32
  await this.createProjectsTable()
33
33
  await this.createActivityTable()
34
-
34
+ await this.createErrorsTable()
35
+ await this.createFeedbackTable()
36
+
35
37
  console.log('✅ All migrations completed successfully')
36
38
  } catch (error) {
37
39
  console.error('❌ Migration failed:', error)
@@ -44,7 +46,7 @@ class DatabaseMigrations {
44
46
  */
45
47
  async createUsersTable() {
46
48
  const tableName = 'vibecodingmachine-users'
47
-
49
+
48
50
  if (await this.tableExists(tableName)) {
49
51
  console.log(`📋 Table ${tableName} already exists`)
50
52
  return
@@ -94,7 +96,7 @@ class DatabaseMigrations {
94
96
  */
95
97
  async createComputersTable() {
96
98
  const tableName = 'vibecodingmachine-computers'
97
-
99
+
98
100
  if (await this.tableExists(tableName)) {
99
101
  console.log(`📋 Table ${tableName} already exists`)
100
102
  return
@@ -137,7 +139,7 @@ class DatabaseMigrations {
137
139
  */
138
140
  async createProjectsTable() {
139
141
  const tableName = 'vibecodingmachine-projects'
140
-
142
+
141
143
  if (await this.tableExists(tableName)) {
142
144
  console.log(`📋 Table ${tableName} already exists`)
143
145
  return
@@ -180,7 +182,7 @@ class DatabaseMigrations {
180
182
  */
181
183
  async createActivityTable() {
182
184
  const tableName = 'vibecodingmachine-activity'
183
-
185
+
184
186
  if (await this.tableExists(tableName)) {
185
187
  console.log(`📋 Table ${tableName} already exists`)
186
188
  return
@@ -222,6 +224,126 @@ class DatabaseMigrations {
222
224
  console.log(`✅ Created table: ${tableName}`)
223
225
  }
224
226
 
227
+ /**
228
+ * Create errors table for tracking application errors
229
+ */
230
+ async createErrorsTable() {
231
+ const tableName = 'vibecodingmachine-errors'
232
+
233
+ if (await this.tableExists(tableName)) {
234
+ console.log(`📋 Table ${tableName} already exists`)
235
+ return
236
+ }
237
+
238
+ const params = {
239
+ TableName: tableName,
240
+ KeySchema: [
241
+ { AttributeName: 'errorId', KeyType: 'HASH' }
242
+ ],
243
+ AttributeDefinitions: [
244
+ { AttributeName: 'errorId', AttributeType: 'S' }
245
+ ],
246
+ BillingMode: 'PAY_PER_REQUEST',
247
+ Tags: [
248
+ { Key: 'Application', Value: 'VibeCodingMachine' },
249
+ { Key: 'Environment', Value: process.env.NODE_ENV || 'development' }
250
+ ]
251
+ }
252
+
253
+ await this.client.send(new CreateTableCommand(params))
254
+ console.log(`✅ Created table: ${tableName}`)
255
+ }
256
+
257
+ /**
258
+ * Create feedback table for user comments
259
+ */
260
+ async createFeedbackTable() {
261
+ const tableName = 'vibecodingmachine-feedback'
262
+
263
+ if (await this.tableExists(tableName)) {
264
+ console.log(`📋 Table ${tableName} already exists`)
265
+ return
266
+ }
267
+
268
+ const params = {
269
+ TableName: tableName,
270
+ KeySchema: [
271
+ { AttributeName: 'feedbackId', KeyType: 'HASH' }
272
+ ],
273
+ AttributeDefinitions: [
274
+ { AttributeName: 'feedbackId', AttributeType: 'S' }
275
+ ],
276
+ BillingMode: 'PAY_PER_REQUEST',
277
+ Tags: [
278
+ { Key: 'Application', Value: 'VibeCodingMachine' },
279
+ { Key: 'Environment', Value: process.env.NODE_ENV || 'development' }
280
+ ]
281
+ }
282
+
283
+ await this.client.send(new CreateTableCommand(params))
284
+ console.log(`✅ Created table: ${tableName}`)
285
+ }
286
+
287
+ /**
288
+ * Create errors table for tracking application errors
289
+ */
290
+ async createErrorsTable() {
291
+ const tableName = 'vibecodingmachine-errors'
292
+
293
+ if (await this.tableExists(tableName)) {
294
+ console.log(`📋 Table ${tableName} already exists`)
295
+ return
296
+ }
297
+
298
+ const params = {
299
+ TableName: tableName,
300
+ KeySchema: [
301
+ { AttributeName: 'errorId', KeyType: 'HASH' }
302
+ ],
303
+ AttributeDefinitions: [
304
+ { AttributeName: 'errorId', AttributeType: 'S' }
305
+ ],
306
+ BillingMode: 'PAY_PER_REQUEST',
307
+ Tags: [
308
+ { Key: 'Application', Value: 'VibeCodingMachine' },
309
+ { Key: 'Environment', Value: process.env.NODE_ENV || 'development' }
310
+ ]
311
+ }
312
+
313
+ await this.client.send(new CreateTableCommand(params))
314
+ console.log(`✅ Created table: ${tableName}`)
315
+ }
316
+
317
+ /**
318
+ * Create feedback table for user comments
319
+ */
320
+ async createFeedbackTable() {
321
+ const tableName = 'vibecodingmachine-feedback'
322
+
323
+ if (await this.tableExists(tableName)) {
324
+ console.log(`📋 Table ${tableName} already exists`)
325
+ return
326
+ }
327
+
328
+ const params = {
329
+ TableName: tableName,
330
+ KeySchema: [
331
+ { AttributeName: 'feedbackId', KeyType: 'HASH' }
332
+ ],
333
+ AttributeDefinitions: [
334
+ { AttributeName: 'feedbackId', AttributeType: 'S' }
335
+ ],
336
+ BillingMode: 'PAY_PER_REQUEST',
337
+ Tags: [
338
+ { Key: 'Application', Value: 'VibeCodingMachine' },
339
+ { Key: 'Environment', Value: process.env.NODE_ENV || 'development' }
340
+ ]
341
+ }
342
+
343
+ await this.client.send(new CreateTableCommand(params))
344
+ console.log(`✅ Created table: ${tableName}`)
345
+ }
346
+
225
347
  /**
226
348
  * Check if a table exists
227
349
  */
@@ -272,10 +394,11 @@ class DatabaseMigrations {
272
394
  */
273
395
  async getAllTableStatus() {
274
396
  const tableNames = [
275
- 'vibecodingmachine-users',
276
397
  'vibecodingmachine-computers',
277
398
  'vibecodingmachine-projects',
278
- 'vibecodingmachine-activity'
399
+ 'vibecodingmachine-activity',
400
+ 'vibecodingmachine-errors',
401
+ 'vibecodingmachine-feedback'
279
402
  ]
280
403
 
281
404
  const statuses = await Promise.all(
@@ -18,9 +18,21 @@ try {
18
18
 
19
19
  class UserDatabaseClient {
20
20
  constructor(apiBaseUrl = null) {
21
- // Default to production API server, or use provided URL
22
- this.apiBaseUrl = apiBaseUrl || process.env.VIBECODINGMACHINE_API_URL || 'https://api.vibecodingmachine.com';
21
+ // Check if we're in a development workspace
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ const rootDir = path.join(__dirname, '..', '..', '..', '..');
25
+ const isDevWorkspace = fs.existsSync(path.join(rootDir, '.git'));
26
+
27
+ // Default to local dev server in development, production otherwise
28
+ if (isDevWorkspace && !apiBaseUrl && !process.env.VIBECODINGMACHINE_API_URL) {
29
+ this.apiBaseUrl = 'http://localhost:3457';
30
+ } else {
31
+ this.apiBaseUrl = apiBaseUrl || process.env.VIBECODINGMACHINE_API_URL || 'https://api.vibecodingmachine.com';
32
+ }
33
+
23
34
  this.authToken = null;
35
+ console.log(require('chalk').gray(`UserDatabaseClient: Using API at ${this.apiBaseUrl}`));
24
36
  }
25
37
 
26
38
  /**
@@ -44,14 +56,36 @@ class UserDatabaseClient {
44
56
  headers.Authorization = `Bearer ${this.authToken}`;
45
57
  }
46
58
 
47
- const response = await fetch(url, {
48
- ...options,
49
- headers
50
- });
59
+ let response;
60
+ try {
61
+ response = await fetch(url, {
62
+ ...options,
63
+ headers
64
+ });
65
+ } catch (fetchError) {
66
+ const msg = `Connection failed: ${fetchError.message}`;
67
+ console.warn(`UserDatabaseClient: ${msg} [${url}]`);
68
+ throw new Error(msg);
69
+ }
51
70
 
52
71
  if (!response.ok) {
53
- const error = await response.json().catch(() => ({ error: 'Network error' }));
54
- throw new Error(error.error || `HTTP ${response.status}`);
72
+ let errorDetail = '';
73
+ let bodyText = '';
74
+ try {
75
+ bodyText = await response.text();
76
+ try {
77
+ const errorJson = JSON.parse(bodyText);
78
+ errorDetail = errorJson.error || errorJson.message || '';
79
+ } catch (e) {
80
+ errorDetail = bodyText;
81
+ }
82
+ } catch (e) {
83
+ errorDetail = 'Could not read response body';
84
+ }
85
+
86
+ if (errorDetail.length > 200) errorDetail = errorDetail.substring(0, 200) + '...';
87
+ const errorMsg = errorDetail || `HTTP ${response.status}`;
88
+ throw new Error(errorMsg);
55
89
  }
56
90
 
57
91
  return response.json();
@@ -274,6 +308,27 @@ class UserDatabaseClient {
274
308
  return false; // Client-side can't check admin status
275
309
  }
276
310
 
311
+ /**
312
+ * Submit user feedback to the admin database
313
+ */
314
+ async submitFeedback(feedbackData) {
315
+ try {
316
+ const response = await this.apiRequest('/api/feedback', {
317
+ method: 'POST',
318
+ body: JSON.stringify({
319
+ timestamp: Date.now(),
320
+ hostname: os.hostname(),
321
+ platform: os.platform(),
322
+ ...feedbackData
323
+ })
324
+ });
325
+ return response;
326
+ } catch (error) {
327
+ console.warn('UserDatabase: submitFeedback failed:', error.message);
328
+ throw error;
329
+ }
330
+ }
331
+
277
332
  /**
278
333
  * Report an error to the admin database for tracking and fixing
279
334
  */
@@ -115,6 +115,13 @@ class UserDatabase {
115
115
  return this.apiClient.isAdmin(userId)
116
116
  }
117
117
 
118
+ /**
119
+ * Submit user feedback to the admin database
120
+ */
121
+ async submitFeedback(feedbackData) {
122
+ return this.apiClient.submitFeedback(feedbackData)
123
+ }
124
+
118
125
  /**
119
126
  * Report an error to the admin database for tracking and fixing
120
127
  */