@wonderwhy-er/desktop-commander 0.2.2 → 0.2.4
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 +27 -4
- package/dist/config-manager.d.ts +10 -0
- package/dist/config-manager.js +7 -1
- package/dist/handlers/edit-search-handlers.js +25 -6
- package/dist/handlers/filesystem-handlers.js +2 -4
- package/dist/handlers/terminal-handlers.d.ts +8 -4
- package/dist/handlers/terminal-handlers.js +16 -10
- package/dist/index-dxt.d.ts +2 -0
- package/dist/index-dxt.js +76 -0
- package/dist/index-with-startup-detection.d.ts +5 -0
- package/dist/index-with-startup-detection.js +180 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +381 -65
- package/dist/terminal-manager.d.ts +7 -0
- package/dist/terminal-manager.js +93 -18
- package/dist/tools/client.d.ts +10 -0
- package/dist/tools/client.js +13 -0
- package/dist/tools/config.d.ts +1 -1
- package/dist/tools/config.js +21 -3
- package/dist/tools/edit.js +4 -3
- package/dist/tools/environment.d.ts +55 -0
- package/dist/tools/environment.js +65 -0
- package/dist/tools/feedback.d.ts +8 -0
- package/dist/tools/feedback.js +132 -0
- package/dist/tools/filesystem.d.ts +10 -0
- package/dist/tools/filesystem.js +410 -60
- package/dist/tools/improved-process-tools.d.ts +24 -0
- package/dist/tools/improved-process-tools.js +453 -0
- package/dist/tools/schemas.d.ts +20 -2
- package/dist/tools/schemas.js +20 -3
- package/dist/tools/usage.d.ts +5 -0
- package/dist/tools/usage.js +24 -0
- package/dist/utils/capture.d.ts +2 -0
- package/dist/utils/capture.js +40 -9
- package/dist/utils/early-logger.d.ts +4 -0
- package/dist/utils/early-logger.js +35 -0
- package/dist/utils/mcp-logger.d.ts +30 -0
- package/dist/utils/mcp-logger.js +59 -0
- package/dist/utils/process-detection.d.ts +23 -0
- package/dist/utils/process-detection.js +150 -0
- package/dist/utils/smithery-detector.d.ts +94 -0
- package/dist/utils/smithery-detector.js +292 -0
- package/dist/utils/startup-detector.d.ts +65 -0
- package/dist/utils/startup-detector.js +390 -0
- package/dist/utils/system-info.d.ts +30 -0
- package/dist/utils/system-info.js +146 -0
- package/dist/utils/usageTracker.d.ts +85 -0
- package/dist/utils/usageTracker.js +280 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -1
package/dist/terminal-manager.js
CHANGED
|
@@ -2,11 +2,37 @@ import { spawn } from 'child_process';
|
|
|
2
2
|
import { DEFAULT_COMMAND_TIMEOUT } from './config.js';
|
|
3
3
|
import { configManager } from './config-manager.js';
|
|
4
4
|
import { capture } from "./utils/capture.js";
|
|
5
|
+
import { analyzeProcessState } from './utils/process-detection.js';
|
|
5
6
|
export class TerminalManager {
|
|
6
7
|
constructor() {
|
|
7
8
|
this.sessions = new Map();
|
|
8
9
|
this.completedSessions = new Map();
|
|
9
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Send input to a running process
|
|
13
|
+
* @param pid Process ID
|
|
14
|
+
* @param input Text to send to the process
|
|
15
|
+
* @returns Whether input was successfully sent
|
|
16
|
+
*/
|
|
17
|
+
sendInputToProcess(pid, input) {
|
|
18
|
+
const session = this.sessions.get(pid);
|
|
19
|
+
if (!session) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
if (session.process.stdin && !session.process.stdin.destroyed) {
|
|
24
|
+
// Ensure input ends with a newline for most REPLs
|
|
25
|
+
const inputWithNewline = input.endsWith('\n') ? input : input + '\n';
|
|
26
|
+
session.process.stdin.write(inputWithNewline);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error(`Error sending input to process ${pid}:`, error);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
10
36
|
async executeCommand(command, timeoutMs = DEFAULT_COMMAND_TIMEOUT, shell) {
|
|
11
37
|
// Get the shell from config if not specified
|
|
12
38
|
let shellToUse = shell;
|
|
@@ -20,13 +46,26 @@ export class TerminalManager {
|
|
|
20
46
|
shellToUse = true;
|
|
21
47
|
}
|
|
22
48
|
}
|
|
49
|
+
// For REPL interactions, we need to ensure stdin, stdout, and stderr are properly configured
|
|
50
|
+
// Note: No special stdio options needed here, Node.js handles pipes by default
|
|
51
|
+
// Enhance SSH commands automatically
|
|
52
|
+
let enhancedCommand = command;
|
|
53
|
+
if (command.trim().startsWith('ssh ') && !command.includes(' -t')) {
|
|
54
|
+
enhancedCommand = command.replace(/^ssh /, 'ssh -t ');
|
|
55
|
+
console.log(`Enhanced SSH command: ${enhancedCommand}`);
|
|
56
|
+
}
|
|
23
57
|
const spawnOptions = {
|
|
24
|
-
shell: shellToUse
|
|
58
|
+
shell: shellToUse,
|
|
59
|
+
env: {
|
|
60
|
+
...process.env,
|
|
61
|
+
TERM: 'xterm-256color' // Better terminal compatibility
|
|
62
|
+
}
|
|
25
63
|
};
|
|
26
|
-
|
|
64
|
+
// Spawn the process with an empty array of arguments and our options
|
|
65
|
+
const childProcess = spawn(enhancedCommand, [], spawnOptions);
|
|
27
66
|
let output = '';
|
|
28
|
-
// Ensure
|
|
29
|
-
if (!
|
|
67
|
+
// Ensure childProcess.pid is defined before proceeding
|
|
68
|
+
if (!childProcess.pid) {
|
|
30
69
|
// Return a consistent error object instead of throwing
|
|
31
70
|
return {
|
|
32
71
|
pid: -1, // Use -1 to indicate an error state
|
|
@@ -35,37 +74,73 @@ export class TerminalManager {
|
|
|
35
74
|
};
|
|
36
75
|
}
|
|
37
76
|
const session = {
|
|
38
|
-
pid:
|
|
39
|
-
process,
|
|
77
|
+
pid: childProcess.pid,
|
|
78
|
+
process: childProcess,
|
|
40
79
|
lastOutput: '',
|
|
41
80
|
isBlocked: false,
|
|
42
81
|
startTime: new Date()
|
|
43
82
|
};
|
|
44
|
-
this.sessions.set(
|
|
83
|
+
this.sessions.set(childProcess.pid, session);
|
|
45
84
|
return new Promise((resolve) => {
|
|
46
|
-
|
|
85
|
+
let resolved = false;
|
|
86
|
+
let periodicCheck = null;
|
|
87
|
+
// Quick prompt patterns for immediate detection
|
|
88
|
+
const quickPromptPatterns = />>>\s*$|>\s*$|\$\s*$|#\s*$/;
|
|
89
|
+
const resolveOnce = (result) => {
|
|
90
|
+
if (resolved)
|
|
91
|
+
return;
|
|
92
|
+
resolved = true;
|
|
93
|
+
if (periodicCheck)
|
|
94
|
+
clearInterval(periodicCheck);
|
|
95
|
+
resolve(result);
|
|
96
|
+
};
|
|
97
|
+
childProcess.stdout.on('data', (data) => {
|
|
47
98
|
const text = data.toString();
|
|
48
99
|
output += text;
|
|
49
100
|
session.lastOutput += text;
|
|
101
|
+
// Immediate check for obvious prompts
|
|
102
|
+
if (quickPromptPatterns.test(text)) {
|
|
103
|
+
session.isBlocked = true;
|
|
104
|
+
resolveOnce({
|
|
105
|
+
pid: childProcess.pid,
|
|
106
|
+
output,
|
|
107
|
+
isBlocked: true
|
|
108
|
+
});
|
|
109
|
+
}
|
|
50
110
|
});
|
|
51
|
-
|
|
111
|
+
childProcess.stderr.on('data', (data) => {
|
|
52
112
|
const text = data.toString();
|
|
53
113
|
output += text;
|
|
54
114
|
session.lastOutput += text;
|
|
55
115
|
});
|
|
116
|
+
// Periodic comprehensive check every 100ms
|
|
117
|
+
periodicCheck = setInterval(() => {
|
|
118
|
+
if (output.trim()) {
|
|
119
|
+
const processState = analyzeProcessState(output, childProcess.pid);
|
|
120
|
+
if (processState.isWaitingForInput) {
|
|
121
|
+
session.isBlocked = true;
|
|
122
|
+
resolveOnce({
|
|
123
|
+
pid: childProcess.pid,
|
|
124
|
+
output,
|
|
125
|
+
isBlocked: true
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}, 100);
|
|
130
|
+
// Timeout fallback
|
|
56
131
|
setTimeout(() => {
|
|
57
132
|
session.isBlocked = true;
|
|
58
|
-
|
|
59
|
-
pid:
|
|
133
|
+
resolveOnce({
|
|
134
|
+
pid: childProcess.pid,
|
|
60
135
|
output,
|
|
61
136
|
isBlocked: true
|
|
62
137
|
});
|
|
63
138
|
}, timeoutMs);
|
|
64
|
-
|
|
65
|
-
if (
|
|
139
|
+
childProcess.on('exit', (code) => {
|
|
140
|
+
if (childProcess.pid) {
|
|
66
141
|
// Store completed session before removing active session
|
|
67
|
-
this.completedSessions.set(
|
|
68
|
-
pid:
|
|
142
|
+
this.completedSessions.set(childProcess.pid, {
|
|
143
|
+
pid: childProcess.pid,
|
|
69
144
|
output: output + session.lastOutput, // Combine all output
|
|
70
145
|
exitCode: code,
|
|
71
146
|
startTime: session.startTime,
|
|
@@ -76,10 +151,10 @@ export class TerminalManager {
|
|
|
76
151
|
const oldestKey = Array.from(this.completedSessions.keys())[0];
|
|
77
152
|
this.completedSessions.delete(oldestKey);
|
|
78
153
|
}
|
|
79
|
-
this.sessions.delete(
|
|
154
|
+
this.sessions.delete(childProcess.pid);
|
|
80
155
|
}
|
|
81
|
-
|
|
82
|
-
pid:
|
|
156
|
+
resolveOnce({
|
|
157
|
+
pid: childProcess.pid,
|
|
83
158
|
output,
|
|
84
159
|
isBlocked: false
|
|
85
160
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple tool to get current client information
|
|
3
|
+
*/
|
|
4
|
+
import { currentClient } from '../server.js';
|
|
5
|
+
export async function getCurrentClient() {
|
|
6
|
+
return {
|
|
7
|
+
content: [{
|
|
8
|
+
type: "text",
|
|
9
|
+
text: `Current client: ${currentClient.name} v${currentClient.version}`
|
|
10
|
+
}],
|
|
11
|
+
isError: false,
|
|
12
|
+
};
|
|
13
|
+
}
|
package/dist/tools/config.d.ts
CHANGED
package/dist/tools/config.js
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
import { configManager } from '../config-manager.js';
|
|
2
2
|
import { SetConfigValueArgsSchema } from './schemas.js';
|
|
3
|
+
import { getSystemInfo } from '../utils/system-info.js';
|
|
4
|
+
import { currentClient } from '../server.js';
|
|
3
5
|
/**
|
|
4
|
-
* Get the entire config
|
|
6
|
+
* Get the entire config including system information
|
|
5
7
|
*/
|
|
6
8
|
export async function getConfig() {
|
|
7
9
|
console.error('getConfig called');
|
|
8
10
|
try {
|
|
9
11
|
const config = await configManager.getConfig();
|
|
10
|
-
|
|
12
|
+
// Add system information and current client to the config response
|
|
13
|
+
const systemInfo = getSystemInfo();
|
|
14
|
+
const configWithSystemInfo = {
|
|
15
|
+
...config,
|
|
16
|
+
currentClient,
|
|
17
|
+
systemInfo: {
|
|
18
|
+
platform: systemInfo.platform,
|
|
19
|
+
platformName: systemInfo.platformName,
|
|
20
|
+
defaultShell: systemInfo.defaultShell,
|
|
21
|
+
pathSeparator: systemInfo.pathSeparator,
|
|
22
|
+
isWindows: systemInfo.isWindows,
|
|
23
|
+
isMacOS: systemInfo.isMacOS,
|
|
24
|
+
isLinux: systemInfo.isLinux,
|
|
25
|
+
examplePaths: systemInfo.examplePaths
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
console.error(`getConfig result: ${JSON.stringify(configWithSystemInfo, null, 2)}`);
|
|
11
29
|
return {
|
|
12
30
|
content: [{
|
|
13
31
|
type: "text",
|
|
14
|
-
text: `Current configuration:\n${JSON.stringify(
|
|
32
|
+
text: `Current configuration:\n${JSON.stringify(configWithSystemInfo, null, 2)}`
|
|
15
33
|
}],
|
|
16
34
|
};
|
|
17
35
|
}
|
package/dist/tools/edit.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { writeFile, readFileInternal, validatePath } from './filesystem.js';
|
|
2
2
|
import { recursiveFuzzyIndexOf, getSimilarityRatio } from './fuzzySearch.js';
|
|
3
3
|
import { capture } from '../utils/capture.js';
|
|
4
4
|
import { EditBlockArgsSchema } from "./schemas.js";
|
|
@@ -86,8 +86,9 @@ export async function performSearchReplace(filePath, block, expectedReplacements
|
|
|
86
86
|
}],
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
|
-
// Read file
|
|
90
|
-
const
|
|
89
|
+
// Read file directly to preserve line endings - critical for edit operations
|
|
90
|
+
const validPath = await validatePath(filePath);
|
|
91
|
+
const content = await readFileInternal(validPath, 0, Number.MAX_SAFE_INTEGER);
|
|
91
92
|
// Make sure content is a string
|
|
92
93
|
if (typeof content !== 'string') {
|
|
93
94
|
capture('server_edit_block_content_not_string', { fileExtension: fileExtension, expectedReplacements });
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export declare function getEnvironmentInfo(): {
|
|
2
|
+
processInfo: {
|
|
3
|
+
pid: number;
|
|
4
|
+
ppid: number;
|
|
5
|
+
cwd: string;
|
|
6
|
+
nodeVersion: string;
|
|
7
|
+
versions: NodeJS.ProcessVersions;
|
|
8
|
+
argv: string[];
|
|
9
|
+
uptime: number;
|
|
10
|
+
memoryUsage: NodeJS.MemoryUsage;
|
|
11
|
+
execPath: string;
|
|
12
|
+
execArgv: string[];
|
|
13
|
+
platform: NodeJS.Platform;
|
|
14
|
+
arch: NodeJS.Architecture;
|
|
15
|
+
title: string;
|
|
16
|
+
};
|
|
17
|
+
systemEnvironment: {
|
|
18
|
+
platform: NodeJS.Platform;
|
|
19
|
+
arch: string;
|
|
20
|
+
hostname: string;
|
|
21
|
+
totalmem: number;
|
|
22
|
+
freemem: number;
|
|
23
|
+
cpus: import("os").CpuInfo[];
|
|
24
|
+
userInfo: import("os").UserInfo<string>;
|
|
25
|
+
networkInterfaces: NodeJS.Dict<import("os").NetworkInterfaceInfo[]>;
|
|
26
|
+
environmentVariables: NodeJS.ProcessEnv;
|
|
27
|
+
loadavg: number[];
|
|
28
|
+
uptime: number;
|
|
29
|
+
tmpdir: string;
|
|
30
|
+
homedir: string;
|
|
31
|
+
endianness: "BE" | "LE";
|
|
32
|
+
release: string;
|
|
33
|
+
type: string;
|
|
34
|
+
};
|
|
35
|
+
runtimeContext: {
|
|
36
|
+
timestamp: string;
|
|
37
|
+
timezone: string;
|
|
38
|
+
locale: string;
|
|
39
|
+
executionTime: number;
|
|
40
|
+
};
|
|
41
|
+
error?: undefined;
|
|
42
|
+
} | {
|
|
43
|
+
error: string;
|
|
44
|
+
processInfo: {
|
|
45
|
+
pid: number;
|
|
46
|
+
cwd: string;
|
|
47
|
+
};
|
|
48
|
+
systemEnvironment: {
|
|
49
|
+
platform: NodeJS.Platform;
|
|
50
|
+
};
|
|
51
|
+
runtimeContext: {
|
|
52
|
+
timestamp: string;
|
|
53
|
+
executionTime: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { platform, arch, totalmem, freemem, cpus, userInfo, networkInterfaces, hostname, loadavg, uptime as osUptime, tmpdir, homedir, endianness, release, type } from 'os';
|
|
2
|
+
import { cwd, env, argv, pid, ppid, version, versions, memoryUsage, uptime } from 'process';
|
|
3
|
+
export function getEnvironmentInfo() {
|
|
4
|
+
const startTime = Date.now();
|
|
5
|
+
try {
|
|
6
|
+
// Process Information
|
|
7
|
+
const processInfo = {
|
|
8
|
+
pid: pid,
|
|
9
|
+
ppid: ppid,
|
|
10
|
+
cwd: cwd(),
|
|
11
|
+
nodeVersion: version,
|
|
12
|
+
versions: versions,
|
|
13
|
+
argv: argv,
|
|
14
|
+
uptime: uptime(),
|
|
15
|
+
memoryUsage: memoryUsage(),
|
|
16
|
+
execPath: process.execPath,
|
|
17
|
+
execArgv: process.execArgv,
|
|
18
|
+
platform: process.platform,
|
|
19
|
+
arch: process.arch,
|
|
20
|
+
title: process.title
|
|
21
|
+
};
|
|
22
|
+
// System Environment
|
|
23
|
+
const systemEnvironment = {
|
|
24
|
+
platform: platform(),
|
|
25
|
+
arch: arch(),
|
|
26
|
+
hostname: hostname(),
|
|
27
|
+
totalmem: totalmem(),
|
|
28
|
+
freemem: freemem(),
|
|
29
|
+
cpus: cpus(),
|
|
30
|
+
userInfo: userInfo(),
|
|
31
|
+
networkInterfaces: networkInterfaces(),
|
|
32
|
+
environmentVariables: env,
|
|
33
|
+
loadavg: loadavg(),
|
|
34
|
+
uptime: osUptime(),
|
|
35
|
+
tmpdir: tmpdir(),
|
|
36
|
+
homedir: homedir(),
|
|
37
|
+
endianness: endianness(),
|
|
38
|
+
release: release(),
|
|
39
|
+
type: type()
|
|
40
|
+
};
|
|
41
|
+
// Runtime Context
|
|
42
|
+
const runtimeContext = {
|
|
43
|
+
timestamp: new Date().toISOString(),
|
|
44
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
45
|
+
locale: Intl.DateTimeFormat().resolvedOptions().locale,
|
|
46
|
+
executionTime: Date.now() - startTime
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
processInfo,
|
|
50
|
+
systemEnvironment,
|
|
51
|
+
runtimeContext
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
error: error instanceof Error ? error.message : String(error),
|
|
57
|
+
processInfo: { pid, cwd: cwd() },
|
|
58
|
+
systemEnvironment: { platform: platform() },
|
|
59
|
+
runtimeContext: {
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
executionTime: Date.now() - startTime
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
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 {};
|
|
@@ -0,0 +1,132 @@
|
|
|
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 Desktop Commander. ` +
|
|
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/mYB6av\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/mYB6av';
|
|
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
|
+
}
|
|
@@ -36,6 +36,16 @@ export declare function readFileFromDisk(filePath: string, offset?: number, leng
|
|
|
36
36
|
* @returns File content or file result with metadata
|
|
37
37
|
*/
|
|
38
38
|
export declare function readFile(filePath: string, isUrl?: boolean, offset?: number, length?: number): Promise<FileResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Read file content without status messages for internal operations
|
|
41
|
+
* This function preserves exact file content including original line endings,
|
|
42
|
+
* which is essential for edit operations that need to maintain file formatting.
|
|
43
|
+
* @param filePath Path to the file
|
|
44
|
+
* @param offset Starting line number to read from (default: 0)
|
|
45
|
+
* @param length Maximum number of lines to read (default: from config or 1000)
|
|
46
|
+
* @returns File content without status headers, with preserved line endings
|
|
47
|
+
*/
|
|
48
|
+
export declare function readFileInternal(filePath: string, offset?: number, length?: number): Promise<string>;
|
|
39
49
|
export declare function writeFile(filePath: string, content: string, mode?: 'rewrite' | 'append'): Promise<void>;
|
|
40
50
|
export interface MultiFileResult {
|
|
41
51
|
path: string;
|