@wonderwhy-er/desktop-commander 0.2.25 → 0.2.26

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.
@@ -5,6 +5,9 @@ declare class FeatureFlagManager {
5
5
  private cacheMaxAge;
6
6
  private flagUrl;
7
7
  private refreshInterval;
8
+ private freshFetchPromise;
9
+ private resolveFreshFetch;
10
+ private loadedFromCache;
8
11
  constructor();
9
12
  /**
10
13
  * Initialize - load from cache and start background refresh
@@ -22,6 +25,16 @@ declare class FeatureFlagManager {
22
25
  * Manually refresh flags immediately (for testing)
23
26
  */
24
27
  refresh(): Promise<boolean>;
28
+ /**
29
+ * Check if flags were loaded from cache (vs fresh fetch)
30
+ */
31
+ wasLoadedFromCache(): boolean;
32
+ /**
33
+ * Wait for fresh flags to be fetched from network.
34
+ * Use this when you need to ensure flags are loaded before making decisions
35
+ * (e.g., A/B test assignments for new users who don't have a cache yet)
36
+ */
37
+ waitForFreshFlags(): Promise<void>;
25
38
  /**
26
39
  * Load flags from local cache
27
40
  */
@@ -9,11 +9,19 @@ class FeatureFlagManager {
9
9
  this.lastFetch = 0;
10
10
  this.cacheMaxAge = 30 * 60 * 1000;
11
11
  this.refreshInterval = null;
12
+ // Track fresh fetch status for A/B tests that need network flags
13
+ this.freshFetchPromise = null;
14
+ this.resolveFreshFetch = null;
15
+ this.loadedFromCache = false;
12
16
  const configDir = path.dirname(CONFIG_FILE);
13
17
  this.cachePath = path.join(configDir, 'feature-flags.json');
14
18
  // Use production flags
15
19
  this.flagUrl = process.env.DC_FLAG_URL ||
16
20
  'https://desktopcommander.app/flags/v1/production.json';
21
+ // Set up promise for waiting on fresh fetch
22
+ this.freshFetchPromise = new Promise((resolve) => {
23
+ this.resolveFreshFetch = resolve;
24
+ });
17
25
  }
18
26
  /**
19
27
  * Initialize - load from cache and start background refresh
@@ -23,8 +31,17 @@ class FeatureFlagManager {
23
31
  // Load from cache immediately (non-blocking)
24
32
  await this.loadFromCache();
25
33
  // Fetch in background (don't block startup)
26
- this.fetchFlags().catch(err => {
34
+ this.fetchFlags().then(() => {
35
+ // Signal that fresh flags are now available
36
+ if (this.resolveFreshFetch) {
37
+ this.resolveFreshFetch();
38
+ }
39
+ }).catch(err => {
27
40
  logger.debug('Initial flag fetch failed:', err.message);
41
+ // Still resolve the promise so waiters don't hang forever
42
+ if (this.resolveFreshFetch) {
43
+ this.resolveFreshFetch();
44
+ }
28
45
  });
29
46
  // Start periodic refresh every 5 minutes
30
47
  this.refreshInterval = setInterval(() => {
@@ -66,6 +83,22 @@ class FeatureFlagManager {
66
83
  return false;
67
84
  }
68
85
  }
86
+ /**
87
+ * Check if flags were loaded from cache (vs fresh fetch)
88
+ */
89
+ wasLoadedFromCache() {
90
+ return this.loadedFromCache;
91
+ }
92
+ /**
93
+ * Wait for fresh flags to be fetched from network.
94
+ * Use this when you need to ensure flags are loaded before making decisions
95
+ * (e.g., A/B test assignments for new users who don't have a cache yet)
96
+ */
97
+ async waitForFreshFlags() {
98
+ if (this.freshFetchPromise) {
99
+ await this.freshFetchPromise;
100
+ }
101
+ }
69
102
  /**
70
103
  * Load flags from local cache
71
104
  */
@@ -73,6 +106,7 @@ class FeatureFlagManager {
73
106
  try {
74
107
  if (!existsSync(this.cachePath)) {
75
108
  logger.debug('No feature flag cache found');
109
+ this.loadedFromCache = false;
76
110
  return;
77
111
  }
78
112
  const data = await fs.readFile(this.cachePath, 'utf8');
@@ -80,11 +114,13 @@ class FeatureFlagManager {
80
114
  if (config.flags) {
81
115
  this.flags = config.flags;
82
116
  this.lastFetch = Date.now();
117
+ this.loadedFromCache = true;
83
118
  logger.debug(`Loaded ${Object.keys(this.flags).length} feature flags from cache`);
84
119
  }
85
120
  }
86
121
  catch (error) {
87
122
  logger.warning('Failed to load feature flags from cache:', error);
123
+ this.loadedFromCache = false;
88
124
  }
89
125
  }
90
126
  /**
@@ -1,5 +1,6 @@
1
1
  import { configManager } from '../config-manager.js';
2
2
  import { hasFeature } from './ab-test.js';
3
+ import { featureFlagManager } from './feature-flags.js';
3
4
  import { openWelcomePage } from './open-browser.js';
4
5
  import { logToStderr } from './logger.js';
5
6
  /**
@@ -15,6 +16,13 @@ export async function handleWelcomePageOnboarding() {
15
16
  if (!configManager.isFirstRun()) {
16
17
  return;
17
18
  }
19
+ // For new users, we need to wait for feature flags to load from network
20
+ // since they won't have a cache file yet. Without this, hasFeature() would
21
+ // return false (no experiments defined) and all new users go to control.
22
+ if (!featureFlagManager.wasLoadedFromCache()) {
23
+ logToStderr('debug', 'Waiting for feature flags to load...');
24
+ await featureFlagManager.waitForFreshFlags();
25
+ }
18
26
  // Check A/B test assignment
19
27
  const shouldShow = await hasFeature('showOnboardingPage');
20
28
  if (!shouldShow) {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.2.25";
1
+ export declare const VERSION = "0.2.26";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.2.25';
1
+ export const VERSION = '0.2.26';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wonderwhy-er/desktop-commander",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
4
4
  "description": "MCP server for terminal operations and file editing",
5
5
  "mcpName": "io.github.wonderwhy-er/desktop-commander",
6
6
  "license": "MIT",