vibecodingmachine-core 2026.3.10-1812 → 2026.3.14-1528

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 (45) hide show
  1. package/package.json +1 -1
  2. package/src/agents/AgentAdditionService.js +1 -1
  3. package/src/agents/config/AgentConfigManager.js +1 -1
  4. package/src/agents/config-managers/DefaultConfig.js +1 -1
  5. package/src/auth/access-denied.html +119 -119
  6. package/src/auth/shared-auth-storage.js +267 -267
  7. package/src/autonomous-mode/feature-implementer.cjs +70 -70
  8. package/src/autonomous-mode/feature-implementer.js +425 -425
  9. package/src/beta-request.js +160 -160
  10. package/src/chat-management/chat-manager.cjs +71 -71
  11. package/src/chat-management/chat-manager.js +342 -342
  12. package/src/compliance/compliance-prompt.js +183 -183
  13. package/src/ide-integration/aider-cli-manager.cjs +850 -850
  14. package/src/ide-integration/applescript-manager.cjs +3225 -3215
  15. package/src/ide-integration/applescript-utils.js +314 -314
  16. package/src/ide-integration/cdp-manager.cjs +221 -221
  17. package/src/ide-integration/claude-code-cli-manager.cjs +456 -456
  18. package/src/ide-integration/cline-cli-manager.cjs +2252 -2252
  19. package/src/ide-integration/continue-cli-manager.js +431 -431
  20. package/src/ide-integration/provider-manager.cjs +664 -595
  21. package/src/ide-integration/quota-detector.cjs +399 -399
  22. package/src/ide-integration/windows-automation-manager.js +235 -87
  23. package/src/index.cjs +143 -142
  24. package/src/llm/direct-llm-manager.cjs +1299 -1299
  25. package/src/localization/index.js +147 -147
  26. package/src/quota-management/index.js +108 -108
  27. package/src/requirement-numbering.js +164 -164
  28. package/src/sync/aws-setup.js +445 -445
  29. package/src/ui/ButtonComponents.js +247 -247
  30. package/src/ui/ChatInterface.js +499 -499
  31. package/src/ui/StateManager.js +259 -259
  32. package/src/utils/audit-logger.cjs +116 -116
  33. package/src/utils/config-helpers.cjs +94 -94
  34. package/src/utils/config-helpers.js +94 -94
  35. package/src/utils/env-helpers.js +54 -54
  36. package/src/utils/error-reporter.js +117 -117
  37. package/src/utils/gcloud-auth.cjs +394 -394
  38. package/src/utils/git-branch-manager.js +278 -278
  39. package/src/utils/logger.cjs +193 -193
  40. package/src/utils/logger.js +191 -191
  41. package/src/utils/repo-helpers.cjs +120 -120
  42. package/src/utils/repo-helpers.js +120 -120
  43. package/src/utils/status-formatter.js +135 -0
  44. package/src/utils/update-checker.js +246 -246
  45. package/src/utils/version-checker.js +170 -170
@@ -1,267 +1,267 @@
1
- const os = require('os');
2
- const path = require('path');
3
- const fs = require('fs-extra');
4
-
5
- const CONFIG_DIR = path.join(os.homedir(), '.config', 'vibecodingmachine');
6
- const TOKEN_FILE = path.join(CONFIG_DIR, 'token.json');
7
- const PROFILE_FILE = path.join(CONFIG_DIR, 'profile.json');
8
- const USAGE_FILE = path.join(CONFIG_DIR, 'usage.json');
9
-
10
- /**
11
- * Shared authentication storage for both CLI and Electron
12
- * Stores tokens and profiles in ~/.config/vibecodingmachine/
13
- */
14
- class SharedAuthStorage {
15
- /**
16
- * Decode JWT token (simple base64 decode, no verification)
17
- */
18
- decodeJWT(token) {
19
- const parts = token.split('.');
20
- if (parts.length !== 3) throw new Error('Invalid JWT');
21
-
22
- const payload = Buffer.from(parts[1], 'base64').toString('utf8');
23
- return JSON.parse(payload);
24
- }
25
-
26
- /**
27
- * Check if authenticated
28
- */
29
- async isAuthenticated() {
30
- const token = await this.getToken();
31
- if (!token) return false;
32
-
33
- try {
34
- // Decode JWT to check expiration
35
- const payload = this.decodeJWT(token);
36
- const now = Math.floor(Date.now() / 1000);
37
-
38
- if (payload.exp && payload.exp > now) {
39
- return true;
40
- }
41
- } catch (error) {
42
- return false;
43
- }
44
-
45
- return false;
46
- }
47
-
48
- /**
49
- * Get stored token (returns id_token for backward compatibility)
50
- */
51
- async getToken() {
52
- try {
53
- if (await fs.pathExists(TOKEN_FILE)) {
54
- const data = await fs.readJson(TOKEN_FILE);
55
- // If data.token exists (old format), return it
56
- // If data.id_token exists (new format), return it
57
- return data.id_token || data.token;
58
- }
59
- } catch (error) {
60
- console.error('Failed to read token:', error);
61
- }
62
- return null;
63
- }
64
-
65
- /**
66
- * Get stored refresh token
67
- */
68
- async getRefreshToken() {
69
- try {
70
- if (await fs.pathExists(TOKEN_FILE)) {
71
- const data = await fs.readJson(TOKEN_FILE);
72
- return data.refresh_token;
73
- }
74
- } catch (error) {
75
- console.error('Failed to read refresh token:', error);
76
- }
77
- return null;
78
- }
79
-
80
- /**
81
- * Save token (supports both string token and object with tokens)
82
- */
83
- async saveToken(tokenOrTokens) {
84
- await fs.ensureDir(CONFIG_DIR);
85
-
86
- let tokenData = {};
87
- let idToken = '';
88
-
89
- if (typeof tokenOrTokens === 'string') {
90
- // Legacy format: just the ID token
91
- tokenData = { token: tokenOrTokens, id_token: tokenOrTokens };
92
- idToken = tokenOrTokens;
93
- } else {
94
- // New format: object with id_token, access_token, refresh_token
95
- tokenData = {
96
- id_token: tokenOrTokens.id_token,
97
- access_token: tokenOrTokens.access_token,
98
- refresh_token: tokenOrTokens.refresh_token,
99
- // Keep legacy field for backward compatibility
100
- token: tokenOrTokens.id_token
101
- };
102
- idToken = tokenOrTokens.id_token;
103
- }
104
-
105
- await fs.writeJson(TOKEN_FILE, tokenData, { spaces: 2 });
106
-
107
- // Also save user profile from token
108
- try {
109
- const payload = this.decodeJWT(idToken);
110
- const email = payload.email;
111
-
112
- // Note: Beta access control is handled server-side by Cognito Pre-Auth Lambda
113
- // If we reached here, the user has already passed beta allowlist check
114
-
115
- const profile = {
116
- userId: payload.sub,
117
- email: email,
118
- name: payload.name || email,
119
- picture: payload.picture,
120
- tier: 'free', // Default, will be updated later
121
- maxIterations: 10
122
- };
123
- await this.saveUserProfile(profile);
124
- } catch (error) {
125
- console.error('Failed to save user profile:', error);
126
- throw error;
127
- }
128
- }
129
-
130
- /**
131
- * Delete token
132
- */
133
- async deleteToken() {
134
- if (await fs.pathExists(TOKEN_FILE)) {
135
- await fs.remove(TOKEN_FILE);
136
- }
137
- if (await fs.pathExists(PROFILE_FILE)) {
138
- await fs.remove(PROFILE_FILE);
139
- }
140
- }
141
-
142
- /**
143
- * Get user profile
144
- */
145
- async getUserProfile() {
146
- try {
147
- if (await fs.pathExists(PROFILE_FILE)) {
148
- return await fs.readJson(PROFILE_FILE);
149
- }
150
- } catch (error) {
151
- console.error('Failed to read profile:', error);
152
- }
153
- return null;
154
- }
155
-
156
- /**
157
- * Save user profile
158
- */
159
- async saveUserProfile(profile) {
160
- await fs.ensureDir(CONFIG_DIR);
161
- await fs.writeJson(PROFILE_FILE, profile, { spaces: 2 });
162
- }
163
-
164
- /**
165
- * Check if user can run auto mode (quota check)
166
- */
167
- async canRunAutoMode() {
168
- const token = await this.getToken();
169
- if (!token) {
170
- return { canRun: false, reason: 'Not authenticated' };
171
- }
172
-
173
- const profile = await this.getUserProfile();
174
- if (!profile) {
175
- return { canRun: false, reason: 'No user profile found' };
176
- }
177
-
178
- // Get today's usage from local tracking
179
- const today = new Date().toISOString().split('T')[0];
180
- let usage = 0;
181
- try {
182
- if (await fs.pathExists(USAGE_FILE)) {
183
- const usageData = await fs.readJson(USAGE_FILE);
184
- usage = usageData[today] || 0;
185
- }
186
- } catch (error) {
187
- console.error('Error reading usage:', error);
188
- }
189
-
190
- // Check if user can run based on tier
191
- const isPremium = profile.tier === 'premium';
192
- const maxIterations = isPremium ? 999999 : (profile.maxIterations || 10);
193
-
194
- if (!isPremium && usage >= maxIterations) {
195
- return {
196
- canRun: false,
197
- reason: `Daily limit reached (${usage}/${maxIterations})`,
198
- tier: profile.tier,
199
- todayUsage: usage,
200
- maxIterations: maxIterations
201
- };
202
- }
203
-
204
- return {
205
- canRun: true,
206
- tier: profile.tier,
207
- features: {
208
- unlimitedIterations: isPremium,
209
- cloudSync: isPremium,
210
- mobileApp: isPremium
211
- },
212
- todayUsage: usage,
213
- maxIterations: maxIterations
214
- };
215
- }
216
-
217
- /**
218
- * Log iteration (for quota tracking)
219
- */
220
- async logIteration() {
221
- const today = new Date().toISOString().split('T')[0];
222
-
223
- try {
224
- await fs.ensureDir(CONFIG_DIR);
225
-
226
- let usageData = {};
227
- if (await fs.pathExists(USAGE_FILE)) {
228
- usageData = await fs.readJson(USAGE_FILE);
229
- }
230
-
231
- usageData[today] = (usageData[today] || 0) + 1;
232
-
233
- await fs.writeJson(USAGE_FILE, usageData, { spaces: 2 });
234
- return true;
235
- } catch (error) {
236
- console.error('Error logging iteration:', error);
237
- return false;
238
- }
239
- }
240
-
241
- /**
242
- * Activate license key
243
- */
244
- async activateLicense(licenseKey) {
245
- // TODO: Call backend API to validate and activate license
246
- // For now, just upgrade to premium locally
247
- const profile = await this.getUserProfile();
248
- if (profile) {
249
- profile.tier = 'premium';
250
- profile.maxIterations = 999999;
251
- profile.licenseKey = licenseKey;
252
- await this.saveUserProfile(profile);
253
- return { success: true, tier: 'premium' };
254
- }
255
- return { success: false, error: 'Not authenticated' };
256
- }
257
-
258
- /**
259
- * Logout
260
- */
261
- async logout() {
262
- await this.deleteToken();
263
- return true;
264
- }
265
- }
266
-
267
- module.exports = new SharedAuthStorage();
1
+ const os = require('os');
2
+ const path = require('path');
3
+ const fs = require('fs-extra');
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), '.config', 'vibecodingmachine');
6
+ const TOKEN_FILE = path.join(CONFIG_DIR, 'token.json');
7
+ const PROFILE_FILE = path.join(CONFIG_DIR, 'profile.json');
8
+ const USAGE_FILE = path.join(CONFIG_DIR, 'usage.json');
9
+
10
+ /**
11
+ * Shared authentication storage for both CLI and Electron
12
+ * Stores tokens and profiles in ~/.config/vibecodingmachine/
13
+ */
14
+ class SharedAuthStorage {
15
+ /**
16
+ * Decode JWT token (simple base64 decode, no verification)
17
+ */
18
+ decodeJWT(token) {
19
+ const parts = token.split('.');
20
+ if (parts.length !== 3) throw new Error('Invalid JWT');
21
+
22
+ const payload = Buffer.from(parts[1], 'base64').toString('utf8');
23
+ return JSON.parse(payload);
24
+ }
25
+
26
+ /**
27
+ * Check if authenticated
28
+ */
29
+ async isAuthenticated() {
30
+ const token = await this.getToken();
31
+ if (!token) return false;
32
+
33
+ try {
34
+ // Decode JWT to check expiration
35
+ const payload = this.decodeJWT(token);
36
+ const now = Math.floor(Date.now() / 1000);
37
+
38
+ if (payload.exp && payload.exp > now) {
39
+ return true;
40
+ }
41
+ } catch (error) {
42
+ return false;
43
+ }
44
+
45
+ return false;
46
+ }
47
+
48
+ /**
49
+ * Get stored token (returns id_token for backward compatibility)
50
+ */
51
+ async getToken() {
52
+ try {
53
+ if (await fs.pathExists(TOKEN_FILE)) {
54
+ const data = await fs.readJson(TOKEN_FILE);
55
+ // If data.token exists (old format), return it
56
+ // If data.id_token exists (new format), return it
57
+ return data.id_token || data.token;
58
+ }
59
+ } catch (error) {
60
+ console.error('Failed to read token:', error);
61
+ }
62
+ return null;
63
+ }
64
+
65
+ /**
66
+ * Get stored refresh token
67
+ */
68
+ async getRefreshToken() {
69
+ try {
70
+ if (await fs.pathExists(TOKEN_FILE)) {
71
+ const data = await fs.readJson(TOKEN_FILE);
72
+ return data.refresh_token;
73
+ }
74
+ } catch (error) {
75
+ console.error('Failed to read refresh token:', error);
76
+ }
77
+ return null;
78
+ }
79
+
80
+ /**
81
+ * Save token (supports both string token and object with tokens)
82
+ */
83
+ async saveToken(tokenOrTokens) {
84
+ await fs.ensureDir(CONFIG_DIR);
85
+
86
+ let tokenData = {};
87
+ let idToken = '';
88
+
89
+ if (typeof tokenOrTokens === 'string') {
90
+ // Legacy format: just the ID token
91
+ tokenData = { token: tokenOrTokens, id_token: tokenOrTokens };
92
+ idToken = tokenOrTokens;
93
+ } else {
94
+ // New format: object with id_token, access_token, refresh_token
95
+ tokenData = {
96
+ id_token: tokenOrTokens.id_token,
97
+ access_token: tokenOrTokens.access_token,
98
+ refresh_token: tokenOrTokens.refresh_token,
99
+ // Keep legacy field for backward compatibility
100
+ token: tokenOrTokens.id_token
101
+ };
102
+ idToken = tokenOrTokens.id_token;
103
+ }
104
+
105
+ await fs.writeJson(TOKEN_FILE, tokenData, { spaces: 2 });
106
+
107
+ // Also save user profile from token
108
+ try {
109
+ const payload = this.decodeJWT(idToken);
110
+ const email = payload.email;
111
+
112
+ // Note: Beta access control is handled server-side by Cognito Pre-Auth Lambda
113
+ // If we reached here, the user has already passed beta allowlist check
114
+
115
+ const profile = {
116
+ userId: payload.sub,
117
+ email: email,
118
+ name: payload.name || email,
119
+ picture: payload.picture,
120
+ tier: 'free', // Default, will be updated later
121
+ maxIterations: 10
122
+ };
123
+ await this.saveUserProfile(profile);
124
+ } catch (error) {
125
+ console.error('Failed to save user profile:', error);
126
+ throw error;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Delete token
132
+ */
133
+ async deleteToken() {
134
+ if (await fs.pathExists(TOKEN_FILE)) {
135
+ await fs.remove(TOKEN_FILE);
136
+ }
137
+ if (await fs.pathExists(PROFILE_FILE)) {
138
+ await fs.remove(PROFILE_FILE);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Get user profile
144
+ */
145
+ async getUserProfile() {
146
+ try {
147
+ if (await fs.pathExists(PROFILE_FILE)) {
148
+ return await fs.readJson(PROFILE_FILE);
149
+ }
150
+ } catch (error) {
151
+ console.error('Failed to read profile:', error);
152
+ }
153
+ return null;
154
+ }
155
+
156
+ /**
157
+ * Save user profile
158
+ */
159
+ async saveUserProfile(profile) {
160
+ await fs.ensureDir(CONFIG_DIR);
161
+ await fs.writeJson(PROFILE_FILE, profile, { spaces: 2 });
162
+ }
163
+
164
+ /**
165
+ * Check if user can run auto mode (quota check)
166
+ */
167
+ async canRunAutoMode() {
168
+ const token = await this.getToken();
169
+ if (!token) {
170
+ return { canRun: false, reason: 'Not authenticated' };
171
+ }
172
+
173
+ const profile = await this.getUserProfile();
174
+ if (!profile) {
175
+ return { canRun: false, reason: 'No user profile found' };
176
+ }
177
+
178
+ // Get today's usage from local tracking
179
+ const today = new Date().toISOString().split('T')[0];
180
+ let usage = 0;
181
+ try {
182
+ if (await fs.pathExists(USAGE_FILE)) {
183
+ const usageData = await fs.readJson(USAGE_FILE);
184
+ usage = usageData[today] || 0;
185
+ }
186
+ } catch (error) {
187
+ console.error('Error reading usage:', error);
188
+ }
189
+
190
+ // Check if user can run based on tier
191
+ const isPremium = profile.tier === 'premium';
192
+ const maxIterations = isPremium ? 999999 : (profile.maxIterations || 10);
193
+
194
+ if (!isPremium && usage >= maxIterations) {
195
+ return {
196
+ canRun: false,
197
+ reason: `Daily limit reached (${usage}/${maxIterations})`,
198
+ tier: profile.tier,
199
+ todayUsage: usage,
200
+ maxIterations: maxIterations
201
+ };
202
+ }
203
+
204
+ return {
205
+ canRun: true,
206
+ tier: profile.tier,
207
+ features: {
208
+ unlimitedIterations: isPremium,
209
+ cloudSync: isPremium,
210
+ mobileApp: isPremium
211
+ },
212
+ todayUsage: usage,
213
+ maxIterations: maxIterations
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Log iteration (for quota tracking)
219
+ */
220
+ async logIteration() {
221
+ const today = new Date().toISOString().split('T')[0];
222
+
223
+ try {
224
+ await fs.ensureDir(CONFIG_DIR);
225
+
226
+ let usageData = {};
227
+ if (await fs.pathExists(USAGE_FILE)) {
228
+ usageData = await fs.readJson(USAGE_FILE);
229
+ }
230
+
231
+ usageData[today] = (usageData[today] || 0) + 1;
232
+
233
+ await fs.writeJson(USAGE_FILE, usageData, { spaces: 2 });
234
+ return true;
235
+ } catch (error) {
236
+ console.error('Error logging iteration:', error);
237
+ return false;
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Activate license key
243
+ */
244
+ async activateLicense(licenseKey) {
245
+ // TODO: Call backend API to validate and activate license
246
+ // For now, just upgrade to premium locally
247
+ const profile = await this.getUserProfile();
248
+ if (profile) {
249
+ profile.tier = 'premium';
250
+ profile.maxIterations = 999999;
251
+ profile.licenseKey = licenseKey;
252
+ await this.saveUserProfile(profile);
253
+ return { success: true, tier: 'premium' };
254
+ }
255
+ return { success: false, error: 'Not authenticated' };
256
+ }
257
+
258
+ /**
259
+ * Logout
260
+ */
261
+ async logout() {
262
+ await this.deleteToken();
263
+ return true;
264
+ }
265
+ }
266
+
267
+ module.exports = new SharedAuthStorage();
@@ -1,70 +1,70 @@
1
- // @vibecodingmachine/core - Feature Implementer (CommonJS) - Stub
2
- // Handles autonomous mode feature implementation and development workflow
3
-
4
- class FeatureImplementer {
5
- constructor(options = {}) {
6
- this.logger = options.logger || console;
7
- this.electronAPI = options.electronAPI || null;
8
- this.onStatusUpdate = options.onStatusUpdate || (() => {});
9
- this.onProgressUpdate = options.onProgressUpdate || (() => {});
10
- this.onMessageUpdate = options.onMessageUpdate || (() => {});
11
- this.onErrorUpdate = options.onErrorUpdate || (() => {});
12
- this.onCompleteUpdate = options.onCompleteUpdate || (() => {});
13
- }
14
-
15
- async startAutonomousMode(ide, tabId) {
16
- this.logger.log('startAutonomousMode stub called for:', ide, tabId);
17
- return {
18
- success: true,
19
- progress: 0,
20
- completed: false,
21
- taskCount: 0
22
- };
23
- }
24
-
25
- stopAutonomousMode() {
26
- this.logger.log('stopAutonomousMode stub');
27
- }
28
-
29
- pauseAutonomousMode() {
30
- this.logger.log('pauseAutonomousMode stub');
31
- }
32
-
33
- resumeAutonomousMode() {
34
- this.logger.log('resumeAutonomousMode stub');
35
- }
36
-
37
- getState() {
38
- return {
39
- isAutonomousMode: false,
40
- isPaused: false,
41
- isStopped: false,
42
- currentProgress: 0,
43
- taskCount: 0
44
- };
45
- }
46
-
47
- setElectronAPI(electronAPI) {
48
- this.electronAPI = electronAPI;
49
- }
50
-
51
- setCallbacks(callbacks) {
52
- if (callbacks.onStatusUpdate) {
53
- this.onStatusUpdate = callbacks.onStatusUpdate;
54
- }
55
- if (callbacks.onProgressUpdate) {
56
- this.onProgressUpdate = callbacks.onProgressUpdate;
57
- }
58
- if (callbacks.onMessageUpdate) {
59
- this.onMessageUpdate = callbacks.onMessageUpdate;
60
- }
61
- if (callbacks.onErrorUpdate) {
62
- this.onErrorUpdate = callbacks.onErrorUpdate;
63
- }
64
- if (callbacks.onCompleteUpdate) {
65
- this.onCompleteUpdate = callbacks.onCompleteUpdate;
66
- }
67
- }
68
- }
69
-
70
- module.exports = { FeatureImplementer };
1
+ // @vibecodingmachine/core - Feature Implementer (CommonJS) - Stub
2
+ // Handles autonomous mode feature implementation and development workflow
3
+
4
+ class FeatureImplementer {
5
+ constructor(options = {}) {
6
+ this.logger = options.logger || console;
7
+ this.electronAPI = options.electronAPI || null;
8
+ this.onStatusUpdate = options.onStatusUpdate || (() => {});
9
+ this.onProgressUpdate = options.onProgressUpdate || (() => {});
10
+ this.onMessageUpdate = options.onMessageUpdate || (() => {});
11
+ this.onErrorUpdate = options.onErrorUpdate || (() => {});
12
+ this.onCompleteUpdate = options.onCompleteUpdate || (() => {});
13
+ }
14
+
15
+ async startAutonomousMode(ide, tabId) {
16
+ this.logger.log('startAutonomousMode stub called for:', ide, tabId);
17
+ return {
18
+ success: true,
19
+ progress: 0,
20
+ completed: false,
21
+ taskCount: 0
22
+ };
23
+ }
24
+
25
+ stopAutonomousMode() {
26
+ this.logger.log('stopAutonomousMode stub');
27
+ }
28
+
29
+ pauseAutonomousMode() {
30
+ this.logger.log('pauseAutonomousMode stub');
31
+ }
32
+
33
+ resumeAutonomousMode() {
34
+ this.logger.log('resumeAutonomousMode stub');
35
+ }
36
+
37
+ getState() {
38
+ return {
39
+ isAutonomousMode: false,
40
+ isPaused: false,
41
+ isStopped: false,
42
+ currentProgress: 0,
43
+ taskCount: 0
44
+ };
45
+ }
46
+
47
+ setElectronAPI(electronAPI) {
48
+ this.electronAPI = electronAPI;
49
+ }
50
+
51
+ setCallbacks(callbacks) {
52
+ if (callbacks.onStatusUpdate) {
53
+ this.onStatusUpdate = callbacks.onStatusUpdate;
54
+ }
55
+ if (callbacks.onProgressUpdate) {
56
+ this.onProgressUpdate = callbacks.onProgressUpdate;
57
+ }
58
+ if (callbacks.onMessageUpdate) {
59
+ this.onMessageUpdate = callbacks.onMessageUpdate;
60
+ }
61
+ if (callbacks.onErrorUpdate) {
62
+ this.onErrorUpdate = callbacks.onErrorUpdate;
63
+ }
64
+ if (callbacks.onCompleteUpdate) {
65
+ this.onCompleteUpdate = callbacks.onCompleteUpdate;
66
+ }
67
+ }
68
+ }
69
+
70
+ module.exports = { FeatureImplementer };