@wonderwhy-er/desktop-commander 0.2.24 → 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.
- package/dist/config-manager.d.ts +5 -0
- package/dist/config-manager.js +9 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +28 -0
- package/dist/utils/ab-test.d.ts +8 -0
- package/dist/utils/ab-test.js +76 -0
- package/dist/utils/capture.js +5 -0
- package/dist/utils/feature-flags.d.ts +13 -0
- package/dist/utils/feature-flags.js +37 -1
- package/dist/utils/open-browser.d.ts +9 -0
- package/dist/utils/open-browser.js +43 -0
- package/dist/utils/usageTracker.js +6 -0
- package/dist/utils/welcome-onboarding.d.ts +9 -0
- package/dist/utils/welcome-onboarding.js +45 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/config-manager.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ declare class ConfigManager {
|
|
|
20
20
|
private configPath;
|
|
21
21
|
private config;
|
|
22
22
|
private initialized;
|
|
23
|
+
private _isFirstRun;
|
|
23
24
|
constructor();
|
|
24
25
|
/**
|
|
25
26
|
* Initialize configuration - load from disk or create default
|
|
@@ -57,6 +58,10 @@ declare class ConfigManager {
|
|
|
57
58
|
* Reset configuration to defaults
|
|
58
59
|
*/
|
|
59
60
|
resetConfig(): Promise<ServerConfig>;
|
|
61
|
+
/**
|
|
62
|
+
* Check if this is the first run (config file was just created)
|
|
63
|
+
*/
|
|
64
|
+
isFirstRun(): boolean;
|
|
60
65
|
}
|
|
61
66
|
export declare const configManager: ConfigManager;
|
|
62
67
|
export {};
|
package/dist/config-manager.js
CHANGED
|
@@ -12,6 +12,7 @@ class ConfigManager {
|
|
|
12
12
|
constructor() {
|
|
13
13
|
this.config = {};
|
|
14
14
|
this.initialized = false;
|
|
15
|
+
this._isFirstRun = false; // Track if this is the first run (config was just created)
|
|
15
16
|
// Get user's home directory
|
|
16
17
|
// Define config directory and file paths
|
|
17
18
|
this.configPath = CONFIG_FILE;
|
|
@@ -34,10 +35,12 @@ class ConfigManager {
|
|
|
34
35
|
// Load existing config
|
|
35
36
|
const configData = await fs.readFile(this.configPath, 'utf8');
|
|
36
37
|
this.config = JSON.parse(configData);
|
|
38
|
+
this._isFirstRun = false;
|
|
37
39
|
}
|
|
38
40
|
catch (error) {
|
|
39
41
|
// Config file doesn't exist, create default
|
|
40
42
|
this.config = this.getDefaultConfig();
|
|
43
|
+
this._isFirstRun = true; // This is a first run!
|
|
41
44
|
await this.saveConfig();
|
|
42
45
|
}
|
|
43
46
|
this.config['version'] = VERSION;
|
|
@@ -184,6 +187,12 @@ class ConfigManager {
|
|
|
184
187
|
await this.saveConfig();
|
|
185
188
|
return { ...this.config };
|
|
186
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Check if this is the first run (config file was just created)
|
|
192
|
+
*/
|
|
193
|
+
isFirstRun() {
|
|
194
|
+
return this._isFirstRun;
|
|
195
|
+
}
|
|
187
196
|
}
|
|
188
197
|
// Export singleton instance
|
|
189
198
|
export const configManager = new ConfigManager();
|
package/dist/server.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ export declare const server: Server<{
|
|
|
7
7
|
_meta?: {
|
|
8
8
|
[x: string]: unknown;
|
|
9
9
|
progressToken?: string | number | undefined;
|
|
10
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
11
|
+
taskId: string;
|
|
12
|
+
} | undefined;
|
|
10
13
|
} | undefined;
|
|
11
14
|
} | undefined;
|
|
12
15
|
}, {
|
|
@@ -15,12 +18,20 @@ export declare const server: Server<{
|
|
|
15
18
|
[x: string]: unknown;
|
|
16
19
|
_meta?: {
|
|
17
20
|
[x: string]: unknown;
|
|
21
|
+
progressToken?: string | number | undefined;
|
|
22
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
23
|
+
taskId: string;
|
|
24
|
+
} | undefined;
|
|
18
25
|
} | undefined;
|
|
19
26
|
} | undefined;
|
|
20
27
|
}, {
|
|
21
28
|
[x: string]: unknown;
|
|
22
29
|
_meta?: {
|
|
23
30
|
[x: string]: unknown;
|
|
31
|
+
progressToken?: string | number | undefined;
|
|
32
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
33
|
+
taskId: string;
|
|
34
|
+
} | undefined;
|
|
24
35
|
} | undefined;
|
|
25
36
|
}>;
|
|
26
37
|
declare let currentClient: {
|
package/dist/server.js
CHANGED
|
@@ -17,6 +17,7 @@ import { trackToolCall } from './utils/trackTools.js';
|
|
|
17
17
|
import { usageTracker } from './utils/usageTracker.js';
|
|
18
18
|
import { processDockerPrompt } from './utils/dockerPrompt.js';
|
|
19
19
|
import { toolHistory } from './utils/toolHistory.js';
|
|
20
|
+
import { handleWelcomePageOnboarding } from './utils/welcome-onboarding.js';
|
|
20
21
|
import { VERSION } from './version.js';
|
|
21
22
|
import { capture, capture_call_tool } from "./utils/capture.js";
|
|
22
23
|
import { logToStderr, logger } from './utils/logger.js';
|
|
@@ -77,6 +78,10 @@ server.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
|
77
78
|
}
|
|
78
79
|
// Defer client connection message until after initialization
|
|
79
80
|
deferLog('info', `Client connected: ${currentClient.name} v${currentClient.version}`);
|
|
81
|
+
// Welcome page for new claude-ai users (A/B test controlled)
|
|
82
|
+
if (currentClient.name === 'claude-ai' && !global.disableOnboarding) {
|
|
83
|
+
await handleWelcomePageOnboarding();
|
|
84
|
+
}
|
|
80
85
|
}
|
|
81
86
|
capture('run_server_mcp_initialized');
|
|
82
87
|
// Return standard initialization response
|
|
@@ -360,6 +365,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
360
365
|
${PATH_GUIDANCE}
|
|
361
366
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
362
367
|
inputSchema: zodToJsonSchema(CreateDirectoryArgsSchema),
|
|
368
|
+
annotations: {
|
|
369
|
+
title: "Create Directory",
|
|
370
|
+
readOnlyHint: false,
|
|
371
|
+
destructiveHint: false,
|
|
372
|
+
},
|
|
363
373
|
},
|
|
364
374
|
{
|
|
365
375
|
name: "list_directory",
|
|
@@ -499,6 +509,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
499
509
|
${PATH_GUIDANCE}
|
|
500
510
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
501
511
|
inputSchema: zodToJsonSchema(StartSearchArgsSchema),
|
|
512
|
+
annotations: {
|
|
513
|
+
title: "Start Search",
|
|
514
|
+
readOnlyHint: true,
|
|
515
|
+
},
|
|
502
516
|
},
|
|
503
517
|
{
|
|
504
518
|
name: "get_more_search_results",
|
|
@@ -544,6 +558,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
544
558
|
|
|
545
559
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
546
560
|
inputSchema: zodToJsonSchema(StopSearchArgsSchema),
|
|
561
|
+
annotations: {
|
|
562
|
+
title: "Stop Search",
|
|
563
|
+
readOnlyHint: false,
|
|
564
|
+
destructiveHint: false,
|
|
565
|
+
},
|
|
547
566
|
},
|
|
548
567
|
{
|
|
549
568
|
name: "list_searches",
|
|
@@ -958,6 +977,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
958
977
|
|
|
959
978
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
960
979
|
inputSchema: zodToJsonSchema(GiveFeedbackArgsSchema),
|
|
980
|
+
annotations: {
|
|
981
|
+
title: "Give Feedback",
|
|
982
|
+
readOnlyHint: false,
|
|
983
|
+
openWorldHint: true,
|
|
984
|
+
},
|
|
961
985
|
},
|
|
962
986
|
{
|
|
963
987
|
name: "get_prompts",
|
|
@@ -985,6 +1009,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
985
1009
|
|
|
986
1010
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
987
1011
|
inputSchema: zodToJsonSchema(GetPromptsArgsSchema),
|
|
1012
|
+
annotations: {
|
|
1013
|
+
title: "Get Prompts",
|
|
1014
|
+
readOnlyHint: true,
|
|
1015
|
+
},
|
|
988
1016
|
}
|
|
989
1017
|
];
|
|
990
1018
|
// Filter tools based on current client
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a feature (variant name) is enabled for current user
|
|
3
|
+
*/
|
|
4
|
+
export declare function hasFeature(featureName: string): Promise<boolean>;
|
|
5
|
+
/**
|
|
6
|
+
* Get all A/B test assignments for analytics (reads from config)
|
|
7
|
+
*/
|
|
8
|
+
export declare function getABTestAssignments(): Promise<Record<string, string>>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { configManager } from '../config-manager.js';
|
|
2
|
+
import { featureFlagManager } from './feature-flags.js';
|
|
3
|
+
// Cache for variant assignments (loaded once per session)
|
|
4
|
+
const variantCache = {};
|
|
5
|
+
/**
|
|
6
|
+
* Get experiments config from feature flags
|
|
7
|
+
*/
|
|
8
|
+
function getExperiments() {
|
|
9
|
+
return featureFlagManager.get('experiments', {});
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get user's variant for an experiment (cached, deterministic)
|
|
13
|
+
*/
|
|
14
|
+
async function getVariant(experimentName) {
|
|
15
|
+
const experiments = getExperiments();
|
|
16
|
+
const experiment = experiments[experimentName];
|
|
17
|
+
if (!experiment?.variants?.length)
|
|
18
|
+
return null;
|
|
19
|
+
// Check cache
|
|
20
|
+
if (variantCache[experimentName]) {
|
|
21
|
+
return variantCache[experimentName];
|
|
22
|
+
}
|
|
23
|
+
// Check persisted assignment
|
|
24
|
+
const configKey = `abTest_${experimentName}`;
|
|
25
|
+
const existing = await configManager.getValue(configKey);
|
|
26
|
+
if (existing && experiment.variants.includes(existing)) {
|
|
27
|
+
variantCache[experimentName] = existing;
|
|
28
|
+
return existing;
|
|
29
|
+
}
|
|
30
|
+
// New assignment based on clientId
|
|
31
|
+
const clientId = await configManager.getValue('clientId') || '';
|
|
32
|
+
const hash = hashCode(clientId + experimentName);
|
|
33
|
+
const variantIndex = hash % experiment.variants.length;
|
|
34
|
+
const variant = experiment.variants[variantIndex];
|
|
35
|
+
await configManager.setValue(configKey, variant);
|
|
36
|
+
variantCache[experimentName] = variant;
|
|
37
|
+
return variant;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if a feature (variant name) is enabled for current user
|
|
41
|
+
*/
|
|
42
|
+
export async function hasFeature(featureName) {
|
|
43
|
+
const experiments = getExperiments();
|
|
44
|
+
if (!experiments || typeof experiments !== 'object')
|
|
45
|
+
return false;
|
|
46
|
+
for (const [expName, experiment] of Object.entries(experiments)) {
|
|
47
|
+
if (experiment?.variants?.includes(featureName)) {
|
|
48
|
+
const variant = await getVariant(expName);
|
|
49
|
+
return variant === featureName;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get all A/B test assignments for analytics (reads from config)
|
|
56
|
+
*/
|
|
57
|
+
export async function getABTestAssignments() {
|
|
58
|
+
const experiments = getExperiments();
|
|
59
|
+
const assignments = {};
|
|
60
|
+
for (const expName of Object.keys(experiments)) {
|
|
61
|
+
const configKey = `abTest_${expName}`;
|
|
62
|
+
const variant = await configManager.getValue(configKey);
|
|
63
|
+
if (variant) {
|
|
64
|
+
assignments[`ab_${expName}`] = variant;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return assignments;
|
|
68
|
+
}
|
|
69
|
+
function hashCode(str) {
|
|
70
|
+
let hash = 0;
|
|
71
|
+
for (let i = 0; i < str.length; i++) {
|
|
72
|
+
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
73
|
+
hash |= 0;
|
|
74
|
+
}
|
|
75
|
+
return Math.abs(hash);
|
|
76
|
+
}
|
package/dist/utils/capture.js
CHANGED
|
@@ -86,6 +86,11 @@ export const captureBase = async (captureURL, event, properties) => {
|
|
|
86
86
|
client_version: currentClient.version,
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
|
+
// Track if user saw onboarding page
|
|
90
|
+
const sawOnboardingPage = await configManager.getValue('sawOnboardingPage');
|
|
91
|
+
if (sawOnboardingPage !== undefined) {
|
|
92
|
+
clientContext = { ...clientContext, saw_onboarding_page: sawOnboardingPage };
|
|
93
|
+
}
|
|
89
94
|
// Create a deep copy of properties to avoid modifying the original objects
|
|
90
95
|
// This ensures we don't alter error objects that are also returned to the AI
|
|
91
96
|
let sanitizedProperties;
|
|
@@ -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().
|
|
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
|
/**
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open a URL in the default browser (cross-platform)
|
|
3
|
+
* Uses execFile/spawn with args array to avoid shell injection
|
|
4
|
+
*/
|
|
5
|
+
export declare function openBrowser(url: string): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Open the Desktop Commander welcome page
|
|
8
|
+
*/
|
|
9
|
+
export declare function openWelcomePage(): Promise<void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { execFile, spawn } from 'child_process';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import { logToStderr } from './logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* Open a URL in the default browser (cross-platform)
|
|
6
|
+
* Uses execFile/spawn with args array to avoid shell injection
|
|
7
|
+
*/
|
|
8
|
+
export async function openBrowser(url) {
|
|
9
|
+
const platform = os.platform();
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const callback = (error) => {
|
|
12
|
+
if (error) {
|
|
13
|
+
logToStderr('error', `Failed to open browser: ${error.message}`);
|
|
14
|
+
reject(error);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
logToStderr('info', `Opened browser to: ${url}`);
|
|
18
|
+
resolve();
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
switch (platform) {
|
|
22
|
+
case 'darwin':
|
|
23
|
+
execFile('open', [url], callback);
|
|
24
|
+
break;
|
|
25
|
+
case 'win32':
|
|
26
|
+
// Windows 'start' is a shell builtin, use spawn with shell but pass URL as separate arg
|
|
27
|
+
spawn('cmd', ['/c', 'start', '', url], { shell: false }).on('close', (code) => {
|
|
28
|
+
code === 0 ? resolve() : reject(new Error(`Exit code ${code}`));
|
|
29
|
+
});
|
|
30
|
+
break;
|
|
31
|
+
default:
|
|
32
|
+
execFile('xdg-open', [url], callback);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Open the Desktop Commander welcome page
|
|
39
|
+
*/
|
|
40
|
+
export async function openWelcomePage() {
|
|
41
|
+
const url = 'https://desktopcommander.app/welcome/';
|
|
42
|
+
await openBrowser(url);
|
|
43
|
+
}
|
|
@@ -325,6 +325,12 @@ class UsageTracker {
|
|
|
325
325
|
* Check if user should see onboarding invitation - SIMPLE VERSION
|
|
326
326
|
*/
|
|
327
327
|
async shouldShowOnboarding() {
|
|
328
|
+
// Check feature flag first (remote kill switch)
|
|
329
|
+
const { featureFlagManager } = await import('./feature-flags.js');
|
|
330
|
+
const onboardingEnabled = featureFlagManager.get('onboarding_injection', true);
|
|
331
|
+
if (!onboardingEnabled) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
328
334
|
// Check if onboarding is disabled via command line argument
|
|
329
335
|
if (global.disableOnboarding) {
|
|
330
336
|
return false;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle welcome page display for new users (A/B test controlled)
|
|
3
|
+
*
|
|
4
|
+
* Only shows to:
|
|
5
|
+
* 1. New users (first run - config was just created)
|
|
6
|
+
* 2. Users in the 'showOnboardingPage' A/B variant
|
|
7
|
+
* 3. Haven't seen it yet
|
|
8
|
+
*/
|
|
9
|
+
export declare function handleWelcomePageOnboarding(): Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { configManager } from '../config-manager.js';
|
|
2
|
+
import { hasFeature } from './ab-test.js';
|
|
3
|
+
import { featureFlagManager } from './feature-flags.js';
|
|
4
|
+
import { openWelcomePage } from './open-browser.js';
|
|
5
|
+
import { logToStderr } from './logger.js';
|
|
6
|
+
/**
|
|
7
|
+
* Handle welcome page display for new users (A/B test controlled)
|
|
8
|
+
*
|
|
9
|
+
* Only shows to:
|
|
10
|
+
* 1. New users (first run - config was just created)
|
|
11
|
+
* 2. Users in the 'showOnboardingPage' A/B variant
|
|
12
|
+
* 3. Haven't seen it yet
|
|
13
|
+
*/
|
|
14
|
+
export async function handleWelcomePageOnboarding() {
|
|
15
|
+
// Only for brand new users (config just created)
|
|
16
|
+
if (!configManager.isFirstRun()) {
|
|
17
|
+
return;
|
|
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
|
+
}
|
|
26
|
+
// Check A/B test assignment
|
|
27
|
+
const shouldShow = await hasFeature('showOnboardingPage');
|
|
28
|
+
if (!shouldShow) {
|
|
29
|
+
logToStderr('debug', 'Welcome page skipped (A/B: noOnboardingPage)');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Double-check not already shown (safety)
|
|
33
|
+
const alreadyShown = await configManager.getValue('sawOnboardingPage');
|
|
34
|
+
if (alreadyShown) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
await openWelcomePage();
|
|
39
|
+
await configManager.setValue('sawOnboardingPage', true);
|
|
40
|
+
logToStderr('info', 'Welcome page opened');
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
logToStderr('warning', `Failed to open welcome page: ${e instanceof Error ? e.message : e}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.2.
|
|
1
|
+
export declare const VERSION = "0.2.26";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.2.
|
|
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.
|
|
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",
|
|
@@ -81,11 +81,11 @@
|
|
|
81
81
|
"@opendocsg/pdf2md": "^0.2.2",
|
|
82
82
|
"@vscode/ripgrep": "^1.15.9",
|
|
83
83
|
"cross-fetch": "^4.1.0",
|
|
84
|
+
"exceljs": "^4.4.0",
|
|
84
85
|
"fastest-levenshtein": "^1.0.16",
|
|
85
86
|
"file-type": "^21.1.1",
|
|
86
87
|
"glob": "^10.3.10",
|
|
87
88
|
"isbinaryfile": "^5.0.4",
|
|
88
|
-
"exceljs": "^4.4.0",
|
|
89
89
|
"md-to-pdf": "^5.2.5",
|
|
90
90
|
"pdf-lib": "^1.17.1",
|
|
91
91
|
"remark": "^15.0.1",
|