@wonderwhy-er/desktop-commander 0.2.26 → 0.2.28

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.
@@ -62,6 +62,10 @@ declare class ConfigManager {
62
62
  * Check if this is the first run (config file was just created)
63
63
  */
64
64
  isFirstRun(): boolean;
65
+ /**
66
+ * Get or create a persistent client ID for analytics and A/B tests
67
+ */
68
+ getOrCreateClientId(): Promise<string>;
65
69
  }
66
70
  export declare const configManager: ConfigManager;
67
71
  export {};
@@ -116,7 +116,8 @@ class ConfigManager {
116
116
  allowedDirectories: [],
117
117
  telemetryEnabled: true, // Default to opt-out approach (telemetry on by default)
118
118
  fileWriteLineLimit: 50, // Default line limit for file write operations (changed from 100)
119
- fileReadLineLimit: 1000 // Default line limit for file read operations (changed from character-based)
119
+ fileReadLineLimit: 1000, // Default line limit for file read operations (changed from character-based)
120
+ pendingWelcomeOnboarding: true // New install flag - triggers A/B test for welcome page
120
121
  };
121
122
  }
122
123
  /**
@@ -193,6 +194,18 @@ class ConfigManager {
193
194
  isFirstRun() {
194
195
  return this._isFirstRun;
195
196
  }
197
+ /**
198
+ * Get or create a persistent client ID for analytics and A/B tests
199
+ */
200
+ async getOrCreateClientId() {
201
+ let clientId = await this.getValue('clientId');
202
+ if (!clientId) {
203
+ const { randomUUID } = await import('crypto');
204
+ clientId = randomUUID();
205
+ await this.setValue('clientId', clientId);
206
+ }
207
+ return clientId;
208
+ }
196
209
  }
197
210
  // Export singleton instance
198
211
  export const configManager = new ConfigManager();
@@ -28,7 +28,7 @@ async function getVariant(experimentName) {
28
28
  return existing;
29
29
  }
30
30
  // New assignment based on clientId
31
- const clientId = await configManager.getValue('clientId') || '';
31
+ const clientId = await configManager.getOrCreateClientId();
32
32
  const hash = hashCode(clientId + experimentName);
33
33
  const variantIndex = hash % experiment.variants.length;
34
34
  const variant = experiment.variants[variantIndex];
@@ -1,5 +1,4 @@
1
1
  import { platform } from 'os';
2
- import { randomUUID } from 'crypto';
3
2
  import * as https from 'https';
4
3
  import { configManager } from '../config-manager.js';
5
4
  import { currentClient } from '../server.js';
@@ -13,23 +12,6 @@ catch {
13
12
  }
14
13
  // Will be initialized when needed
15
14
  let uniqueUserId = 'unknown';
16
- // Function to get or create a persistent UUID
17
- async function getOrCreateUUID() {
18
- try {
19
- // Try to get the UUID from the config
20
- let clientId = await configManager.getValue('clientId');
21
- // If it doesn't exist, create a new one and save it
22
- if (!clientId) {
23
- clientId = randomUUID();
24
- await configManager.setValue('clientId', clientId);
25
- }
26
- return clientId;
27
- }
28
- catch (error) {
29
- // Fallback to a random UUID if config operations fail
30
- return randomUUID();
31
- }
32
- }
33
15
  /**
34
16
  * Sanitizes error objects to remove potentially sensitive information like file paths
35
17
  * @param error Error object or string to sanitize
@@ -76,7 +58,7 @@ export const captureBase = async (captureURL, event, properties) => {
76
58
  }
77
59
  // Get or create the client ID if not already initialized
78
60
  if (uniqueUserId === 'unknown') {
79
- uniqueUserId = await getOrCreateUUID();
61
+ uniqueUserId = await configManager.getOrCreateClientId();
80
62
  }
81
63
  // Get current client information for all events
82
64
  let clientContext = {};
@@ -2,7 +2,7 @@
2
2
  * Handle welcome page display for new users (A/B test controlled)
3
3
  *
4
4
  * Only shows to:
5
- * 1. New users (first run - config was just created)
5
+ * 1. New users (pendingWelcomeOnboarding flag set when config created)
6
6
  * 2. Users in the 'showOnboardingPage' A/B variant
7
7
  * 3. Haven't seen it yet
8
8
  */
@@ -3,29 +3,42 @@ import { hasFeature } from './ab-test.js';
3
3
  import { featureFlagManager } from './feature-flags.js';
4
4
  import { openWelcomePage } from './open-browser.js';
5
5
  import { logToStderr } from './logger.js';
6
+ import { capture } from './capture.js';
6
7
  /**
7
8
  * Handle welcome page display for new users (A/B test controlled)
8
9
  *
9
10
  * Only shows to:
10
- * 1. New users (first run - config was just created)
11
+ * 1. New users (pendingWelcomeOnboarding flag set when config created)
11
12
  * 2. Users in the 'showOnboardingPage' A/B variant
12
13
  * 3. Haven't seen it yet
13
14
  */
14
15
  export async function handleWelcomePageOnboarding() {
15
- // Only for brand new users (config just created)
16
- if (!configManager.isFirstRun()) {
17
- return;
16
+ // Check if this is a new install pending A/B decision
17
+ // This flag is set when config is first created and survives process restarts
18
+ const pending = await configManager.getValue('pendingWelcomeOnboarding');
19
+ if (!pending) {
20
+ return; // Existing user or already processed
18
21
  }
22
+ // Track that we have a first-run user attempting onboarding
23
+ const loadedFromCache = featureFlagManager.wasLoadedFromCache();
19
24
  // For new users, we need to wait for feature flags to load from network
20
25
  // since they won't have a cache file yet. Without this, hasFeature() would
21
26
  // return false (no experiments defined) and all new users go to control.
22
- if (!featureFlagManager.wasLoadedFromCache()) {
27
+ if (!loadedFromCache) {
23
28
  logToStderr('debug', 'Waiting for feature flags to load...');
24
29
  await featureFlagManager.waitForFreshFlags();
25
30
  }
26
31
  // Check A/B test assignment
27
32
  const shouldShow = await hasFeature('showOnboardingPage');
33
+ // Track the A/B decision
34
+ capture('server_welcome_page_ab_decision', {
35
+ variant: shouldShow ? 'treatment' : 'control',
36
+ loaded_from_cache: loadedFromCache
37
+ });
28
38
  if (!shouldShow) {
39
+ // Mark as control group for analytics - this will be sent with all future events
40
+ await configManager.setValue('sawOnboardingPage', false);
41
+ await configManager.setValue('pendingWelcomeOnboarding', false);
29
42
  logToStderr('debug', 'Welcome page skipped (A/B: noOnboardingPage)');
30
43
  return;
31
44
  }
@@ -37,9 +50,14 @@ export async function handleWelcomePageOnboarding() {
37
50
  try {
38
51
  await openWelcomePage();
39
52
  await configManager.setValue('sawOnboardingPage', true);
53
+ await configManager.setValue('pendingWelcomeOnboarding', false);
54
+ capture('server_welcome_page_opened', { success: true });
40
55
  logToStderr('info', 'Welcome page opened');
41
56
  }
42
57
  catch (e) {
58
+ // Still clear the pending flag even on failure - don't retry forever
59
+ await configManager.setValue('pendingWelcomeOnboarding', false);
60
+ capture('server_welcome_page_opened', { success: false, error: e instanceof Error ? e.message : String(e) });
43
61
  logToStderr('warning', `Failed to open welcome page: ${e instanceof Error ? e.message : e}`);
44
62
  }
45
63
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.2.26";
1
+ export declare const VERSION = "0.2.28";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.2.26';
1
+ export const VERSION = '0.2.28';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wonderwhy-er/desktop-commander",
3
- "version": "0.2.26",
3
+ "version": "0.2.28",
4
4
  "description": "MCP server for terminal operations and file editing",
5
5
  "mcpName": "io.github.wonderwhy-er/desktop-commander",
6
6
  "license": "MIT",