@wonderwhy-er/desktop-commander 0.2.29-alpha.0 → 0.2.29-alpha.10

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.
Files changed (59) hide show
  1. package/dist/index.js +10 -0
  2. package/dist/npm-scripts/remote.d.ts +1 -0
  3. package/dist/npm-scripts/remote.js +20 -0
  4. package/dist/remote-device/desktop-commander-integration.d.ts +143 -0
  5. package/dist/remote-device/desktop-commander-integration.js +147 -0
  6. package/dist/remote-device/device-authenticator.d.ts +16 -0
  7. package/dist/remote-device/device-authenticator.js +120 -0
  8. package/dist/remote-device/device.d.ts +25 -0
  9. package/dist/remote-device/device.js +308 -0
  10. package/dist/remote-device/remote-channel.d.ts +51 -0
  11. package/dist/remote-device/remote-channel.js +255 -0
  12. package/dist/remote-device/scripts/blocking-offline-update.js +64 -0
  13. package/dist/remote-device/templates/auth-success.d.ts +1 -0
  14. package/dist/remote-device/templates/auth-success.js +30 -0
  15. package/dist/server.js +39 -11
  16. package/dist/version.d.ts +1 -1
  17. package/dist/version.js +1 -1
  18. package/package.json +8 -3
  19. package/dist/data/spec-kit-prompts.json +0 -123
  20. package/dist/handlers/node-handlers.d.ts +0 -6
  21. package/dist/handlers/node-handlers.js +0 -73
  22. package/dist/handlers/test-crash-handler.d.ts +0 -11
  23. package/dist/handlers/test-crash-handler.js +0 -26
  24. package/dist/http-index.d.ts +0 -45
  25. package/dist/http-index.js +0 -51
  26. package/dist/http-server-auto-tunnel.d.ts +0 -1
  27. package/dist/http-server-auto-tunnel.js +0 -667
  28. package/dist/http-server-named-tunnel.d.ts +0 -2
  29. package/dist/http-server-named-tunnel.js +0 -167
  30. package/dist/http-server-tunnel.d.ts +0 -2
  31. package/dist/http-server-tunnel.js +0 -111
  32. package/dist/http-server.d.ts +0 -2
  33. package/dist/http-server.js +0 -270
  34. package/dist/index-oauth.d.ts +0 -2
  35. package/dist/index-oauth.js +0 -201
  36. package/dist/oauth/auth-middleware.d.ts +0 -20
  37. package/dist/oauth/auth-middleware.js +0 -62
  38. package/dist/oauth/index.d.ts +0 -3
  39. package/dist/oauth/index.js +0 -3
  40. package/dist/oauth/oauth-manager.d.ts +0 -80
  41. package/dist/oauth/oauth-manager.js +0 -179
  42. package/dist/oauth/oauth-routes.d.ts +0 -3
  43. package/dist/oauth/oauth-routes.js +0 -377
  44. package/dist/oauth/provider.d.ts +0 -22
  45. package/dist/oauth/provider.js +0 -124
  46. package/dist/oauth/server.d.ts +0 -18
  47. package/dist/oauth/server.js +0 -160
  48. package/dist/oauth/types.d.ts +0 -54
  49. package/dist/oauth/types.js +0 -2
  50. package/dist/setup.log +0 -275
  51. package/dist/test-setup.js +0 -14
  52. package/dist/tools/pdf-processor.d.ts +0 -1
  53. package/dist/tools/pdf-processor.js +0 -3
  54. package/dist/tools/search.d.ts +0 -32
  55. package/dist/tools/search.js +0 -202
  56. package/dist/utils/crash-logger.d.ts +0 -18
  57. package/dist/utils/crash-logger.js +0 -44
  58. package/dist/utils/dedent.d.ts +0 -8
  59. package/dist/utils/dedent.js +0 -38
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { runSetup } from './npm-scripts/setup.js';
7
7
  import { runUninstall } from './npm-scripts/uninstall.js';
8
8
  import { capture } from './utils/capture.js';
9
9
  import { logToStderr, logger } from './utils/logger.js';
10
+ import { runRemote } from './npm-scripts/remote.js';
10
11
  // Store messages to defer until after initialization
11
12
  const deferredMessages = [];
12
13
  function deferLog(level, message) {
@@ -24,6 +25,15 @@ async function runServer() {
24
25
  await runUninstall();
25
26
  return;
26
27
  }
28
+ if (process.argv[2] === 'remote') {
29
+ await runRemote();
30
+ return;
31
+ }
32
+ // Check if first argument is "remote"
33
+ if (process.argv[2] === 'remote') {
34
+ await runRemote();
35
+ return;
36
+ }
27
37
  // Parse command line arguments for onboarding control
28
38
  const DISABLE_ONBOARDING = process.argv.includes('--no-onboarding');
29
39
  if (DISABLE_ONBOARDING) {
@@ -0,0 +1 @@
1
+ export declare function runRemote(): Promise<void>;
@@ -0,0 +1,20 @@
1
+ import { MCPDevice } from '../remote-device/device.js';
2
+ import os from 'os';
3
+ import caffeinate from 'caffeinate';
4
+ export async function runRemote() {
5
+ const persistSession = process.argv.includes('--persist-session');
6
+ const disableNoSleep = process.argv.includes('--disable-no-sleep');
7
+ // Start caffeinate on macOS (unless disabled)
8
+ // Caffeinate will monitor this process and automatically exit when it terminates
9
+ if (!disableNoSleep && os.platform() === 'darwin') {
10
+ try {
11
+ await caffeinate({ pid: process.pid });
12
+ console.log('☕ Caffeinate started (preventing system sleep)');
13
+ }
14
+ catch (error) {
15
+ console.warn('⚠️ Failed to start caffeinate:', error);
16
+ }
17
+ }
18
+ const device = new MCPDevice({ persistSession });
19
+ await device.start();
20
+ }
@@ -0,0 +1,143 @@
1
+ interface McpConfig {
2
+ command: string;
3
+ args: string[];
4
+ cwd?: string;
5
+ env?: Record<string, string>;
6
+ }
7
+ export declare class DesktopCommanderIntegration {
8
+ private mcpClient;
9
+ private mcpTransport;
10
+ private isReady;
11
+ initialize(): Promise<void>;
12
+ resolveMcpConfig(): Promise<McpConfig | null>;
13
+ callClientTool(toolName: string, args: any, metadata?: any): Promise<{
14
+ [x: string]: unknown;
15
+ content: ({
16
+ type: "text";
17
+ text: string;
18
+ annotations?: {
19
+ audience?: ("user" | "assistant")[] | undefined;
20
+ priority?: number | undefined;
21
+ lastModified?: string | undefined;
22
+ } | undefined;
23
+ _meta?: Record<string, unknown> | undefined;
24
+ } | {
25
+ type: "image";
26
+ data: string;
27
+ mimeType: string;
28
+ annotations?: {
29
+ audience?: ("user" | "assistant")[] | undefined;
30
+ priority?: number | undefined;
31
+ lastModified?: string | undefined;
32
+ } | undefined;
33
+ _meta?: Record<string, unknown> | undefined;
34
+ } | {
35
+ type: "audio";
36
+ data: string;
37
+ mimeType: string;
38
+ annotations?: {
39
+ audience?: ("user" | "assistant")[] | undefined;
40
+ priority?: number | undefined;
41
+ lastModified?: string | undefined;
42
+ } | undefined;
43
+ _meta?: Record<string, unknown> | undefined;
44
+ } | {
45
+ type: "resource";
46
+ resource: {
47
+ uri: string;
48
+ text: string;
49
+ mimeType?: string | undefined;
50
+ _meta?: Record<string, unknown> | undefined;
51
+ } | {
52
+ uri: string;
53
+ blob: string;
54
+ mimeType?: string | undefined;
55
+ _meta?: Record<string, unknown> | undefined;
56
+ };
57
+ annotations?: {
58
+ audience?: ("user" | "assistant")[] | undefined;
59
+ priority?: number | undefined;
60
+ lastModified?: string | undefined;
61
+ } | undefined;
62
+ _meta?: Record<string, unknown> | undefined;
63
+ } | {
64
+ uri: string;
65
+ name: string;
66
+ type: "resource_link";
67
+ description?: string | undefined;
68
+ mimeType?: string | undefined;
69
+ annotations?: {
70
+ audience?: ("user" | "assistant")[] | undefined;
71
+ priority?: number | undefined;
72
+ lastModified?: string | undefined;
73
+ } | undefined;
74
+ _meta?: {
75
+ [x: string]: unknown;
76
+ } | undefined;
77
+ icons?: {
78
+ src: string;
79
+ mimeType?: string | undefined;
80
+ sizes?: string[] | undefined;
81
+ theme?: "light" | "dark" | undefined;
82
+ }[] | undefined;
83
+ title?: string | undefined;
84
+ })[];
85
+ _meta?: {
86
+ [x: string]: unknown;
87
+ progressToken?: string | number | undefined;
88
+ "io.modelcontextprotocol/related-task"?: {
89
+ taskId: string;
90
+ } | undefined;
91
+ } | undefined;
92
+ structuredContent?: Record<string, unknown> | undefined;
93
+ isError?: boolean | undefined;
94
+ } | {
95
+ [x: string]: unknown;
96
+ toolResult: unknown;
97
+ _meta?: {
98
+ [x: string]: unknown;
99
+ progressToken?: string | number | undefined;
100
+ "io.modelcontextprotocol/related-task"?: {
101
+ taskId: string;
102
+ } | undefined;
103
+ } | undefined;
104
+ }>;
105
+ listClientTools(): Promise<{
106
+ tools: {
107
+ inputSchema: {
108
+ [x: string]: unknown;
109
+ type: "object";
110
+ properties?: Record<string, object> | undefined;
111
+ required?: string[] | undefined;
112
+ };
113
+ name: string;
114
+ description?: string | undefined;
115
+ outputSchema?: {
116
+ [x: string]: unknown;
117
+ type: "object";
118
+ properties?: Record<string, object> | undefined;
119
+ required?: string[] | undefined;
120
+ } | undefined;
121
+ annotations?: {
122
+ title?: string | undefined;
123
+ readOnlyHint?: boolean | undefined;
124
+ destructiveHint?: boolean | undefined;
125
+ idempotentHint?: boolean | undefined;
126
+ openWorldHint?: boolean | undefined;
127
+ } | undefined;
128
+ execution?: {
129
+ taskSupport?: "optional" | "required" | "forbidden" | undefined;
130
+ } | undefined;
131
+ _meta?: Record<string, unknown> | undefined;
132
+ icons?: {
133
+ src: string;
134
+ mimeType?: string | undefined;
135
+ sizes?: string[] | undefined;
136
+ theme?: "light" | "dark" | undefined;
137
+ }[] | undefined;
138
+ title?: string | undefined;
139
+ }[];
140
+ }>;
141
+ shutdown(): Promise<void>;
142
+ }
143
+ export {};
@@ -0,0 +1,147 @@
1
+ import { spawn } from 'child_process';
2
+ import path from 'path';
3
+ import fs from 'fs/promises';
4
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
6
+ import { fileURLToPath } from 'url';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ export class DesktopCommanderIntegration {
10
+ constructor() {
11
+ this.mcpClient = null;
12
+ this.mcpTransport = null;
13
+ this.isReady = false;
14
+ }
15
+ async initialize() {
16
+ const config = await this.resolveMcpConfig();
17
+ if (!config) {
18
+ throw new Error('Desktop Commander MCP not found. Please install it globally via `npm install -g @wonderwhy-er/desktop-commander` or build the local project.');
19
+ }
20
+ console.log(` - ⏳ Connecting to Local Desktop Commander MCP using: ${config.command} ${config.args.join(' ')}`);
21
+ try {
22
+ this.mcpTransport = new StdioClientTransport(config);
23
+ // Create MCP client
24
+ this.mcpClient = new Client({
25
+ name: "desktop-commander-client",
26
+ version: "1.0.0"
27
+ }, {
28
+ capabilities: {}
29
+ });
30
+ // Connect to Desktop Commander
31
+ await this.mcpClient.connect(this.mcpTransport);
32
+ this.isReady = true;
33
+ console.log(' - 🔌 Connected to Desktop Commander MCP');
34
+ }
35
+ catch (error) {
36
+ console.error(' - ❌ Failed to connect to Desktop Commander MCP:', error);
37
+ throw error;
38
+ }
39
+ }
40
+ async resolveMcpConfig() {
41
+ // Option 1: Development/Local Build
42
+ // Adjusting path resolution since we are now in src/remote-device and dist is in root/dist
43
+ // Original: path.resolve(__dirname, '../../dist/index.js')
44
+ const devPath = path.resolve(__dirname, '../../dist/index.js');
45
+ try {
46
+ await fs.access(devPath);
47
+ console.debug(' - 🔍 Found local MCP server at:', devPath);
48
+ return {
49
+ command: process.execPath, // Use the current node executable
50
+ args: [devPath],
51
+ cwd: path.dirname(devPath)
52
+ };
53
+ }
54
+ catch {
55
+ // Local file not found, continue...
56
+ }
57
+ // Option 2: Global Installation
58
+ const commandName = 'desktop-commander';
59
+ try {
60
+ await new Promise((resolve, reject) => {
61
+ // Use 'which' to check if the command exists in PATH
62
+ // We can't run it directly as it's an stdio MCP server that waits for input
63
+ const check = spawn('which', [commandName]);
64
+ check.on('error', reject);
65
+ check.on('close', (code) => code === 0 ? resolve() : reject(new Error('Command not found')));
66
+ });
67
+ console.debug(' - Found global desktop-commander CLI');
68
+ return {
69
+ command: commandName,
70
+ args: []
71
+ };
72
+ }
73
+ catch {
74
+ // Global command not found
75
+ }
76
+ return null;
77
+ }
78
+ async callClientTool(toolName, args, metadata) {
79
+ if (!this.isReady || !this.mcpClient) {
80
+ throw new Error('DesktopIntegration not initialized');
81
+ }
82
+ // Proxy other tools to MCP server
83
+ try {
84
+ console.log(`Forwarding tool call ${toolName} to MCP server`, metadata);
85
+ const result = await this.mcpClient.callTool({
86
+ name: toolName,
87
+ arguments: args,
88
+ _meta: { remote: true, ...metadata || {} }
89
+ });
90
+ return result;
91
+ }
92
+ catch (error) {
93
+ console.error(`Error executing tool ${toolName}:`, error);
94
+ throw error;
95
+ }
96
+ }
97
+ async listClientTools() {
98
+ if (!this.mcpClient)
99
+ return { tools: [] };
100
+ try {
101
+ // List tools from MCP server
102
+ const mcpTools = await this.mcpClient.listTools();
103
+ // Merge tools
104
+ return {
105
+ tools: mcpTools.tools || []
106
+ };
107
+ }
108
+ catch (error) {
109
+ console.error('Error fetching capabilities:', error);
110
+ // Fallback to local tools
111
+ return {
112
+ tools: []
113
+ };
114
+ }
115
+ }
116
+ async shutdown() {
117
+ const closeWithTimeout = async (operation, name, timeoutMs = 3000) => {
118
+ return Promise.race([
119
+ operation(),
120
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`${name} timeout after ${timeoutMs}ms`)), timeoutMs))
121
+ ]);
122
+ };
123
+ if (this.mcpClient) {
124
+ try {
125
+ console.log(' → Closing MCP client...');
126
+ await closeWithTimeout(() => this.mcpClient.close(), 'MCP client close');
127
+ console.log(' ✓ MCP client closed');
128
+ }
129
+ catch (e) {
130
+ console.warn(' ⚠️ MCP client close timeout or error:', e.message);
131
+ }
132
+ this.mcpClient = null;
133
+ }
134
+ if (this.mcpTransport) {
135
+ try {
136
+ console.log(' → Closing MCP transport...');
137
+ await closeWithTimeout(() => this.mcpTransport.close(), 'MCP transport close');
138
+ console.log(' ✓ MCP transport closed');
139
+ }
140
+ catch (e) {
141
+ console.warn(' ⚠️ MCP transport close timeout or error:', e.message);
142
+ }
143
+ this.mcpTransport = null;
144
+ }
145
+ this.isReady = false;
146
+ }
147
+ }
@@ -0,0 +1,16 @@
1
+ interface AuthSession {
2
+ access_token: string;
3
+ refresh_token: string | null;
4
+ device_id?: string;
5
+ }
6
+ export declare class DeviceAuthenticator {
7
+ private baseServerUrl;
8
+ constructor(baseServerUrl: string);
9
+ authenticate(deviceId?: string): Promise<AuthSession>;
10
+ private generatePKCE;
11
+ private requestDeviceCode;
12
+ private displayUserInstructions;
13
+ private pollForAuthorization;
14
+ private sleep;
15
+ }
16
+ export {};
@@ -0,0 +1,120 @@
1
+ import open from 'open';
2
+ import os from 'os';
3
+ import crypto from 'crypto';
4
+ const CLIENT_ID = 'mcp-device';
5
+ export class DeviceAuthenticator {
6
+ constructor(baseServerUrl) {
7
+ this.baseServerUrl = baseServerUrl;
8
+ }
9
+ async authenticate(deviceId) {
10
+ console.log('🔐 Starting device authorization flow...\n');
11
+ // Generate PKCE
12
+ const pkce = this.generatePKCE();
13
+ // Step 1: Request device code
14
+ const deviceAuth = await this.requestDeviceCode(pkce.challenge, deviceId);
15
+ // Step 2: Display user instructions and open browser
16
+ this.displayUserInstructions(deviceAuth);
17
+ // Step 3: Poll for authorization
18
+ const tokens = await this.pollForAuthorization(deviceAuth, pkce.verifier);
19
+ console.log(' - ✅ Authorization successful!\n');
20
+ return tokens;
21
+ }
22
+ generatePKCE() {
23
+ const verifier = crypto.randomBytes(32).toString('base64url');
24
+ const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
25
+ return { verifier, challenge };
26
+ }
27
+ async requestDeviceCode(codeChallenge, deviceId) {
28
+ console.log(' - 📡 Requesting device code...');
29
+ const response = await fetch(`${this.baseServerUrl}/device/start`, {
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json' },
32
+ body: JSON.stringify({
33
+ client_id: CLIENT_ID,
34
+ scope: 'mcp:tools',
35
+ device_name: os.hostname(),
36
+ device_type: 'mcp',
37
+ device_id: deviceId,
38
+ code_challenge: codeChallenge,
39
+ code_challenge_method: 'S256',
40
+ }),
41
+ });
42
+ if (!response.ok) {
43
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }));
44
+ throw new Error(error.error_description || 'Failed to start device flow');
45
+ }
46
+ const data = await response.json();
47
+ console.log(' - ✅ Device code received\n');
48
+ return data;
49
+ }
50
+ displayUserInstructions(deviceAuth) {
51
+ console.log('📋 Please complete authentication:\n');
52
+ console.log(' 1. Open this URL in your browser:');
53
+ console.log(` ${deviceAuth.verification_uri}\n`);
54
+ console.log(' 2. Enter this code when prompted:');
55
+ console.log(` ${deviceAuth.user_code}\n`);
56
+ console.log(` Code expires in ${Math.floor(deviceAuth.expires_in / 60)} minutes.\n`);
57
+ // Try to open browser automatically
58
+ open(deviceAuth.verification_uri_complete).catch(() => {
59
+ console.log(' - Could not open browser automatically.');
60
+ console.log(` - Please visit: ${deviceAuth.verification_uri}\n`);
61
+ });
62
+ console.log(' - ⏳ Waiting for authorization...\n');
63
+ }
64
+ async pollForAuthorization(deviceAuth, codeVerifier) {
65
+ const interval = (deviceAuth.interval || 5) * 1000;
66
+ const maxAttempts = Math.floor(deviceAuth.expires_in / (deviceAuth.interval || 5));
67
+ let attempt = 0;
68
+ while (attempt < maxAttempts) {
69
+ attempt++;
70
+ // Wait before polling
71
+ await this.sleep(interval);
72
+ try {
73
+ const response = await fetch(`${this.baseServerUrl}/device/poll`, {
74
+ method: 'POST',
75
+ headers: { 'Content-Type': 'application/json' },
76
+ body: JSON.stringify({
77
+ device_code: deviceAuth.device_code,
78
+ client_id: CLIENT_ID,
79
+ code_verifier: codeVerifier,
80
+ }),
81
+ });
82
+ if (response.ok) {
83
+ const tokens = await response.json();
84
+ if (tokens.access_token) {
85
+ return {
86
+ device_id: tokens.device_id,
87
+ access_token: tokens.access_token,
88
+ refresh_token: tokens.refresh_token || null,
89
+ };
90
+ }
91
+ }
92
+ const error = await response.json().catch(() => ({ error: 'unknown' }));
93
+ // Check error type
94
+ if (error.error === 'authorization_pending') {
95
+ // Still waiting - continue polling
96
+ continue;
97
+ }
98
+ if (error.error === 'slow_down') {
99
+ // Server requested slower polling
100
+ await this.sleep(interval);
101
+ continue;
102
+ }
103
+ // Terminal error
104
+ throw new Error(error.error_description || error.error || 'Authorization failed');
105
+ }
106
+ catch (fetchError) {
107
+ // Network error - retry unless we're out of attempts
108
+ if (attempt >= maxAttempts) {
109
+ throw fetchError;
110
+ }
111
+ // Continue polling on network errors
112
+ continue;
113
+ }
114
+ }
115
+ throw new Error('Authorization timeout - user did not authorize within the time limit');
116
+ }
117
+ sleep(ms) {
118
+ return new Promise((resolve) => setTimeout(resolve, ms));
119
+ }
120
+ }
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ export interface MCPDeviceOptions {
3
+ persistSession?: boolean;
4
+ }
5
+ export declare class MCPDevice {
6
+ private baseServerUrl;
7
+ private remoteChannel;
8
+ private deviceId?;
9
+ private user;
10
+ private isShuttingDown;
11
+ private configPath;
12
+ private persistSession;
13
+ private desktop;
14
+ constructor(options?: MCPDeviceOptions);
15
+ private setupShutdownHandlers;
16
+ start(): Promise<void>;
17
+ loadPersistedConfig(): Promise<any>;
18
+ savePersistedConfig(session: any): Promise<void>;
19
+ fetchSupabaseConfig(): Promise<{
20
+ supabaseUrl: any;
21
+ anonKey: any;
22
+ }>;
23
+ handleNewToolCall(payload: any): Promise<void>;
24
+ shutdown(): Promise<void>;
25
+ }