vibecodingmachine-core 2025.12.25-25 → 2026.1.22-1441
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/ERROR_REPORTING_API.md +212 -0
- package/ERROR_REPORTING_USAGE.md +380 -0
- package/__tests__/provider-manager-fallback.test.js +43 -0
- package/__tests__/provider-manager-rate-limit.test.js +61 -0
- package/__tests__/utils/git-branch-manager.test.js +61 -0
- package/package.json +1 -1
- package/src/beta-request.js +160 -0
- package/src/compliance/compliance-manager.js +5 -2
- package/src/database/migrations.js +135 -12
- package/src/database/user-database-client.js +127 -8
- package/src/database/user-schema.js +28 -0
- package/src/health-tracking/__tests__/ide-health-tracker.test.js +420 -0
- package/src/health-tracking/__tests__/interaction-recorder.test.js +392 -0
- package/src/health-tracking/errors.js +50 -0
- package/src/health-tracking/health-reporter.js +331 -0
- package/src/health-tracking/ide-health-tracker.js +446 -0
- package/src/health-tracking/interaction-recorder.js +161 -0
- package/src/health-tracking/json-storage.js +276 -0
- package/src/health-tracking/storage-interface.js +63 -0
- package/src/health-tracking/validators.js +277 -0
- package/src/ide-integration/applescript-manager.cjs +1087 -9
- package/src/ide-integration/applescript-manager.js +565 -15
- package/src/ide-integration/applescript-utils.js +26 -18
- package/src/ide-integration/provider-manager.cjs +158 -28
- package/src/ide-integration/quota-detector.cjs +339 -16
- package/src/ide-integration/quota-detector.js +6 -1
- package/src/index.cjs +36 -1
- package/src/index.js +20 -0
- package/src/localization/translations/en.js +15 -1
- package/src/localization/translations/es.js +14 -0
- package/src/requirement-numbering.js +164 -0
- package/src/sync/aws-setup.js +4 -4
- package/src/utils/admin-utils.js +33 -0
- package/src/utils/error-reporter.js +117 -0
- package/src/utils/git-branch-manager.js +278 -0
- package/src/utils/requirement-helpers.js +44 -5
- package/src/utils/requirements-parser.js +28 -3
- package/tests/health-tracking/health-reporter.test.js +329 -0
- package/tests/health-tracking/ide-health-tracker.test.js +368 -0
- package/tests/health-tracking/interaction-recorder.test.js +309 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { isGitRepo, getCurrentBranch, hasUncommittedChanges } = require('../../src/utils/git-branch-manager');
|
|
6
|
+
|
|
7
|
+
describe('git-branch-manager', () => {
|
|
8
|
+
let tempRepoPath;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
tempRepoPath = path.join(os.tmpdir(), `vcm-test-repo-${Date.now()}`);
|
|
12
|
+
await fs.ensureDir(tempRepoPath);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await fs.remove(tempRepoPath);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const initRepo = () => {
|
|
20
|
+
execSync('git init', { cwd: tempRepoPath, stdio: 'ignore' });
|
|
21
|
+
execSync('git config user.email "test@example.com"', { cwd: tempRepoPath, stdio: 'ignore' });
|
|
22
|
+
execSync('git config user.name "Test User"', { cwd: tempRepoPath, stdio: 'ignore' });
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
test('isGitRepo identifies git repositories', () => {
|
|
26
|
+
expect(isGitRepo(tempRepoPath)).toBe(false);
|
|
27
|
+
initRepo();
|
|
28
|
+
expect(isGitRepo(tempRepoPath)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('getCurrentBranch returns the current branch name', async () => {
|
|
32
|
+
initRepo();
|
|
33
|
+
// Must commit something for HEAD to exist
|
|
34
|
+
await fs.writeFile(path.join(tempRepoPath, 'initial.txt'), 'init');
|
|
35
|
+
execSync('git add .', { cwd: tempRepoPath, stdio: 'ignore' });
|
|
36
|
+
execSync('git commit -m "initial commit"', { cwd: tempRepoPath, stdio: 'ignore' });
|
|
37
|
+
|
|
38
|
+
const branch = getCurrentBranch(tempRepoPath);
|
|
39
|
+
expect(branch).toMatch(/^(main|master)$/);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('hasUncommittedChanges detects dirty repository', async () => {
|
|
43
|
+
initRepo();
|
|
44
|
+
// Should be false initially (even if empty)
|
|
45
|
+
expect(hasUncommittedChanges(tempRepoPath)).toBe(false);
|
|
46
|
+
|
|
47
|
+
// Create a file
|
|
48
|
+
await fs.writeFile(path.join(tempRepoPath, 'test.txt'), 'hello');
|
|
49
|
+
// Untracked file counts as uncommitted change
|
|
50
|
+
expect(hasUncommittedChanges(tempRepoPath)).toBe(true);
|
|
51
|
+
|
|
52
|
+
// Commit it
|
|
53
|
+
execSync('git add .', { cwd: tempRepoPath, stdio: 'ignore' });
|
|
54
|
+
execSync('git commit -m "initial commit"', { cwd: tempRepoPath, stdio: 'ignore' });
|
|
55
|
+
expect(hasUncommittedChanges(tempRepoPath)).toBe(false);
|
|
56
|
+
|
|
57
|
+
// Modify it
|
|
58
|
+
await fs.appendFile(path.join(tempRepoPath, 'test.txt'), ' world');
|
|
59
|
+
expect(hasUncommittedChanges(tempRepoPath)).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Beta Request Management for VibeCodingMachine
|
|
3
|
+
*
|
|
4
|
+
* Handles beta access requests from web, CLI, and GUI applications
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
|
|
9
|
+
class BetaRequestManager {
|
|
10
|
+
constructor(apiBaseUrl = null) {
|
|
11
|
+
this.apiBaseUrl = apiBaseUrl || process.env.VIBECODINGMACHINE_API_URL || 'https://api.vibecodingmachine.com';
|
|
12
|
+
this.authToken = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Set authentication token for API requests
|
|
17
|
+
*/
|
|
18
|
+
setAuthToken(token) {
|
|
19
|
+
this.authToken = token;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Make authenticated API request
|
|
24
|
+
*/
|
|
25
|
+
async apiRequest(endpoint, options = {}) {
|
|
26
|
+
const url = `${this.apiBaseUrl}${endpoint}`;
|
|
27
|
+
const headers = {
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
...options.headers
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (this.authToken) {
|
|
33
|
+
headers.Authorization = `Bearer ${this.authToken}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fetch = globalThis.fetch || require('node-fetch');
|
|
37
|
+
const response = await fetch(url, {
|
|
38
|
+
...options,
|
|
39
|
+
headers
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
const error = await response.json().catch(() => ({ error: 'Network error' }));
|
|
44
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return response.json();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Submit a beta access request
|
|
52
|
+
*/
|
|
53
|
+
async submitBetaRequest(requestData) {
|
|
54
|
+
try {
|
|
55
|
+
const result = await this.apiRequest('/api/beta/request', {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
...requestData,
|
|
59
|
+
requestDate: Date.now(),
|
|
60
|
+
status: 'REQUEST'
|
|
61
|
+
})
|
|
62
|
+
});
|
|
63
|
+
return result;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Beta request submission failed:', error);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if user already has a pending or approved request
|
|
72
|
+
*/
|
|
73
|
+
async checkExistingRequest(email) {
|
|
74
|
+
try {
|
|
75
|
+
const userId = this.generateUserId(email);
|
|
76
|
+
const result = await this.apiRequest(`/api/users/${userId}`);
|
|
77
|
+
return result.user;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
// User doesn't exist or API error
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Validate request data
|
|
86
|
+
*/
|
|
87
|
+
validateRequestData(data) {
|
|
88
|
+
const errors = [];
|
|
89
|
+
|
|
90
|
+
if (!data.email || !data.email.trim()) {
|
|
91
|
+
errors.push('Email is required');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (data.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
|
|
95
|
+
errors.push('Invalid email format');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!data.name || !data.name.trim()) {
|
|
99
|
+
errors.push('Name is required');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!data.language) {
|
|
103
|
+
errors.push('Language preference is required');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!data.appPurpose || !data.appPurpose.trim()) {
|
|
107
|
+
errors.push('App purpose description is required');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
isValid: errors.length === 0,
|
|
112
|
+
errors
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get supported languages
|
|
118
|
+
*/
|
|
119
|
+
getSupportedLanguages() {
|
|
120
|
+
return {
|
|
121
|
+
'en': 'English',
|
|
122
|
+
'es': 'Spanish'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Generate user ID from email (consistent with existing system)
|
|
128
|
+
*/
|
|
129
|
+
generateUserId(email) {
|
|
130
|
+
return crypto.createHash('sha256').update(email.toLowerCase()).digest('hex').substring(0, 16);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Format request data for email notification
|
|
135
|
+
*/
|
|
136
|
+
formatRequestForEmail(requestData) {
|
|
137
|
+
const supportedLanguages = this.getSupportedLanguages();
|
|
138
|
+
const languageName = supportedLanguages[requestData.language] || requestData.language;
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
recipient: 'jesse.d.olsen@gmail.com',
|
|
142
|
+
subject: `New Beta Request: ${requestData.name} (${requestData.email})`,
|
|
143
|
+
body: `
|
|
144
|
+
New VibeCodingMachine beta access request received:
|
|
145
|
+
|
|
146
|
+
Name: ${requestData.name}
|
|
147
|
+
Email: ${requestData.email}
|
|
148
|
+
Language: ${languageName}
|
|
149
|
+
App Purpose: ${requestData.appPurpose}
|
|
150
|
+
Other Info: ${requestData.otherInfo || 'None provided'}
|
|
151
|
+
Request Date: ${new Date(requestData.requestDate).toLocaleString()}
|
|
152
|
+
Source: ${requestData.source || 'Unknown'}
|
|
153
|
+
|
|
154
|
+
Please review and approve/reject this request in the admin panel.
|
|
155
|
+
`.trim()
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = BetaRequestManager;
|
|
@@ -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(
|
|
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
|
-
//
|
|
22
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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();
|
|
@@ -228,6 +262,45 @@ class UserDatabaseClient {
|
|
|
228
262
|
throw new Error('setAdminStatus not allowed from client');
|
|
229
263
|
}
|
|
230
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Submit a beta access request
|
|
267
|
+
*/
|
|
268
|
+
async submitBetaRequest(requestData) {
|
|
269
|
+
if (!this.authToken) {
|
|
270
|
+
console.warn('UserDatabase: No auth token, beta request submission requires authentication');
|
|
271
|
+
throw new Error('Authentication required for beta request');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const result = await this.apiRequest('/api/beta/request', {
|
|
276
|
+
method: 'POST',
|
|
277
|
+
body: JSON.stringify({
|
|
278
|
+
...requestData,
|
|
279
|
+
requestDate: Date.now(),
|
|
280
|
+
status: 'REQUEST'
|
|
281
|
+
})
|
|
282
|
+
});
|
|
283
|
+
return result;
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.warn('UserDatabase: submitBetaRequest failed:', error.message);
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Check if user already has a pending or approved request
|
|
292
|
+
*/
|
|
293
|
+
async checkExistingRequest(email) {
|
|
294
|
+
try {
|
|
295
|
+
const userId = this.generateUserId(email);
|
|
296
|
+
const result = await this.apiRequest(`/api/users/${userId}`);
|
|
297
|
+
return result.user;
|
|
298
|
+
} catch (error) {
|
|
299
|
+
// User doesn't exist or API error
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
231
304
|
/**
|
|
232
305
|
* Check if user is an admin (not implemented client-side for security)
|
|
233
306
|
*/
|
|
@@ -235,6 +308,52 @@ class UserDatabaseClient {
|
|
|
235
308
|
return false; // Client-side can't check admin status
|
|
236
309
|
}
|
|
237
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
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Report an error to the admin database for tracking and fixing
|
|
334
|
+
*/
|
|
335
|
+
async reportError(errorData) {
|
|
336
|
+
try {
|
|
337
|
+
const errorReport = {
|
|
338
|
+
timestamp: Date.now(),
|
|
339
|
+
hostname: os.hostname(),
|
|
340
|
+
platform: os.platform(),
|
|
341
|
+
arch: os.arch(),
|
|
342
|
+
nodeVersion: process.version,
|
|
343
|
+
...errorData
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
await this.apiRequest('/api/errors/report', {
|
|
347
|
+
method: 'POST',
|
|
348
|
+
body: JSON.stringify(errorReport)
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return true;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.warn('UserDatabase: reportError failed:', error.message);
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
238
357
|
// Helper methods (same as server-side)
|
|
239
358
|
generateUserId(email) {
|
|
240
359
|
return crypto.createHash('sha256').update(email.toLowerCase()).digest('hex').substring(0, 16);
|
|
@@ -94,6 +94,20 @@ class UserDatabase {
|
|
|
94
94
|
return this.apiClient.setAdminStatus(userId, isAdmin)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Submit a beta access request
|
|
99
|
+
*/
|
|
100
|
+
async submitBetaRequest(requestData) {
|
|
101
|
+
return this.apiClient.submitBetaRequest(requestData)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if user already has a pending or approved request
|
|
106
|
+
*/
|
|
107
|
+
async checkExistingRequest(email) {
|
|
108
|
+
return this.apiClient.checkExistingRequest(email)
|
|
109
|
+
}
|
|
110
|
+
|
|
97
111
|
/**
|
|
98
112
|
* Check if user is an admin
|
|
99
113
|
*/
|
|
@@ -101,6 +115,20 @@ class UserDatabase {
|
|
|
101
115
|
return this.apiClient.isAdmin(userId)
|
|
102
116
|
}
|
|
103
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Submit user feedback to the admin database
|
|
120
|
+
*/
|
|
121
|
+
async submitFeedback(feedbackData) {
|
|
122
|
+
return this.apiClient.submitFeedback(feedbackData)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Report an error to the admin database for tracking and fixing
|
|
127
|
+
*/
|
|
128
|
+
async reportError(errorData) {
|
|
129
|
+
return this.apiClient.reportError(errorData)
|
|
130
|
+
}
|
|
131
|
+
|
|
104
132
|
// Helper methods - delegate to API client
|
|
105
133
|
generateUserId(email) {
|
|
106
134
|
return this.apiClient.generateUserId(email)
|