@hcu-lab.me/mcp-device 1.0.6 → 1.0.7
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/README.md +7 -71
- package/dist/bootstrap.js +1 -1
- package/dist/config-manager.js +1 -1
- package/dist/config.js +1 -1
- package/dist/custom-stdio.js +1 -2
- package/dist/data/onboarding-prompts.json +22 -11
- package/dist/device/device-state.d.ts +0 -5
- package/dist/device/device-state.js +0 -84
- package/dist/device/device.d.ts +1 -1
- package/dist/device/device.js +8 -7
- package/dist/device/execution-engine.d.ts +10 -1
- package/dist/device/execution-engine.js +81 -128
- package/dist/device/gateway-channel.d.ts +6 -0
- package/dist/device/gateway-channel.js +16 -1
- package/dist/device/gateway-tool-adapter.d.ts +3 -3
- package/dist/device/gateway-tool-adapter.js +12 -11
- package/dist/device/linux-service.d.ts +4 -0
- package/dist/device/linux-service.js +10 -3
- package/dist/device/runtime-owner.js +2 -2
- package/dist/device/windows-service.d.ts +3 -4
- package/dist/device/windows-service.js +5 -44
- package/dist/npm-scripts/remote.js +0 -2
- package/dist/server.js +9 -99
- package/dist/terminal-manager.js +1 -1
- package/dist/tools/config.d.ts +1 -1
- package/dist/tools/filesystem.js +3 -3
- package/dist/tools/pdf/markdown.js +1 -1
- package/dist/tools/schemas.d.ts +2 -3
- package/dist/tools/schemas.js +0 -10
- package/dist/ui/config-editor/index.html +1 -1
- package/dist/ui/file-preview/index.html +1 -1
- package/dist/ui/file-preview/styles.css +2 -2
- package/dist/utils/capture.d.ts +5 -28
- package/dist/utils/capture.js +11 -455
- package/dist/utils/feature-flags.d.ts +0 -47
- package/dist/utils/feature-flags.js +5 -199
- package/dist/utils/files/docx.d.ts +2 -2
- package/dist/utils/files/docx.js +2 -2
- package/dist/utils/usageTracker.js +0 -24
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -6
- package/dist/tools/feedback.d.ts +0 -8
- package/dist/tools/feedback.js +0 -132
|
@@ -1,215 +1,21 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
4
|
-
import { CONFIG_FILE } from '../config.js';
|
|
5
|
-
import { logger } from './logger.js';
|
|
6
1
|
class FeatureFlagManager {
|
|
7
2
|
constructor() {
|
|
8
3
|
this.flags = {};
|
|
9
|
-
this.lastFetch = 0;
|
|
10
|
-
this.cacheMaxAge = 30 * 60 * 1000;
|
|
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;
|
|
16
|
-
const configDir = path.dirname(CONFIG_FILE);
|
|
17
|
-
this.cachePath = path.join(configDir, 'feature-flags.json');
|
|
18
|
-
// Use production flags (v2 supports weighted variants)
|
|
19
|
-
this.flagUrl = process.env.DC_FLAG_URL ||
|
|
20
|
-
'https://desktopcommander.app/flags/v2/production.json';
|
|
21
|
-
// Set up promise for waiting on fresh fetch
|
|
22
|
-
this.freshFetchPromise = new Promise((resolve) => {
|
|
23
|
-
this.resolveFreshFetch = resolve;
|
|
24
|
-
});
|
|
25
4
|
}
|
|
26
|
-
|
|
27
|
-
* Initialize - load from cache and start background refresh
|
|
28
|
-
*/
|
|
29
|
-
async initialize() {
|
|
30
|
-
try {
|
|
31
|
-
// Load from cache immediately (non-blocking)
|
|
32
|
-
await this.loadFromCache();
|
|
33
|
-
// Fetch in background (don't block startup)
|
|
34
|
-
this.fetchFlags().then(() => {
|
|
35
|
-
// Signal that fresh flags are now available
|
|
36
|
-
if (this.resolveFreshFetch) {
|
|
37
|
-
this.resolveFreshFetch();
|
|
38
|
-
}
|
|
39
|
-
}).catch(err => {
|
|
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
|
-
}
|
|
45
|
-
});
|
|
46
|
-
// Start periodic refresh every 5 minutes
|
|
47
|
-
this.refreshInterval = setInterval(() => {
|
|
48
|
-
this.fetchFlags().catch(err => {
|
|
49
|
-
logger.debug('Periodic flag fetch failed:', err.message);
|
|
50
|
-
});
|
|
51
|
-
}, this.cacheMaxAge);
|
|
52
|
-
// Allow process to exit even if interval is pending
|
|
53
|
-
// This is critical for proper cleanup when MCP client disconnects
|
|
54
|
-
this.refreshInterval.unref();
|
|
55
|
-
logger.info(`Feature flags initialized (refresh every ${this.cacheMaxAge / 1000}s)`);
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
logger.warning('Failed to initialize feature flags:', error);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Get a flag value
|
|
63
|
-
*/
|
|
5
|
+
async initialize() { }
|
|
64
6
|
get(flagName, defaultValue = false) {
|
|
65
7
|
return this.flags[flagName] !== undefined ? this.flags[flagName] : defaultValue;
|
|
66
8
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Get all flags for debugging
|
|
69
|
-
*/
|
|
70
9
|
getAll() {
|
|
71
10
|
return { ...this.flags };
|
|
72
11
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Manually refresh flags immediately (for testing)
|
|
75
|
-
*/
|
|
76
12
|
async refresh() {
|
|
77
|
-
|
|
78
|
-
await this.fetchFlags();
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
logger.error('Manual refresh failed:', error);
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
13
|
+
return true;
|
|
85
14
|
}
|
|
86
|
-
/**
|
|
87
|
-
* Check if flags were loaded from cache (vs fresh fetch)
|
|
88
|
-
*/
|
|
89
15
|
wasLoadedFromCache() {
|
|
90
|
-
return
|
|
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
|
-
* Has a hard timeout to prevent blocking MCP startup if the fetch hangs.
|
|
98
|
-
* See: https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/465
|
|
99
|
-
*/
|
|
100
|
-
async waitForFreshFlags() {
|
|
101
|
-
if (this.freshFetchPromise) {
|
|
102
|
-
let safetyTimeoutHandle;
|
|
103
|
-
try {
|
|
104
|
-
const safetyTimeout = new Promise((resolve) => {
|
|
105
|
-
safetyTimeoutHandle = setTimeout(resolve, 5000);
|
|
106
|
-
});
|
|
107
|
-
await Promise.race([this.freshFetchPromise, safetyTimeout]);
|
|
108
|
-
}
|
|
109
|
-
finally {
|
|
110
|
-
if (safetyTimeoutHandle) {
|
|
111
|
-
clearTimeout(safetyTimeoutHandle);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Load flags from local cache
|
|
118
|
-
*/
|
|
119
|
-
async loadFromCache() {
|
|
120
|
-
try {
|
|
121
|
-
if (!existsSync(this.cachePath)) {
|
|
122
|
-
logger.debug('No feature flag cache found');
|
|
123
|
-
this.loadedFromCache = false;
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
const data = await fs.readFile(this.cachePath, 'utf8');
|
|
127
|
-
const config = JSON.parse(data);
|
|
128
|
-
if (config.flags) {
|
|
129
|
-
this.flags = config.flags;
|
|
130
|
-
this.lastFetch = Date.now();
|
|
131
|
-
this.loadedFromCache = true;
|
|
132
|
-
logger.debug(`Loaded ${Object.keys(this.flags).length} feature flags from cache`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
logger.warning('Failed to load feature flags from cache:', error);
|
|
137
|
-
this.loadedFromCache = false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Fetch flags from remote URL
|
|
142
|
-
*/
|
|
143
|
-
async fetchFlags() {
|
|
144
|
-
const FETCH_TIMEOUT_MS = 3000;
|
|
145
|
-
const controller = new AbortController();
|
|
146
|
-
const abortTimeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
147
|
-
let hardTimeoutHandle;
|
|
148
|
-
try {
|
|
149
|
-
// Don't log here - runs async and can interfere with MCP clients
|
|
150
|
-
// Use Promise.race as a hard timeout safety net.
|
|
151
|
-
// On some platforms (Windows + Node 24 / undici 7.x), AbortController.abort()
|
|
152
|
-
// fails to interrupt an in-progress TCP connect — the fetch hangs until the
|
|
153
|
-
// OS-level TCP timeout (~30s on Windows). Promise.race guarantees we reject
|
|
154
|
-
// at the JS level regardless of AbortController behavior.
|
|
155
|
-
// See: https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/465
|
|
156
|
-
const fetchPromise = fetch(this.flagUrl, {
|
|
157
|
-
signal: controller.signal,
|
|
158
|
-
headers: {
|
|
159
|
-
'Cache-Control': 'no-cache',
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
const hardTimeout = new Promise((_, reject) => hardTimeoutHandle = setTimeout(() => reject(new Error('Feature flags fetch timed out')), FETCH_TIMEOUT_MS));
|
|
163
|
-
const response = await Promise.race([fetchPromise, hardTimeout]);
|
|
164
|
-
if (!response.ok) {
|
|
165
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
166
|
-
}
|
|
167
|
-
const config = await response.json();
|
|
168
|
-
// Update flags
|
|
169
|
-
if (config.flags) {
|
|
170
|
-
this.flags = config.flags;
|
|
171
|
-
this.lastFetch = Date.now();
|
|
172
|
-
// Save to cache (silently - don't log during async operations
|
|
173
|
-
// as it can interfere with MCP clients that close quickly)
|
|
174
|
-
await this.saveToCache(config);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
catch (error) {
|
|
178
|
-
logger.debug('Failed to fetch feature flags:', error.message);
|
|
179
|
-
// Continue with cached values
|
|
180
|
-
}
|
|
181
|
-
finally {
|
|
182
|
-
clearTimeout(abortTimeout);
|
|
183
|
-
if (hardTimeoutHandle) {
|
|
184
|
-
clearTimeout(hardTimeoutHandle);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Save flags to local cache
|
|
190
|
-
*/
|
|
191
|
-
async saveToCache(config) {
|
|
192
|
-
try {
|
|
193
|
-
const configDir = path.dirname(this.cachePath);
|
|
194
|
-
if (!existsSync(configDir)) {
|
|
195
|
-
await fs.mkdir(configDir, { recursive: true });
|
|
196
|
-
}
|
|
197
|
-
await fs.writeFile(this.cachePath, JSON.stringify(config, null, 2), 'utf8');
|
|
198
|
-
// Don't log here - this runs async and can cause issues with MCP clients
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
logger.warning('Failed to save feature flags to cache:', error);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Cleanup on shutdown
|
|
206
|
-
*/
|
|
207
|
-
destroy() {
|
|
208
|
-
if (this.refreshInterval) {
|
|
209
|
-
clearInterval(this.refreshInterval);
|
|
210
|
-
this.refreshInterval = null;
|
|
211
|
-
}
|
|
16
|
+
return false;
|
|
212
17
|
}
|
|
18
|
+
async waitForFreshFlags() { }
|
|
19
|
+
destroy() { }
|
|
213
20
|
}
|
|
214
|
-
// Export singleton instance
|
|
215
21
|
export const featureFlagManager = new FeatureFlagManager();
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* READ (default): Returns a text-bearing outline — skips shapes, drawings, SVG noise.
|
|
7
7
|
* Shows paragraphs with text, tables with cell content, style info, and image refs.
|
|
8
|
-
* Each element shows its raw XML tag context so
|
|
8
|
+
* Each element shows its raw XML tag context so an agent can target it for editing.
|
|
9
9
|
*
|
|
10
10
|
* READ (with offset/length): Returns raw pretty-printed XML with line pagination,
|
|
11
|
-
* so
|
|
11
|
+
* so an agent can drill into specific sections when the outline isn't enough.
|
|
12
12
|
*
|
|
13
13
|
* EDIT (old_string/new_string): Find/replace on the pretty-printed XML, then
|
|
14
14
|
* compact and repack into valid DOCX. Works exactly like text file editing.
|
package/dist/utils/files/docx.js
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* READ (default): Returns a text-bearing outline — skips shapes, drawings, SVG noise.
|
|
7
7
|
* Shows paragraphs with text, tables with cell content, style info, and image refs.
|
|
8
|
-
* Each element shows its raw XML tag context so
|
|
8
|
+
* Each element shows its raw XML tag context so an agent can target it for editing.
|
|
9
9
|
*
|
|
10
10
|
* READ (with offset/length): Returns raw pretty-printed XML with line pagination,
|
|
11
|
-
* so
|
|
11
|
+
* so an agent can drill into specific sections when the outline isn't enough.
|
|
12
12
|
*
|
|
13
13
|
* EDIT (old_string/new_string): Find/replace on the pretty-printed XML, then
|
|
14
14
|
* compact and repack into valid DOCX. Works exactly like text file editing.
|
|
@@ -162,18 +162,6 @@ class UsageTracker {
|
|
|
162
162
|
if (!feedbackEnabled) {
|
|
163
163
|
return false;
|
|
164
164
|
}
|
|
165
|
-
// Check if client is desktop-commander (disable for this client)
|
|
166
|
-
//return true;
|
|
167
|
-
try {
|
|
168
|
-
const { currentClient } = await import('../server.js');
|
|
169
|
-
if (currentClient?.name === 'desktop-commander') {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
// If we can't import server, continue with other checks
|
|
175
|
-
console.log('[FEEDBACK DEBUG] Could not check client name, continuing...');
|
|
176
|
-
}
|
|
177
165
|
const stats = await this.getStats();
|
|
178
166
|
// Don't prompt if feedback already given (check top-level config)
|
|
179
167
|
const feedbackGiven = await configManager.getValue('feedbackGiven');
|
|
@@ -339,7 +327,6 @@ class UsageTracker {
|
|
|
339
327
|
// within seconds, so when the flag is ON onboarding still fires on a
|
|
340
328
|
// later call while the user is new. Deliberately no waiting here to keep
|
|
341
329
|
// tool calls latency-free.
|
|
342
|
-
// See: https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/538
|
|
343
330
|
const onboardingEnabled = featureFlagManager.get('onboarding_injection', false);
|
|
344
331
|
if (!onboardingEnabled) {
|
|
345
332
|
return false;
|
|
@@ -353,17 +340,6 @@ class UsageTracker {
|
|
|
353
340
|
if (global.disableOnboarding) {
|
|
354
341
|
return false;
|
|
355
342
|
}
|
|
356
|
-
// Check if client is desktop-commander (disable for this client)
|
|
357
|
-
try {
|
|
358
|
-
const { currentClient } = await import('../server.js');
|
|
359
|
-
if (currentClient?.name === 'desktop-commander') {
|
|
360
|
-
return false;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
catch (error) {
|
|
364
|
-
// If we can't import server, continue with other checks
|
|
365
|
-
console.log('[ONBOARDING DEBUG] Could not check client name, continuing...');
|
|
366
|
-
}
|
|
367
343
|
const stats = await this.getStats();
|
|
368
344
|
const onboardingState = await this.getOnboardingState();
|
|
369
345
|
const now = Date.now();
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.0.
|
|
1
|
+
export declare const VERSION = "1.0.7";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '1.0.
|
|
1
|
+
export const VERSION = '1.0.7';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hcu-lab.me/mcp-device",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "MCP Device
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "MCP Device runtime.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Hirohimeow",
|
|
7
7
|
"homepage": "https://github.com/Hiroshimeow/agent-mcp-device",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"bump": "node scripts/sync-version.js --bump",
|
|
34
34
|
"bump:minor": "node scripts/sync-version.js --bump --minor",
|
|
35
35
|
"bump:major": "node scripts/sync-version.js --bump --major",
|
|
36
|
-
"build": "shx rm -rf dist
|
|
36
|
+
"build": "shx rm -rf dist && tsc && shx chmod +x dist/*.js && shx cp src/device/update-helper.cjs dist/device/update-helper.cjs && shx mkdir -p dist/data && shx cp src/data/onboarding-prompts.json dist/data/ && node scripts/copy-official-ca.cjs && node scripts/build-ui-runtime.cjs",
|
|
37
37
|
"watch": "tsc --watch",
|
|
38
38
|
"start": "node dist/mcp-device.js",
|
|
39
39
|
"start:debug": "node --inspect-brk=9229 dist/mcp-device.js",
|
|
40
|
-
"prepack": "node scripts/verify-production-trust.cjs
|
|
40
|
+
"prepack": "node scripts/verify-production-trust.cjs",
|
|
41
41
|
"prepare": "npm run build",
|
|
42
42
|
"clean": "shx rm -rf dist",
|
|
43
43
|
"test": "npm run build && node test/run-all-tests.js",
|
|
@@ -57,7 +57,6 @@
|
|
|
57
57
|
"mcp",
|
|
58
58
|
"model-context-protocol",
|
|
59
59
|
"terminal",
|
|
60
|
-
"claude",
|
|
61
60
|
"ai",
|
|
62
61
|
"command-line",
|
|
63
62
|
"process-management",
|
|
@@ -73,7 +72,7 @@
|
|
|
73
72
|
"file-operations"
|
|
74
73
|
],
|
|
75
74
|
"dependencies": {
|
|
76
|
-
"@modelcontextprotocol/sdk": "
|
|
75
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
77
76
|
"@opendocsg/pdf2md": "^0.2.2",
|
|
78
77
|
"@tiptap/core": "^3.31.3",
|
|
79
78
|
"@tiptap/extension-image": "^3.31.3",
|
package/dist/tools/feedback.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { ServerResult } from '../types.js';
|
|
2
|
-
interface FeedbackParams {
|
|
3
|
-
}
|
|
4
|
-
/**
|
|
5
|
-
* Open feedback form in browser with optional pre-filled data
|
|
6
|
-
*/
|
|
7
|
-
export declare function giveFeedbackToDesktopCommander(params?: FeedbackParams): Promise<ServerResult>;
|
|
8
|
-
export {};
|
package/dist/tools/feedback.js
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { usageTracker } from '../utils/usageTracker.js';
|
|
2
|
-
import { capture } from '../utils/capture.js';
|
|
3
|
-
import { configManager } from '../config-manager.js';
|
|
4
|
-
import { exec } from 'child_process';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
|
-
import * as os from 'os';
|
|
7
|
-
const execAsync = promisify(exec);
|
|
8
|
-
/**
|
|
9
|
-
* Open feedback form in browser with optional pre-filled data
|
|
10
|
-
*/
|
|
11
|
-
export async function giveFeedbackToDesktopCommander(params = {}) {
|
|
12
|
-
try {
|
|
13
|
-
// Get usage stats for context
|
|
14
|
-
const stats = await usageTracker.getStats();
|
|
15
|
-
// Capture feedback tool usage event
|
|
16
|
-
await capture('feedback_tool_called', {
|
|
17
|
-
total_calls: stats.totalToolCalls,
|
|
18
|
-
successful_calls: stats.successfulCalls,
|
|
19
|
-
failed_calls: stats.failedCalls,
|
|
20
|
-
days_since_first_use: Math.floor((Date.now() - stats.firstUsed) / (1000 * 60 * 60 * 24)),
|
|
21
|
-
total_sessions: stats.totalSessions,
|
|
22
|
-
platform: os.platform(),
|
|
23
|
-
});
|
|
24
|
-
// Build Tally.so URL with pre-filled parameters
|
|
25
|
-
const tallyUrl = await buildTallyUrl(params, stats);
|
|
26
|
-
// Open URL in default browser
|
|
27
|
-
const success = await openUrlInBrowser(tallyUrl);
|
|
28
|
-
if (success) {
|
|
29
|
-
// Capture successful browser opening
|
|
30
|
-
await capture('feedback_form_opened_successfully', {
|
|
31
|
-
total_calls: stats.totalToolCalls,
|
|
32
|
-
platform: os.platform()
|
|
33
|
-
});
|
|
34
|
-
// Mark that user has given feedback (or at least opened the form)
|
|
35
|
-
await usageTracker.markFeedbackGiven();
|
|
36
|
-
return {
|
|
37
|
-
content: [{
|
|
38
|
-
type: "text",
|
|
39
|
-
text: `🎉 **Feedback form opened in your browser!**\n\n` +
|
|
40
|
-
`Thank you for taking the time to share your experience with MCP Device. ` +
|
|
41
|
-
`Your feedback helps us build better features and improve the tool for everyone.\n\n` +
|
|
42
|
-
`The form has been pre-filled with the information you provided. ` +
|
|
43
|
-
`You can modify or add any additional details before submitting.\n\n` +
|
|
44
|
-
`**Form URL**: ${tallyUrl.length > 100 ? tallyUrl.substring(0, 100) + '...' : tallyUrl}`
|
|
45
|
-
}]
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
// Capture browser opening failure
|
|
50
|
-
await capture('feedback_form_open_failed', {
|
|
51
|
-
total_calls: stats.totalToolCalls,
|
|
52
|
-
platform: os.platform(),
|
|
53
|
-
error_type: 'browser_open_failed'
|
|
54
|
-
});
|
|
55
|
-
return {
|
|
56
|
-
content: [{
|
|
57
|
-
type: "text",
|
|
58
|
-
text: `⚠️ **Couldn't open browser automatically**\n\n` +
|
|
59
|
-
`Please copy and paste this URL into your browser to access the feedback form:\n\n` +
|
|
60
|
-
`${tallyUrl}\n\n` +
|
|
61
|
-
`The form has been pre-filled with your information. Thank you for your feedback!`
|
|
62
|
-
}]
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
// Capture error event
|
|
68
|
-
await capture('feedback_tool_error', {
|
|
69
|
-
error_message: error instanceof Error ? error.message : String(error),
|
|
70
|
-
error_type: error instanceof Error ? error.constructor.name : 'unknown'
|
|
71
|
-
});
|
|
72
|
-
return {
|
|
73
|
-
content: [{
|
|
74
|
-
type: "text",
|
|
75
|
-
text: `❌ **Error opening feedback form**: ${error instanceof Error ? error.message : String(error)}\n\n` +
|
|
76
|
-
`You can still access our feedback form directly at: https://tally.so/r/mKqoKg\n\n` +
|
|
77
|
-
`We appreciate your willingness to provide feedback!`
|
|
78
|
-
}],
|
|
79
|
-
isError: true
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Build Tally.so URL with pre-filled parameters
|
|
85
|
-
*/
|
|
86
|
-
async function buildTallyUrl(params, stats) {
|
|
87
|
-
const baseUrl = 'https://tally.so/r/mKqoKg';
|
|
88
|
-
const urlParams = new URLSearchParams();
|
|
89
|
-
// Only auto-filled hidden fields remain
|
|
90
|
-
urlParams.set('tool_call_count', stats.totalToolCalls.toString());
|
|
91
|
-
// Calculate days using
|
|
92
|
-
const daysUsing = Math.floor((Date.now() - stats.firstUsed) / (1000 * 60 * 60 * 24));
|
|
93
|
-
urlParams.set('days_using', daysUsing.toString());
|
|
94
|
-
// Add platform info
|
|
95
|
-
urlParams.set('platform', os.platform());
|
|
96
|
-
// Add client_id from analytics config
|
|
97
|
-
try {
|
|
98
|
-
const clientId = await configManager.getValue('clientId') || 'unknown';
|
|
99
|
-
urlParams.set('client_id', clientId);
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
// Fallback if config read fails
|
|
103
|
-
urlParams.set('client_id', 'unknown');
|
|
104
|
-
}
|
|
105
|
-
return `${baseUrl}?${urlParams.toString()}`;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Open URL in default browser (cross-platform)
|
|
109
|
-
*/
|
|
110
|
-
async function openUrlInBrowser(url) {
|
|
111
|
-
try {
|
|
112
|
-
const platform = os.platform();
|
|
113
|
-
let command;
|
|
114
|
-
switch (platform) {
|
|
115
|
-
case 'darwin': // macOS
|
|
116
|
-
command = `open "${url}"`;
|
|
117
|
-
break;
|
|
118
|
-
case 'win32': // Windows
|
|
119
|
-
command = `start "" "${url}"`;
|
|
120
|
-
break;
|
|
121
|
-
default: // Linux and others
|
|
122
|
-
command = `xdg-open "${url}"`;
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
await execAsync(command);
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
console.error('Failed to open browser:', error);
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
}
|