@wonderwhy-er/desktop-commander 0.2.16 → 0.2.18-alpha.0
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 +3 -5
- package/dist/data/spec-kit-prompts.json +123 -0
- package/dist/handlers/filesystem-handlers.js +5 -2
- package/dist/handlers/history-handlers.d.ts +5 -0
- package/dist/handlers/history-handlers.js +35 -0
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +1 -0
- package/dist/http-index.d.ts +45 -0
- package/dist/http-index.js +51 -0
- package/dist/http-server-auto-tunnel.d.ts +1 -0
- package/dist/http-server-auto-tunnel.js +667 -0
- package/dist/http-server-named-tunnel.d.ts +2 -0
- package/dist/http-server-named-tunnel.js +167 -0
- package/dist/http-server-tunnel.d.ts +2 -0
- package/dist/http-server-tunnel.js +111 -0
- package/dist/http-server.d.ts +2 -0
- package/dist/http-server.js +270 -0
- package/dist/index.js +4 -0
- package/dist/oauth/auth-middleware.d.ts +20 -0
- package/dist/oauth/auth-middleware.js +62 -0
- package/dist/oauth/index.d.ts +3 -0
- package/dist/oauth/index.js +3 -0
- package/dist/oauth/oauth-manager.d.ts +80 -0
- package/dist/oauth/oauth-manager.js +179 -0
- package/dist/oauth/oauth-routes.d.ts +3 -0
- package/dist/oauth/oauth-routes.js +377 -0
- package/dist/server.js +316 -210
- package/dist/setup-claude-server.js +29 -5
- package/dist/terminal-manager.d.ts +1 -1
- package/dist/terminal-manager.js +56 -1
- package/dist/tools/config.js +15 -1
- package/dist/tools/feedback.js +2 -2
- package/dist/tools/filesystem.d.ts +1 -1
- package/dist/tools/filesystem.js +51 -3
- package/dist/tools/improved-process-tools.js +179 -58
- package/dist/tools/schemas.d.ts +25 -0
- package/dist/tools/schemas.js +10 -0
- package/dist/types.d.ts +19 -0
- package/dist/utils/feature-flags.d.ts +43 -0
- package/dist/utils/feature-flags.js +147 -0
- package/dist/utils/toolHistory.d.ts +73 -0
- package/dist/utils/toolHistory.js +192 -0
- package/dist/utils/usageTracker.d.ts +4 -0
- package/dist/utils/usageTracker.js +63 -37
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -1
|
@@ -44,7 +44,7 @@ async function initConfigFile() {
|
|
|
44
44
|
// Ensure config directory exists
|
|
45
45
|
const configDir = path.dirname(CONFIG_FILE);
|
|
46
46
|
if (!existsSync(configDir)) {
|
|
47
|
-
|
|
47
|
+
mkdirSync(configDir, { recursive: true });
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// Check if config file exists
|
|
@@ -206,6 +206,28 @@ function detectShell() {
|
|
|
206
206
|
return 'unknown-shell';
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
// Function to get the package spec that was used to run this script
|
|
210
|
+
function getPackageSpec() {
|
|
211
|
+
// Check if running via npx - look for the package spec in process.argv
|
|
212
|
+
// e.g., npx @wonderwhy-er/desktop-commander@0.2.18-alpha setup
|
|
213
|
+
const argv = process.argv;
|
|
214
|
+
|
|
215
|
+
// Look for the package name in argv
|
|
216
|
+
for (let i = 0; i < argv.length; i++) {
|
|
217
|
+
const arg = argv[i];
|
|
218
|
+
if (arg.includes('@wonderwhy-er/desktop-commander')) {
|
|
219
|
+
// Extract just the package spec (e.g., @wonderwhy-er/desktop-commander@0.2.18-alpha)
|
|
220
|
+
const match = arg.match(/(@wonderwhy-er\/desktop-commander(@[^\/\s]+)?)/);
|
|
221
|
+
if (match) {
|
|
222
|
+
return match[1];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Fallback to @latest if we can't detect
|
|
228
|
+
return '@wonderwhy-er/desktop-commander@latest';
|
|
229
|
+
}
|
|
230
|
+
|
|
209
231
|
// Function to determine execution context
|
|
210
232
|
function getExecutionContext() {
|
|
211
233
|
// Check if running from npx
|
|
@@ -728,6 +750,7 @@ export default async function setup() {
|
|
|
728
750
|
"DEBUG": "*"
|
|
729
751
|
};
|
|
730
752
|
|
|
753
|
+
const packageSpec = getPackageSpec();
|
|
731
754
|
serverConfig = {
|
|
732
755
|
"command": isWindows ? "node.exe" : "node",
|
|
733
756
|
"args": [
|
|
@@ -735,11 +758,11 @@ export default async function setup() {
|
|
|
735
758
|
isWindows ?
|
|
736
759
|
join(process.env.APPDATA || '', "npm", "npx.cmd").replace(/\\/g, '\\\\') :
|
|
737
760
|
"$(which npx)",
|
|
738
|
-
|
|
761
|
+
packageSpec
|
|
739
762
|
],
|
|
740
763
|
"env": debugEnv
|
|
741
764
|
};
|
|
742
|
-
await trackEvent('npx_setup_config_debug_npx');
|
|
765
|
+
await trackEvent('npx_setup_config_debug_npx', { packageSpec });
|
|
743
766
|
} else {
|
|
744
767
|
// Debug with local installation path
|
|
745
768
|
const indexPath = join(__dirname, 'dist', 'index.js');
|
|
@@ -763,13 +786,14 @@ export default async function setup() {
|
|
|
763
786
|
} else {
|
|
764
787
|
// Standard configuration without debug
|
|
765
788
|
if (isNpx) {
|
|
789
|
+
const packageSpec = getPackageSpec();
|
|
766
790
|
serverConfig = {
|
|
767
791
|
"command": isWindows ? "npx.cmd" : "npx",
|
|
768
792
|
"args": [
|
|
769
|
-
|
|
793
|
+
packageSpec
|
|
770
794
|
]
|
|
771
795
|
};
|
|
772
|
-
await trackEvent('npx_setup_config_standard_npx');
|
|
796
|
+
await trackEvent('npx_setup_config_standard_npx', { packageSpec });
|
|
773
797
|
} else {
|
|
774
798
|
// For local installation, use absolute path to handle Windows properly
|
|
775
799
|
const indexPath = join(__dirname, 'dist', 'index.js');
|
|
@@ -16,7 +16,7 @@ export declare class TerminalManager {
|
|
|
16
16
|
* @returns Whether input was successfully sent
|
|
17
17
|
*/
|
|
18
18
|
sendInputToProcess(pid: number, input: string): boolean;
|
|
19
|
-
executeCommand(command: string, timeoutMs?: number, shell?: string): Promise<CommandExecutionResult>;
|
|
19
|
+
executeCommand(command: string, timeoutMs?: number, shell?: string, collectTiming?: boolean): Promise<CommandExecutionResult>;
|
|
20
20
|
getNewOutput(pid: number): string | null;
|
|
21
21
|
/**
|
|
22
22
|
* Get a session by PID
|
package/dist/terminal-manager.js
CHANGED
|
@@ -33,7 +33,7 @@ export class TerminalManager {
|
|
|
33
33
|
return false;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
async executeCommand(command, timeoutMs = DEFAULT_COMMAND_TIMEOUT, shell) {
|
|
36
|
+
async executeCommand(command, timeoutMs = DEFAULT_COMMAND_TIMEOUT, shell, collectTiming = false) {
|
|
37
37
|
// Get the shell from config if not specified
|
|
38
38
|
let shellToUse = shell;
|
|
39
39
|
if (!shellToUse) {
|
|
@@ -81,6 +81,12 @@ export class TerminalManager {
|
|
|
81
81
|
startTime: new Date()
|
|
82
82
|
};
|
|
83
83
|
this.sessions.set(childProcess.pid, session);
|
|
84
|
+
// Timing telemetry
|
|
85
|
+
const startTime = Date.now();
|
|
86
|
+
let firstOutputTime;
|
|
87
|
+
let lastOutputTime;
|
|
88
|
+
const outputEvents = [];
|
|
89
|
+
let exitReason = 'timeout';
|
|
84
90
|
return new Promise((resolve) => {
|
|
85
91
|
let resolved = false;
|
|
86
92
|
let periodicCheck = null;
|
|
@@ -92,15 +98,47 @@ export class TerminalManager {
|
|
|
92
98
|
resolved = true;
|
|
93
99
|
if (periodicCheck)
|
|
94
100
|
clearInterval(periodicCheck);
|
|
101
|
+
// Add timing info if requested
|
|
102
|
+
if (collectTiming) {
|
|
103
|
+
const endTime = Date.now();
|
|
104
|
+
result.timingInfo = {
|
|
105
|
+
startTime,
|
|
106
|
+
endTime,
|
|
107
|
+
totalDurationMs: endTime - startTime,
|
|
108
|
+
exitReason,
|
|
109
|
+
firstOutputTime,
|
|
110
|
+
lastOutputTime,
|
|
111
|
+
timeToFirstOutputMs: firstOutputTime ? firstOutputTime - startTime : undefined,
|
|
112
|
+
outputEvents: outputEvents.length > 0 ? outputEvents : undefined
|
|
113
|
+
};
|
|
114
|
+
}
|
|
95
115
|
resolve(result);
|
|
96
116
|
};
|
|
97
117
|
childProcess.stdout.on('data', (data) => {
|
|
98
118
|
const text = data.toString();
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
if (!firstOutputTime)
|
|
121
|
+
firstOutputTime = now;
|
|
122
|
+
lastOutputTime = now;
|
|
99
123
|
output += text;
|
|
100
124
|
session.lastOutput += text;
|
|
125
|
+
// Record output event if collecting timing
|
|
126
|
+
if (collectTiming) {
|
|
127
|
+
outputEvents.push({
|
|
128
|
+
timestamp: now,
|
|
129
|
+
deltaMs: now - startTime,
|
|
130
|
+
source: 'stdout',
|
|
131
|
+
length: text.length,
|
|
132
|
+
snippet: text.slice(0, 50).replace(/\n/g, '\\n')
|
|
133
|
+
});
|
|
134
|
+
}
|
|
101
135
|
// Immediate check for obvious prompts
|
|
102
136
|
if (quickPromptPatterns.test(text)) {
|
|
103
137
|
session.isBlocked = true;
|
|
138
|
+
exitReason = 'early_exit_quick_pattern';
|
|
139
|
+
if (collectTiming && outputEvents.length > 0) {
|
|
140
|
+
outputEvents[outputEvents.length - 1].matchedPattern = 'quick_pattern';
|
|
141
|
+
}
|
|
104
142
|
resolveOnce({
|
|
105
143
|
pid: childProcess.pid,
|
|
106
144
|
output,
|
|
@@ -110,8 +148,22 @@ export class TerminalManager {
|
|
|
110
148
|
});
|
|
111
149
|
childProcess.stderr.on('data', (data) => {
|
|
112
150
|
const text = data.toString();
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
if (!firstOutputTime)
|
|
153
|
+
firstOutputTime = now;
|
|
154
|
+
lastOutputTime = now;
|
|
113
155
|
output += text;
|
|
114
156
|
session.lastOutput += text;
|
|
157
|
+
// Record output event if collecting timing
|
|
158
|
+
if (collectTiming) {
|
|
159
|
+
outputEvents.push({
|
|
160
|
+
timestamp: now,
|
|
161
|
+
deltaMs: now - startTime,
|
|
162
|
+
source: 'stderr',
|
|
163
|
+
length: text.length,
|
|
164
|
+
snippet: text.slice(0, 50).replace(/\n/g, '\\n')
|
|
165
|
+
});
|
|
166
|
+
}
|
|
115
167
|
});
|
|
116
168
|
// Periodic comprehensive check every 100ms
|
|
117
169
|
periodicCheck = setInterval(() => {
|
|
@@ -119,6 +171,7 @@ export class TerminalManager {
|
|
|
119
171
|
const processState = analyzeProcessState(output, childProcess.pid);
|
|
120
172
|
if (processState.isWaitingForInput) {
|
|
121
173
|
session.isBlocked = true;
|
|
174
|
+
exitReason = 'early_exit_periodic_check';
|
|
122
175
|
resolveOnce({
|
|
123
176
|
pid: childProcess.pid,
|
|
124
177
|
output,
|
|
@@ -130,6 +183,7 @@ export class TerminalManager {
|
|
|
130
183
|
// Timeout fallback
|
|
131
184
|
setTimeout(() => {
|
|
132
185
|
session.isBlocked = true;
|
|
186
|
+
exitReason = 'timeout';
|
|
133
187
|
resolveOnce({
|
|
134
188
|
pid: childProcess.pid,
|
|
135
189
|
output,
|
|
@@ -153,6 +207,7 @@ export class TerminalManager {
|
|
|
153
207
|
}
|
|
154
208
|
this.sessions.delete(childProcess.pid);
|
|
155
209
|
}
|
|
210
|
+
exitReason = 'process_exit';
|
|
156
211
|
resolveOnce({
|
|
157
212
|
pid: childProcess.pid,
|
|
158
213
|
output,
|
package/dist/tools/config.js
CHANGED
|
@@ -2,6 +2,7 @@ import { configManager } from '../config-manager.js';
|
|
|
2
2
|
import { SetConfigValueArgsSchema } from './schemas.js';
|
|
3
3
|
import { getSystemInfo } from '../utils/system-info.js';
|
|
4
4
|
import { currentClient } from '../server.js';
|
|
5
|
+
import { featureFlagManager } from '../utils/feature-flags.js';
|
|
5
6
|
/**
|
|
6
7
|
* Get the entire config including system information
|
|
7
8
|
*/
|
|
@@ -11,10 +12,23 @@ export async function getConfig() {
|
|
|
11
12
|
const config = await configManager.getConfig();
|
|
12
13
|
// Add system information and current client to the config response
|
|
13
14
|
const systemInfo = getSystemInfo();
|
|
15
|
+
// Get memory usage
|
|
16
|
+
const memoryUsage = process.memoryUsage();
|
|
17
|
+
const memory = {
|
|
18
|
+
rss: `${(memoryUsage.rss / 1024 / 1024).toFixed(2)} MB`,
|
|
19
|
+
heapTotal: `${(memoryUsage.heapTotal / 1024 / 1024).toFixed(2)} MB`,
|
|
20
|
+
heapUsed: `${(memoryUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`,
|
|
21
|
+
external: `${(memoryUsage.external / 1024 / 1024).toFixed(2)} MB`,
|
|
22
|
+
arrayBuffers: `${(memoryUsage.arrayBuffers / 1024 / 1024).toFixed(2)} MB`
|
|
23
|
+
};
|
|
14
24
|
const configWithSystemInfo = {
|
|
15
25
|
...config,
|
|
16
26
|
currentClient,
|
|
17
|
-
|
|
27
|
+
featureFlags: featureFlagManager.getAll(),
|
|
28
|
+
systemInfo: {
|
|
29
|
+
...systemInfo,
|
|
30
|
+
memory
|
|
31
|
+
}
|
|
18
32
|
};
|
|
19
33
|
console.error(`getConfig result: ${JSON.stringify(configWithSystemInfo, null, 2)}`);
|
|
20
34
|
return {
|
package/dist/tools/feedback.js
CHANGED
|
@@ -73,7 +73,7 @@ export async function giveFeedbackToDesktopCommander(params = {}) {
|
|
|
73
73
|
content: [{
|
|
74
74
|
type: "text",
|
|
75
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/
|
|
76
|
+
`You can still access our feedback form directly at: https://tally.so/r/mKqoKg\n\n` +
|
|
77
77
|
`We appreciate your willingness to provide feedback!`
|
|
78
78
|
}],
|
|
79
79
|
isError: true
|
|
@@ -84,7 +84,7 @@ export async function giveFeedbackToDesktopCommander(params = {}) {
|
|
|
84
84
|
* Build Tally.so URL with pre-filled parameters
|
|
85
85
|
*/
|
|
86
86
|
async function buildTallyUrl(params, stats) {
|
|
87
|
-
const baseUrl = 'https://tally.so/r/
|
|
87
|
+
const baseUrl = 'https://tally.so/r/mKqoKg';
|
|
88
88
|
const urlParams = new URLSearchParams();
|
|
89
89
|
// Only auto-filled hidden fields remain
|
|
90
90
|
urlParams.set('tool_call_count', stats.totalToolCalls.toString());
|
|
@@ -56,7 +56,7 @@ export interface MultiFileResult {
|
|
|
56
56
|
}
|
|
57
57
|
export declare function readMultipleFiles(paths: string[]): Promise<MultiFileResult[]>;
|
|
58
58
|
export declare function createDirectory(dirPath: string): Promise<void>;
|
|
59
|
-
export declare function listDirectory(dirPath: string): Promise<string[]>;
|
|
59
|
+
export declare function listDirectory(dirPath: string, depth?: number): Promise<string[]>;
|
|
60
60
|
export declare function moveFile(sourcePath: string, destinationPath: string): Promise<void>;
|
|
61
61
|
export declare function searchFiles(rootPath: string, pattern: string): Promise<string[]>;
|
|
62
62
|
export declare function getFileInfo(filePath: string): Promise<Record<string, any>>;
|
package/dist/tools/filesystem.js
CHANGED
|
@@ -757,10 +757,58 @@ export async function createDirectory(dirPath) {
|
|
|
757
757
|
const validPath = await validatePath(dirPath);
|
|
758
758
|
await fs.mkdir(validPath, { recursive: true });
|
|
759
759
|
}
|
|
760
|
-
export async function listDirectory(dirPath) {
|
|
760
|
+
export async function listDirectory(dirPath, depth = 2) {
|
|
761
761
|
const validPath = await validatePath(dirPath);
|
|
762
|
-
const
|
|
763
|
-
|
|
762
|
+
const results = [];
|
|
763
|
+
const MAX_NESTED_ITEMS = 100; // Maximum items to show per nested directory
|
|
764
|
+
async function listRecursive(currentPath, currentDepth, relativePath = '', isTopLevel = true) {
|
|
765
|
+
if (currentDepth <= 0)
|
|
766
|
+
return;
|
|
767
|
+
let entries;
|
|
768
|
+
try {
|
|
769
|
+
entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
770
|
+
}
|
|
771
|
+
catch (error) {
|
|
772
|
+
// If we can't read this directory (permission denied), show as denied
|
|
773
|
+
const displayPath = relativePath || path.basename(currentPath);
|
|
774
|
+
results.push(`[DENIED] ${displayPath}`);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
// Apply filtering for nested directories (not top level)
|
|
778
|
+
const totalEntries = entries.length;
|
|
779
|
+
let entriesToShow = entries;
|
|
780
|
+
let filteredCount = 0;
|
|
781
|
+
if (!isTopLevel && totalEntries > MAX_NESTED_ITEMS) {
|
|
782
|
+
entriesToShow = entries.slice(0, MAX_NESTED_ITEMS);
|
|
783
|
+
filteredCount = totalEntries - MAX_NESTED_ITEMS;
|
|
784
|
+
}
|
|
785
|
+
for (const entry of entriesToShow) {
|
|
786
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
787
|
+
const displayPath = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
|
788
|
+
// Add this entry to results
|
|
789
|
+
results.push(`${entry.isDirectory() ? "[DIR]" : "[FILE]"} ${displayPath}`);
|
|
790
|
+
// If it's a directory and we have depth remaining, recurse
|
|
791
|
+
if (entry.isDirectory() && currentDepth > 1) {
|
|
792
|
+
try {
|
|
793
|
+
// Validate the path before recursing
|
|
794
|
+
await validatePath(fullPath);
|
|
795
|
+
await listRecursive(fullPath, currentDepth - 1, displayPath, false);
|
|
796
|
+
}
|
|
797
|
+
catch (error) {
|
|
798
|
+
// If validation fails or we can't access it, it will be marked as denied
|
|
799
|
+
// when we try to read it in the recursive call
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
// Add warning message if items were filtered
|
|
805
|
+
if (filteredCount > 0) {
|
|
806
|
+
const displayPath = relativePath || path.basename(currentPath);
|
|
807
|
+
results.push(`[WARNING] ${displayPath}: ${filteredCount} items hidden (showing first ${MAX_NESTED_ITEMS} of ${totalEntries} total)`);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
await listRecursive(validPath, depth, '', true);
|
|
811
|
+
return results;
|
|
764
812
|
}
|
|
765
813
|
export async function moveFile(sourcePath, destinationPath) {
|
|
766
814
|
const validSourcePath = await validatePath(sourcePath);
|