@wonderwhy-er/desktop-commander 0.2.17 → 0.2.18-alpha.1

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 CHANGED
@@ -10,10 +10,7 @@
10
10
  [![Discord](https://img.shields.io/badge/Join%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/kQ27sNnZr7)
11
11
 
12
12
 
13
- Work with code and text, run processes, and automate tasks, going far beyond other AI editors - without API token costs.
14
-
15
-
16
- ![Desktop Commander MCP](https://raw.githubusercontent.com/wonderwhy-er/ClaudeComputerCommander/main/docs/vertical_video_mobile.mp4)
13
+ Work with code and text, run processes, and automate tasks, going far beyond other AI editors - while using host client subscriptions instead of API token costs.
17
14
 
18
15
  <a href="https://glama.ai/mcp/servers/zempur9oh4">
19
16
  <img width="380" height="200" src="https://glama.ai/mcp/servers/zempur9oh4/badge" alt="Desktop Commander MCP" />
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,667 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ #;
54
+ Tunnel;
55
+ Options;
56
+ for (Desktop; Commander; #)
57
+ #;
58
+ The;
59
+ Problem;
60
+ Currently;
61
+ Desktop;
62
+ Commander;
63
+ requires: -;
64
+ Named;
65
+ tunnel(stable, URL) - `https://mcp.desktopcommander.app`
66
+ - ;
67
+ Requires;
68
+ own;
69
+ domain + Cloudflare;
70
+ account
71
+ - ;
72
+ Not;
73
+ accessible;
74
+ for (all; users
75
+ ** Goal; )
76
+ : ** Make;
77
+ Desktop;
78
+ Commander;
79
+ work;
80
+ for (users; without; their)
81
+ own;
82
+ infrastructure.
83
+ ;
84
+ -- - ;
85
+ #;
86
+ Solution;
87
+ Options;
88
+ #;
89
+ #;
90
+ #;
91
+ Option;
92
+ 1;
93
+ Random;
94
+ Cloudflare;
95
+ Tunnels(Quick, Start)
96
+ ** Best;
97
+ for (; ; )
98
+ : ** Testing, development, personal;
99
+ use
100
+ ** How;
101
+ it;
102
+ works: **
103
+ -Run `cloudflared tunnel --url http://localhost:3000`
104
+ - Get;
105
+ random;
106
+ URL: `https://random-words-1234.trycloudflare.com`
107
+ - ** Changes;
108
+ every;
109
+ restart **
110
+ ** Pros;
111
+ **
112
+ -;
113
+ No;
114
+ domain;
115
+ needed
116
+ - ;
117
+ No;
118
+ Cloudflare;
119
+ account;
120
+ needed
121
+ - ;
122
+ Free
123
+ - ;
124
+ Works;
125
+ immediately
126
+ ** Cons;
127
+ **
128
+ -;
129
+ URL;
130
+ changes;
131
+ on;
132
+ restart
133
+ - ;
134
+ Must;
135
+ reconfigure;
136
+ clients;
137
+ after;
138
+ restart
139
+ - ;
140
+ OAuth;
141
+ redirect;
142
+ URIs;
143
+ break ;
144
+ (need);
145
+ new URL;
146
+ each;
147
+ time;
148
+ ** Implementation;
149
+ **
150
+ `` `bash
151
+ # Already supported!
152
+ npm run start:tunnel
153
+
154
+ # Or manually:
155
+ cloudflared tunnel --url http://localhost:3000 &
156
+ node dist/http-server.js
157
+ ` ``
158
+ ** For;
159
+ OAuth: **
160
+ -;
161
+ Not;
162
+ viable(redirect, URIs, must, be, stable)
163
+ - ;
164
+ Works;
165
+ without;
166
+ auth;
167
+ -- - ;
168
+ #;
169
+ #;
170
+ Option;
171
+ 2;
172
+ Desktop;
173
+ Commander;
174
+ Cloud;
175
+ Service(Recommended)
176
+ ** Best;
177
+ for (; ; )
178
+ : ** Production;
179
+ use, end;
180
+ users
181
+ ** How;
182
+ it;
183
+ works: **
184
+ 1.;
185
+ Desktop;
186
+ Commander;
187
+ hosts;
188
+ a;
189
+ proxy;
190
+ service;
191
+ 2.;
192
+ Users;
193
+ run;
194
+ local;
195
+ server;
196
+ 3.;
197
+ Proxy;
198
+ connects;
199
+ local;
200
+ cloud;
201
+ 4.;
202
+ Users;
203
+ get;
204
+ stable;
205
+ subdomain: `https://username.dc.run/mcp`
206
+ ** Pros;
207
+ **
208
+ -;
209
+ No;
210
+ domain;
211
+ needed
212
+ - ;
213
+ Stable;
214
+ URLs(survives, restarts)
215
+ - ;
216
+ OAuth;
217
+ works(stable, redirect, URIs)
218
+ - ;
219
+ Easy;
220
+ user;
221
+ experience
222
+ - ;
223
+ Can;
224
+ manage;
225
+ auth / quotas;
226
+ centrally
227
+ ** Cons;
228
+ **
229
+ -;
230
+ Requires;
231
+ building;
232
+ proxy;
233
+ service
234
+ - ;
235
+ Hosting;
236
+ costs
237
+ - ;
238
+ Privacy;
239
+ concerns(traffic, goes, through, proxy)
240
+ ** Architecture;
241
+ **
242
+ `` `
243
+ User's Computer Desktop Commander Cloud AI Client
244
+ ┌──────────────┐ ┌─────────────────────┐ ┌──────────┐
245
+ │ │ │ │ │ │
246
+ │ DC Server │─────────────▶│ Proxy Service │◀────────│ Claude/ │
247
+ │ (local:3000) │ WebSocket │ username.dc.run │ HTTPS │ ChatGPT │
248
+ │ │ │ │ │ │
249
+ └──────────────┘ └─────────────────────┘ └──────────┘
250
+ ` ``
251
+ ** Implementation;
252
+ Plan: **
253
+ -Build;
254
+ proxy;
255
+ service(Node.js + WebSocket)
256
+ - Deploy;
257
+ to;
258
+ cloud(Vercel / Railway / Fly.io)
259
+ - Users;
260
+ register;
261
+ for (subdomain
262
+ - Local; client; connects)
263
+ to;
264
+ proxy;
265
+ -- - ;
266
+ #;
267
+ #;
268
+ Option;
269
+ 3;
270
+ ngrok;
271
+ Integration
272
+ ** Best;
273
+ for (; ; )
274
+ : ** Users;
275
+ already;
276
+ /**
277
+ * Start Desktop Commander with auto-detected tunnel
278
+ *
279
+ * Usage:
280
+ * node http-server-auto-tunnel.js
281
+ *
282
+ * This script:
283
+ * 1. Checks for TUNNEL_URL env var (named tunnel)
284
+ * 2. If not set, starts random Cloudflare tunnel
285
+ * 3. Extracts tunnel URL from cloudflared output
286
+ * 4. Starts HTTP server with detected URL
287
+ */
288
+ import { spawn } from 'child_process';
289
+ import { fileURLToPath } from 'url';
290
+ import { dirname } from 'path';
291
+ function startRandomTunnel() {
292
+ console.log('🌐 Starting random Cloudflare Tunnel...');
293
+ console.log('⏳ Waiting for tunnel URL...\n');
294
+ const tunnel = spawn('cloudflared', ['tunnel', '--url', 'http://localhost:3000']);
295
+ let tunnelURL = null;
296
+ let server = null;
297
+ tunnel.stdout.on('data', (data) => {
298
+ const output = data.toString();
299
+ process.stdout.write(data);
300
+ // Look for the tunnel URL
301
+ const urlMatch = output.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i);
302
+ if (urlMatch && !tunnelURL) {
303
+ tunnelURL = urlMatch[0];
304
+ console.log('\n✅ Random Tunnel URL detected:', tunnelURL);
305
+ if (process.env.REQUIRE_AUTH === 'true') {
306
+ console.log('\n⚠️ WARNING: OAuth with random tunnels is not recommended!');
307
+ console.log(' The URL will change on restart, breaking OAuth redirects.');
308
+ console.log(' Consider using REQUIRE_AUTH=false or setting up a named tunnel.\n');
309
+ }
310
+ console.log('📝 To use this URL:');
311
+ console.log(` Add to Claude/ChatGPT: ${tunnelURL}/mcp`);
312
+ console.log(` This URL expires when the tunnel stops!\n`);
313
+ server = startServer(tunnelURL);
314
+ }
315
+ });
316
+ tunnel.stderr.on('data', (data) => {
317
+ const output = data.toString();
318
+ process.stderr.write(data);
319
+ // Also check stderr
320
+ const urlMatch = output.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i);
321
+ if (urlMatch && !tunnelURL) {
322
+ tunnelURL = urlMatch[0];
323
+ console.log('\n✅ Random Tunnel URL detected:', tunnelURL);
324
+ server = startServer(tunnelURL);
325
+ }
326
+ });
327
+ tunnel.on('error', (err) => {
328
+ console.error('❌ Tunnel failed to start:', err);
329
+ console.error('\nMake sure cloudflared is installed:');
330
+ console.error(' brew install cloudflare/cloudflare/cloudflared');
331
+ process.exit(1);
332
+ });
333
+ tunnel.on('close', (code) => {
334
+ console.log('\n🛑 Tunnel closed with code:', code);
335
+ if (server)
336
+ server.kill();
337
+ process.exit(code);
338
+ });
339
+ // Cleanup
340
+ process.on('SIGINT', () => {
341
+ console.log('\n🛑 Shutting down...');
342
+ tunnel.kill();
343
+ if (server)
344
+ server.kill();
345
+ process.exit(0);
346
+ });
347
+ }
348
+ function startServer(baseUrl) {
349
+ console.log('\n📡 Starting Desktop Commander HTTP server...');
350
+ const env = {
351
+ ...process.env,
352
+ BASE_URL: baseUrl,
353
+ PORT: '3000'
354
+ };
355
+ const server = spawn('node', ['dist/http-server.js'], {
356
+ cwd: __dirname,
357
+ env,
358
+ stdio: 'inherit'
359
+ });
360
+ server.on('error', (err) => {
361
+ console.error('❌ Server failed to start:', err);
362
+ process.exit(1);
363
+ });
364
+ server.on('close', (code) => {
365
+ console.log('🔴 Server closed with code:', code);
366
+ process.exit(code);
367
+ });
368
+ return server;
369
+ }
370
+ var ngrok, __filename, __dirname;
371
+ const env_1 = { stack: [], error: void 0, hasError: false };
372
+ try {
373
+ ngrok = __addDisposableResource(env_1, void 0, false);
374
+ ** How;
375
+ it;
376
+ works: **
377
+ -Use;
378
+ ngrok;
379
+ instead;
380
+ of;
381
+ Cloudflare;
382
+ tunnels
383
+ - Get;
384
+ stable;
385
+ URLs;
386
+ with (ngrok)
387
+ paid;
388
+ plan
389
+ - Free;
390
+ tier;
391
+ has;
392
+ random;
393
+ URLs;
394
+ like;
395
+ Cloudflare
396
+ ** Pros;
397
+ **
398
+ -;
399
+ Well - known;
400
+ tool
401
+ - ;
402
+ Good;
403
+ documentation
404
+ - ;
405
+ Paid;
406
+ plan = stable;
407
+ URLs
408
+ ** Cons;
409
+ **
410
+ -;
411
+ Free;
412
+ tier = random;
413
+ URLs(same, problem)
414
+ - ;
415
+ Paid;
416
+ plan = $8 - 10 / month;
417
+ per;
418
+ user
419
+ - ;
420
+ Not
421
+ ** Implementation;
422
+ **
423
+ `` `typescript
424
+ // Add to package.json
425
+ "scripts": {
426
+ "start:ngrok": "node dist/http-server-ngrok.js"
427
+ }
428
+ ` `` `` `typescript
429
+ // src/http-server-ngrok.ts
430
+ import ngrok from 'ngrok';
431
+
432
+ async function startWithNgrok() {
433
+ const url = await ngrok.connect({
434
+ addr: 3000,
435
+ authtoken: process.env.NGROK_AUTH_TOKEN
436
+ });
437
+
438
+ console.log(`;
439
+ ngrok;
440
+ tunnel: $;
441
+ {
442
+ url;
443
+ }
444
+ `);
445
+ process.env.BASE_URL = url;
446
+
447
+ // Start server...
448
+ }
449
+ ` ``;
450
+ -- - ;
451
+ #;
452
+ #;
453
+ Option;
454
+ 4;
455
+ Hybrid;
456
+ Approach(Best, Balance)
457
+ ** Best;
458
+ for (; ; )
459
+ : ** Supporting;
460
+ all;
461
+ user;
462
+ types
463
+ ** How;
464
+ it;
465
+ works: **
466
+ -Offer;
467
+ multiple;
468
+ tunnel;
469
+ options
470
+ - Users;
471
+ choose;
472
+ based;
473
+ on;
474
+ their;
475
+ needs `` `bash
476
+ # Option A: Random tunnel (no setup)
477
+ npm run start:tunnel
478
+
479
+ # Option B: Named tunnel (own domain)
480
+ TUNNEL_URL=https://mcp.yourname.com npm run start:named-tunnel
481
+
482
+ # Option C: Cloud service (future)
483
+ npm run start:cloud -- --username yourname
484
+
485
+ # Option D: Local only (no tunnel)
486
+ npm run start:local
487
+ ` ``
488
+ ** Configuration;
489
+ **
490
+ `` `typescript
491
+ // Auto-detect best option
492
+ export async function startServer(options) {
493
+ if (options.cloudUsername) {
494
+ // Use DC cloud proxy
495
+ return startWithCloudProxy(options.cloudUsername);
496
+ } else if (options.namedTunnel) {
497
+ // Use named Cloudflare tunnel
498
+ return startWithNamedTunnel(options.namedTunnel);
499
+ } else if (options.ngrokToken) {
500
+ // Use ngrok
501
+ return startWithNgrok(options.ngrokToken);
502
+ } else {
503
+ // Random Cloudflare tunnel
504
+ return startWithRandomTunnel();
505
+ }
506
+ }
507
+ ` ``;
508
+ -- - ;
509
+ #;
510
+ #;
511
+ Option;
512
+ 5;
513
+ Local;
514
+ Network;
515
+ Only(No, Tunnel)
516
+ ** Best;
517
+ for (; ; )
518
+ : ** Privacy - conscious;
519
+ users, local;
520
+ testing
521
+ ** How;
522
+ it;
523
+ works: **
524
+ -Server;
525
+ runs;
526
+ on;
527
+ local;
528
+ network;
529
+ only
530
+ - Access;
531
+ via `http://192.168.x.x:3000`
532
+ - No;
533
+ internet;
534
+ exposure
535
+ ** Pros;
536
+ **
537
+ -;
538
+ Maximum;
539
+ privacy
540
+ - ;
541
+ No;
542
+ external;
543
+ dependencies
544
+ - ;
545
+ Free
546
+ ** Cons;
547
+ **
548
+ -;
549
+ Only;
550
+ works;
551
+ on;
552
+ same;
553
+ network
554
+ - ;
555
+ Can;
556
+ 't use with cloud AI services
557
+ - ;
558
+ Must;
559
+ use;
560
+ Claude;
561
+ Desktop(not, claude.ai)
562
+ ** Implementation;
563
+ **
564
+ `` `bash
565
+ # Already works!
566
+ npm run start
567
+
568
+ # Claude Desktop config:
569
+ {
570
+ "mcpServers": {
571
+ "desktop-commander": {
572
+ "command": "node",
573
+ "args": ["/path/to/dist/index.js"]
574
+ }
575
+ }
576
+ }
577
+ ` ``;
578
+ -- - ;
579
+ #;
580
+ Recommended;
581
+ Architecture;
582
+ #;
583
+ #;
584
+ #;
585
+ Phase;
586
+ 1;
587
+ Current(Multiple, Options);
588
+ Offer;
589
+ all;
590
+ tunnel;
591
+ methods, let;
592
+ users;
593
+ choose: `` `markdown
594
+ # Installation Methods
595
+
596
+ ## Quick Start (No Setup)
597
+ npm run start:tunnel
598
+ → Random URL, changes on restart
599
+
600
+ ## Production (Own Domain)
601
+ TUNNEL_URL=https://mcp.yourname.com npm run start:named-tunnel
602
+ → Stable URL, requires Cloudflare
603
+
604
+ ## Local Only (Claude Desktop)
605
+ npm run start
606
+ → No tunnel, configure in Claude Desktop
607
+ ` ``;
608
+ #;
609
+ #;
610
+ #;
611
+ Phase;
612
+ 2;
613
+ Cloud;
614
+ Service(Future);
615
+ Build;
616
+ Desktop;
617
+ Commander;
618
+ Cloud: `` `bash
619
+ # User experience
620
+ npm install -g desktop-commander
621
+ dc login
622
+ dc start
623
+
624
+ # Behind the scenes:
625
+ # - Authenticates with DC Cloud
626
+ # - Gets stable subdomain: https://yourname.dc.run
627
+ # - Starts local server
628
+ # - Connects to cloud proxy
629
+ # - OAuth redirect: https://yourname.dc.run/oauth/callback
630
+ ` ``;
631
+ -- - ;
632
+ #;
633
+ Quick;
634
+ Implementation: Support;
635
+ Random;
636
+ Tunnels;
637
+ Let;
638
+ 's make random tunnels work better:
639
+ < function_calls >
640
+ name;
641
+ "desktop-commander-host:write_file" >
642
+ name;
643
+ "content" > ;
644
+ !/usr/bin / env;
645
+ node;
646
+ __filename = fileURLToPath(import.meta.url);
647
+ __dirname = dirname(__filename);
648
+ console.log('🚀 Starting Desktop Commander with Auto Tunnel Detection\n');
649
+ // Check if named tunnel is configured
650
+ if (process.env.TUNNEL_URL) {
651
+ console.log('✅ Using named tunnel from TUNNEL_URL');
652
+ console.log(` URL: ${process.env.TUNNEL_URL}\n`);
653
+ startServer(process.env.TUNNEL_URL);
654
+ }
655
+ else {
656
+ console.log('⚠️ No TUNNEL_URL configured, using random Cloudflare tunnel');
657
+ console.log(' Note: URL will change on restart!\n');
658
+ startRandomTunnel();
659
+ }
660
+ }
661
+ catch (e_1) {
662
+ env_1.error = e_1;
663
+ env_1.hasError = true;
664
+ }
665
+ finally {
666
+ __disposeResources(env_1);
667
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};