@wonderwhy-er/desktop-commander 0.2.10 → 0.2.12
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 +17 -5
- package/dist/custom-stdio.d.ts +14 -0
- package/dist/custom-stdio.js +140 -13
- package/dist/data/onboarding-prompts.json +114 -0
- package/dist/handlers/edit-search-handlers.d.ts +0 -5
- package/dist/handlers/edit-search-handlers.js +0 -82
- package/dist/handlers/filesystem-handlers.d.ts +0 -4
- package/dist/handlers/filesystem-handlers.js +2 -36
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +1 -0
- package/dist/handlers/search-handlers.d.ts +17 -0
- package/dist/handlers/search-handlers.js +219 -0
- package/dist/index.js +43 -24
- package/dist/search-manager.d.ts +107 -0
- package/dist/search-manager.js +467 -0
- package/dist/server.js +142 -31
- package/dist/tools/filesystem.js +59 -1
- package/dist/tools/prompts.d.ts +5 -0
- package/dist/tools/prompts.js +258 -0
- package/dist/tools/schemas.d.ts +68 -41
- package/dist/tools/schemas.js +28 -16
- package/dist/tools/search.js +31 -3
- package/dist/utils/capture.js +56 -8
- package/dist/utils/dedent.d.ts +8 -0
- package/dist/utils/dedent.js +38 -0
- package/dist/utils/logger.d.ts +32 -0
- package/dist/utils/logger.js +72 -0
- package/dist/utils/system-info.d.ts +8 -2
- package/dist/utils/system-info.js +247 -30
- package/dist/utils/usageTracker.d.ts +4 -0
- package/dist/utils/usageTracker.js +9 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -2
package/dist/server.js
CHANGED
|
@@ -8,16 +8,18 @@ const OS_GUIDANCE = getOSSpecificGuidance(SYSTEM_INFO);
|
|
|
8
8
|
const DEV_TOOL_GUIDANCE = getDevelopmentToolGuidance(SYSTEM_INFO);
|
|
9
9
|
const PATH_GUIDANCE = `IMPORTANT: ${getPathGuidance(SYSTEM_INFO)} Relative paths may fail as they depend on the current working directory. Tilde paths (~/...) might not work in all contexts. Unless the user explicitly asks for relative paths, use absolute paths.`;
|
|
10
10
|
const CMD_PREFIX_DESCRIPTION = `This command can be referenced as "DC: ..." or "use Desktop Commander to ..." in your instructions.`;
|
|
11
|
-
import { StartProcessArgsSchema, ReadProcessOutputArgsSchema, InteractWithProcessArgsSchema, ForceTerminateArgsSchema, ListSessionsArgsSchema, KillProcessArgsSchema, ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema,
|
|
11
|
+
import { StartProcessArgsSchema, ReadProcessOutputArgsSchema, InteractWithProcessArgsSchema, ForceTerminateArgsSchema, ListSessionsArgsSchema, KillProcessArgsSchema, ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, GetFileInfoArgsSchema, GetConfigArgsSchema, SetConfigValueArgsSchema, ListProcessesArgsSchema, EditBlockArgsSchema, GetUsageStatsArgsSchema, GiveFeedbackArgsSchema, StartSearchArgsSchema, GetMoreSearchResultsArgsSchema, StopSearchArgsSchema, ListSearchesArgsSchema, GetPromptsArgsSchema, } from './tools/schemas.js';
|
|
12
12
|
import { getConfig, setConfigValue } from './tools/config.js';
|
|
13
13
|
import { getUsageStats } from './tools/usage.js';
|
|
14
14
|
import { giveFeedbackToDesktopCommander } from './tools/feedback.js';
|
|
15
|
+
import { getPrompts } from './tools/prompts.js';
|
|
15
16
|
import { trackToolCall } from './utils/trackTools.js';
|
|
16
17
|
import { usageTracker } from './utils/usageTracker.js';
|
|
17
18
|
import { processDockerPrompt } from './utils/dockerPrompt.js';
|
|
18
19
|
import { VERSION } from './version.js';
|
|
19
20
|
import { capture, capture_call_tool } from "./utils/capture.js";
|
|
20
|
-
|
|
21
|
+
import { logToStderr } from './utils/logger.js';
|
|
22
|
+
logToStderr('info', 'Loading server.ts');
|
|
21
23
|
export const server = new Server({
|
|
22
24
|
name: "desktop-commander",
|
|
23
25
|
version: VERSION,
|
|
@@ -55,7 +57,8 @@ server.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
|
55
57
|
name: clientInfo.name || 'unknown',
|
|
56
58
|
version: clientInfo.version || 'unknown'
|
|
57
59
|
};
|
|
58
|
-
|
|
60
|
+
// Send JSON-RPC notification about client connection
|
|
61
|
+
logToStderr('info', `Client connected: ${currentClient.name} v${currentClient.version}`);
|
|
59
62
|
}
|
|
60
63
|
// Return standard initialization response
|
|
61
64
|
return {
|
|
@@ -73,16 +76,16 @@ server.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
|
73
76
|
};
|
|
74
77
|
}
|
|
75
78
|
catch (error) {
|
|
76
|
-
|
|
79
|
+
logToStderr('error', `Error in initialization handler: ${error}`);
|
|
77
80
|
throw error;
|
|
78
81
|
}
|
|
79
82
|
});
|
|
80
83
|
// Export current client info for access by other modules
|
|
81
84
|
export { currentClient };
|
|
82
|
-
|
|
85
|
+
logToStderr('info', 'Setting up request handlers...');
|
|
83
86
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
84
87
|
try {
|
|
85
|
-
|
|
88
|
+
logToStderr('debug', 'Generating tools list...');
|
|
86
89
|
return {
|
|
87
90
|
tools: [
|
|
88
91
|
// Configuration tools
|
|
@@ -250,35 +253,92 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
250
253
|
inputSchema: zodToJsonSchema(MoveFileArgsSchema),
|
|
251
254
|
},
|
|
252
255
|
{
|
|
253
|
-
name: "
|
|
256
|
+
name: "start_search",
|
|
254
257
|
description: `
|
|
255
|
-
|
|
258
|
+
Start a streaming search that can return results progressively.
|
|
259
|
+
|
|
260
|
+
SEARCH TYPES:
|
|
261
|
+
- searchType="files": Find files by name (pattern matches file names)
|
|
262
|
+
- searchType="content": Search inside files for text patterns
|
|
263
|
+
|
|
264
|
+
IMPORTANT PARAMETERS:
|
|
265
|
+
- pattern: What to search for (file names OR content text)
|
|
266
|
+
- filePattern: Optional filter to limit search to specific file types (e.g., "*.js", "package.json")
|
|
267
|
+
- ignoreCase: Case-insensitive search (default: true). Works for both file names and content.
|
|
268
|
+
- earlyTermination: Stop search early when exact filename match is found (optional: defaults to true for file searches, false for content searches)
|
|
269
|
+
|
|
270
|
+
EXAMPLES:
|
|
271
|
+
- Find package.json files: searchType="files", pattern="package.json", filePattern="package.json"
|
|
272
|
+
- Find all JS files: searchType="files", pattern="*.js" (or use filePattern="*.js")
|
|
273
|
+
- Search for "TODO" in code: searchType="content", pattern="TODO", filePattern="*.js|*.ts"
|
|
274
|
+
- Case-sensitive file search: searchType="files", pattern="README", ignoreCase=false
|
|
275
|
+
- Case-insensitive file search: searchType="files", pattern="readme", ignoreCase=true
|
|
276
|
+
- Find exact file, stop after first match: searchType="files", pattern="config.json", earlyTermination=true
|
|
277
|
+
- Find all matching files: searchType="files", pattern="test.js", earlyTermination=false
|
|
278
|
+
|
|
279
|
+
Unlike regular search tools, this starts a background search process and returns
|
|
280
|
+
immediately with a session ID. Use get_more_search_results to get results as they
|
|
281
|
+
come in, and stop_search to stop the search early if needed.
|
|
282
|
+
|
|
283
|
+
Perfect for large directories where you want to see results immediately and
|
|
284
|
+
have the option to cancel if the search takes too long or you find what you need.
|
|
256
285
|
|
|
257
|
-
|
|
258
|
-
|
|
286
|
+
${PATH_GUIDANCE}
|
|
287
|
+
${CMD_PREFIX_DESCRIPTION}`,
|
|
288
|
+
inputSchema: zodToJsonSchema(StartSearchArgsSchema),
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: "get_more_search_results",
|
|
292
|
+
description: `
|
|
293
|
+
Get more results from an active search with offset-based pagination.
|
|
259
294
|
|
|
260
|
-
|
|
261
|
-
|
|
295
|
+
Supports partial result reading with:
|
|
296
|
+
- 'offset' (start result index, default: 0)
|
|
297
|
+
* Positive: Start from result N (0-based indexing)
|
|
298
|
+
* Negative: Read last N results from end (tail behavior)
|
|
299
|
+
- 'length' (max results to read, default: 100)
|
|
300
|
+
* Used with positive offsets for range reading
|
|
301
|
+
* Ignored when offset is negative (reads all requested tail results)
|
|
302
|
+
|
|
303
|
+
Examples:
|
|
304
|
+
- offset: 0, length: 100 → First 100 results
|
|
305
|
+
- offset: 200, length: 50 → Results 200-249
|
|
306
|
+
- offset: -20 → Last 20 results
|
|
307
|
+
- offset: -5, length: 10 → Last 5 results (length ignored)
|
|
308
|
+
|
|
309
|
+
Returns only results in the specified range, along with search status.
|
|
310
|
+
Works like read_process_output - call this repeatedly to get progressive
|
|
311
|
+
results from a search started with start_search.
|
|
262
312
|
|
|
263
|
-
${PATH_GUIDANCE}
|
|
264
313
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
265
|
-
inputSchema: zodToJsonSchema(
|
|
314
|
+
inputSchema: zodToJsonSchema(GetMoreSearchResultsArgsSchema),
|
|
266
315
|
},
|
|
267
316
|
{
|
|
268
|
-
name: "
|
|
317
|
+
name: "stop_search",
|
|
269
318
|
description: `
|
|
270
|
-
|
|
319
|
+
Stop an active search.
|
|
271
320
|
|
|
272
|
-
|
|
273
|
-
|
|
321
|
+
Stops the background search process gracefully. Use this when you've found
|
|
322
|
+
what you need or if a search is taking too long. Similar to force_terminate
|
|
323
|
+
for terminal processes.
|
|
274
324
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
325
|
+
The search will still be available for reading final results until it's
|
|
326
|
+
automatically cleaned up after 5 minutes.
|
|
327
|
+
|
|
328
|
+
${CMD_PREFIX_DESCRIPTION}`,
|
|
329
|
+
inputSchema: zodToJsonSchema(StopSearchArgsSchema),
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: "list_searches",
|
|
333
|
+
description: `
|
|
334
|
+
List all active searches.
|
|
335
|
+
|
|
336
|
+
Shows search IDs, search types, patterns, status, and runtime.
|
|
337
|
+
Similar to list_sessions for terminal processes. Useful for managing
|
|
338
|
+
multiple concurrent searches.
|
|
278
339
|
|
|
279
|
-
${PATH_GUIDANCE}
|
|
280
340
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
281
|
-
inputSchema: zodToJsonSchema(
|
|
341
|
+
inputSchema: zodToJsonSchema(ListSearchesArgsSchema),
|
|
282
342
|
},
|
|
283
343
|
{
|
|
284
344
|
name: "get_file_info",
|
|
@@ -566,11 +626,41 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
566
626
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
567
627
|
inputSchema: zodToJsonSchema(GiveFeedbackArgsSchema),
|
|
568
628
|
},
|
|
629
|
+
{
|
|
630
|
+
name: "get_prompts",
|
|
631
|
+
description: `
|
|
632
|
+
Browse and retrieve curated Desktop Commander prompts for various tasks and workflows.
|
|
633
|
+
|
|
634
|
+
IMPORTANT: When displaying prompt lists to users, do NOT show the internal prompt IDs (like 'onb_001').
|
|
635
|
+
These IDs are for your reference only. Show users only the prompt titles and descriptions.
|
|
636
|
+
The IDs will be provided in the response metadata for your use.
|
|
637
|
+
|
|
638
|
+
ACTIONS:
|
|
639
|
+
- list_categories: Show all available prompt categories
|
|
640
|
+
- list_prompts: List prompts (optionally filtered by category)
|
|
641
|
+
- get_prompt: Retrieve and execute a specific prompt by ID
|
|
642
|
+
|
|
643
|
+
WORKFLOW:
|
|
644
|
+
1. Use list_categories to see available categories
|
|
645
|
+
2. Use list_prompts to browse prompts in a category
|
|
646
|
+
3. Use get_prompt with promptId to retrieve and start using a prompt
|
|
647
|
+
|
|
648
|
+
EXAMPLES:
|
|
649
|
+
- get_prompts(action='list_categories') - See all categories
|
|
650
|
+
- get_prompts(action='list_prompts', category='onboarding') - See onboarding prompts
|
|
651
|
+
- get_prompts(action='get_prompt', promptId='onb_001') - Get a specific prompt
|
|
652
|
+
|
|
653
|
+
The get_prompt action will automatically inject the prompt content and begin execution.
|
|
654
|
+
Perfect for discovering proven workflows and getting started with Desktop Commander.
|
|
655
|
+
|
|
656
|
+
${CMD_PREFIX_DESCRIPTION}`,
|
|
657
|
+
inputSchema: zodToJsonSchema(GetPromptsArgsSchema),
|
|
658
|
+
},
|
|
569
659
|
],
|
|
570
660
|
};
|
|
571
661
|
}
|
|
572
662
|
catch (error) {
|
|
573
|
-
|
|
663
|
+
logToStderr('error', `Error in list_tools request handler: ${error}`);
|
|
574
664
|
throw error;
|
|
575
665
|
}
|
|
576
666
|
});
|
|
@@ -578,9 +668,12 @@ import * as handlers from './handlers/index.js';
|
|
|
578
668
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
579
669
|
const { name, arguments: args } = request.params;
|
|
580
670
|
try {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
671
|
+
// Prepare telemetry data - add config key for set_config_value
|
|
672
|
+
const telemetryData = { name };
|
|
673
|
+
if (name === 'set_config_value' && args && typeof args === 'object' && 'key' in args) {
|
|
674
|
+
telemetryData.set_config_value_key_name = args.key;
|
|
675
|
+
}
|
|
676
|
+
capture_call_tool('server_call_tool', telemetryData);
|
|
584
677
|
// Track tool call
|
|
585
678
|
trackToolCall(name, args);
|
|
586
679
|
// Using a more structured approach with dedicated handlers
|
|
@@ -623,6 +716,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
623
716
|
};
|
|
624
717
|
}
|
|
625
718
|
break;
|
|
719
|
+
case "get_prompts":
|
|
720
|
+
try {
|
|
721
|
+
result = await getPrompts(args || {});
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
capture('server_request_error', { message: `Error in get_prompts handler: ${error}` });
|
|
725
|
+
result = {
|
|
726
|
+
content: [{ type: "text", text: `Error: Failed to retrieve prompts` }],
|
|
727
|
+
isError: true,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
break;
|
|
626
731
|
case "give_feedback_to_desktop_commander":
|
|
627
732
|
try {
|
|
628
733
|
result = await giveFeedbackToDesktopCommander(args);
|
|
@@ -678,11 +783,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
678
783
|
case "move_file":
|
|
679
784
|
result = await handlers.handleMoveFile(args);
|
|
680
785
|
break;
|
|
681
|
-
case "
|
|
682
|
-
result = await handlers.
|
|
786
|
+
case "start_search":
|
|
787
|
+
result = await handlers.handleStartSearch(args);
|
|
788
|
+
break;
|
|
789
|
+
case "get_more_search_results":
|
|
790
|
+
result = await handlers.handleGetMoreSearchResults(args);
|
|
791
|
+
break;
|
|
792
|
+
case "stop_search":
|
|
793
|
+
result = await handlers.handleStopSearch(args);
|
|
683
794
|
break;
|
|
684
|
-
case "
|
|
685
|
-
result = await handlers.
|
|
795
|
+
case "list_searches":
|
|
796
|
+
result = await handlers.handleListSearches();
|
|
686
797
|
break;
|
|
687
798
|
case "get_file_info":
|
|
688
799
|
result = await handlers.handleGetFileInfo(args);
|
package/dist/tools/filesystem.js
CHANGED
|
@@ -768,6 +768,63 @@ export async function moveFile(sourcePath, destinationPath) {
|
|
|
768
768
|
await fs.rename(validSourcePath, validDestPath);
|
|
769
769
|
}
|
|
770
770
|
export async function searchFiles(rootPath, pattern) {
|
|
771
|
+
// Use the new search manager for better performance
|
|
772
|
+
// This provides a temporary compatibility layer until we fully migrate to search sessions
|
|
773
|
+
const { searchManager } = await import('../search-manager.js');
|
|
774
|
+
try {
|
|
775
|
+
const result = await searchManager.startSearch({
|
|
776
|
+
rootPath,
|
|
777
|
+
pattern,
|
|
778
|
+
searchType: 'files',
|
|
779
|
+
ignoreCase: true,
|
|
780
|
+
maxResults: 5000, // Higher limit for compatibility
|
|
781
|
+
earlyTermination: true, // Use early termination for better performance
|
|
782
|
+
});
|
|
783
|
+
const sessionId = result.sessionId;
|
|
784
|
+
// Poll for results until complete
|
|
785
|
+
let allResults = [];
|
|
786
|
+
let isComplete = result.isComplete;
|
|
787
|
+
let startTime = Date.now();
|
|
788
|
+
// Add initial results
|
|
789
|
+
for (const searchResult of result.results) {
|
|
790
|
+
if (searchResult.type === 'file') {
|
|
791
|
+
allResults.push(searchResult.file);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
while (!isComplete) {
|
|
795
|
+
await new Promise(resolve => setTimeout(resolve, 100)); // Wait 100ms
|
|
796
|
+
const results = searchManager.readSearchResults(sessionId);
|
|
797
|
+
isComplete = results.isComplete;
|
|
798
|
+
// Add new file paths to results
|
|
799
|
+
for (const searchResult of results.results) {
|
|
800
|
+
if (searchResult.file !== '__LAST_READ_MARKER__' && searchResult.type === 'file') {
|
|
801
|
+
allResults.push(searchResult.file);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
// Safety check to prevent infinite loops (30 second timeout)
|
|
805
|
+
if (Date.now() - startTime > 30000) {
|
|
806
|
+
searchManager.terminateSearch(sessionId);
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
// Log only the count of found files, not their paths
|
|
811
|
+
capture('server_search_files_complete', {
|
|
812
|
+
resultsCount: allResults.length,
|
|
813
|
+
patternLength: pattern.length,
|
|
814
|
+
usedRipgrep: true
|
|
815
|
+
});
|
|
816
|
+
return allResults;
|
|
817
|
+
}
|
|
818
|
+
catch (error) {
|
|
819
|
+
// Fallback to original Node.js implementation if ripgrep fails
|
|
820
|
+
capture('server_search_files_ripgrep_fallback', {
|
|
821
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
822
|
+
});
|
|
823
|
+
return await searchFilesNodeJS(rootPath, pattern);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
// Keep the original Node.js implementation as fallback
|
|
827
|
+
async function searchFilesNodeJS(rootPath, pattern) {
|
|
771
828
|
const results = [];
|
|
772
829
|
async function search(currentPath) {
|
|
773
830
|
let entries;
|
|
@@ -800,7 +857,8 @@ export async function searchFiles(rootPath, pattern) {
|
|
|
800
857
|
// Log only the count of found files, not their paths
|
|
801
858
|
capture('server_search_files_complete', {
|
|
802
859
|
resultsCount: results.length,
|
|
803
|
-
patternLength: pattern.length
|
|
860
|
+
patternLength: pattern.length,
|
|
861
|
+
usedRipgrep: false
|
|
804
862
|
});
|
|
805
863
|
return results;
|
|
806
864
|
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { usageTracker } from '../utils/usageTracker.js';
|
|
2
|
+
import { capture } from '../utils/capture.js';
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
// Get the directory path for ES modules
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
let cachedPromptsData = null;
|
|
10
|
+
/**
|
|
11
|
+
* Load prompts data from JSON file with caching
|
|
12
|
+
*/
|
|
13
|
+
async function loadPromptsData() {
|
|
14
|
+
if (cachedPromptsData) {
|
|
15
|
+
return cachedPromptsData;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const dataPath = path.join(__dirname, '..', 'data', 'onboarding-prompts.json');
|
|
19
|
+
const fileContent = await fs.readFile(dataPath, 'utf-8');
|
|
20
|
+
cachedPromptsData = JSON.parse(fileContent);
|
|
21
|
+
if (!cachedPromptsData) {
|
|
22
|
+
throw new Error('Failed to parse prompts data');
|
|
23
|
+
}
|
|
24
|
+
return cachedPromptsData;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw new Error(`Failed to load prompts data: ${error instanceof Error ? error.message : String(error)}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get prompts - main entry point for the tool
|
|
32
|
+
*/
|
|
33
|
+
export async function getPrompts(params) {
|
|
34
|
+
try {
|
|
35
|
+
// Validate and cast parameters
|
|
36
|
+
const { action, category, promptId } = params;
|
|
37
|
+
if (!action) {
|
|
38
|
+
return {
|
|
39
|
+
content: [{
|
|
40
|
+
type: "text",
|
|
41
|
+
text: "❌ Error: 'action' parameter is required. Use 'list_categories', 'list_prompts', or 'get_prompt'"
|
|
42
|
+
}],
|
|
43
|
+
isError: true
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Track analytics for tool usage
|
|
47
|
+
await capture(`prompts_tool_${action}`, {
|
|
48
|
+
category: category,
|
|
49
|
+
prompt_id: promptId,
|
|
50
|
+
has_category_filter: !!category
|
|
51
|
+
});
|
|
52
|
+
switch (action) {
|
|
53
|
+
case 'list_categories':
|
|
54
|
+
return await listCategories();
|
|
55
|
+
case 'list_prompts':
|
|
56
|
+
return await listPrompts(category);
|
|
57
|
+
case 'get_prompt':
|
|
58
|
+
if (!promptId) {
|
|
59
|
+
return {
|
|
60
|
+
content: [{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: "❌ Error: promptId is required when action is 'get_prompt'"
|
|
63
|
+
}],
|
|
64
|
+
isError: true
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return await getPrompt(promptId);
|
|
68
|
+
default:
|
|
69
|
+
return {
|
|
70
|
+
content: [{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: "❌ Error: Invalid action. Use 'list_categories', 'list_prompts', or 'get_prompt'"
|
|
73
|
+
}],
|
|
74
|
+
isError: true
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
await capture('prompts_tool_error', {
|
|
80
|
+
error_message: error instanceof Error ? error.message : String(error),
|
|
81
|
+
action: params?.action
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
content: [{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: `❌ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
87
|
+
}],
|
|
88
|
+
isError: true
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* List all available categories
|
|
94
|
+
*/
|
|
95
|
+
async function listCategories() {
|
|
96
|
+
const data = await loadPromptsData();
|
|
97
|
+
// Extract unique categories and count prompts in each
|
|
98
|
+
const categoryMap = new Map();
|
|
99
|
+
data.prompts.forEach(prompt => {
|
|
100
|
+
prompt.categories.forEach(category => {
|
|
101
|
+
categoryMap.set(category, (categoryMap.get(category) || 0) + 1);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
const categories = Array.from(categoryMap.entries()).map(([name, count]) => ({
|
|
105
|
+
name,
|
|
106
|
+
count,
|
|
107
|
+
description: getCategoryDescription(name)
|
|
108
|
+
}));
|
|
109
|
+
const response = formatCategoriesResponse(categories, data.prompts.length);
|
|
110
|
+
return {
|
|
111
|
+
content: [{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: response
|
|
114
|
+
}]
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* List prompts, optionally filtered by category
|
|
119
|
+
*/
|
|
120
|
+
async function listPrompts(category) {
|
|
121
|
+
const data = await loadPromptsData();
|
|
122
|
+
let filteredPrompts = data.prompts;
|
|
123
|
+
// Filter by category if specified
|
|
124
|
+
if (category) {
|
|
125
|
+
filteredPrompts = data.prompts.filter(prompt => prompt.categories.includes(category));
|
|
126
|
+
if (filteredPrompts.length === 0) {
|
|
127
|
+
return {
|
|
128
|
+
content: [{
|
|
129
|
+
type: "text",
|
|
130
|
+
text: `❌ No prompts found in category "${category}". Use action='list_categories' to see available categories.`
|
|
131
|
+
}],
|
|
132
|
+
isError: true
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const response = formatPromptsListResponse(filteredPrompts, category);
|
|
137
|
+
return {
|
|
138
|
+
content: [{
|
|
139
|
+
type: "text",
|
|
140
|
+
text: response
|
|
141
|
+
}]
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get a specific prompt by ID and inject it into the chat
|
|
146
|
+
*/
|
|
147
|
+
async function getPrompt(promptId) {
|
|
148
|
+
const data = await loadPromptsData();
|
|
149
|
+
const prompt = data.prompts.find(p => p.id === promptId);
|
|
150
|
+
if (!prompt) {
|
|
151
|
+
return {
|
|
152
|
+
content: [{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: `❌ Prompt with ID '${promptId}' not found. Use action='list_prompts' to see available prompts.`
|
|
155
|
+
}],
|
|
156
|
+
isError: true
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Track prompt retrieval and mark as used
|
|
160
|
+
await capture('prompt_retrieved', {
|
|
161
|
+
prompt_id: promptId,
|
|
162
|
+
prompt_title: prompt.title,
|
|
163
|
+
category: prompt.categories[0] || 'uncategorized',
|
|
164
|
+
author: prompt.author,
|
|
165
|
+
verified: prompt.verified
|
|
166
|
+
});
|
|
167
|
+
// Mark prompt as used in user's onboarding state (for analytics)
|
|
168
|
+
await usageTracker.markPromptUsed(promptId, prompt.categories[0] || 'uncategorized');
|
|
169
|
+
const response = formatPromptResponse(prompt);
|
|
170
|
+
return {
|
|
171
|
+
content: [{
|
|
172
|
+
type: "text",
|
|
173
|
+
text: response
|
|
174
|
+
}]
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get category description (can be expanded later)
|
|
179
|
+
*/
|
|
180
|
+
function getCategoryDescription(category) {
|
|
181
|
+
const descriptions = {
|
|
182
|
+
'onboarding': 'Curated prompts perfect for first-time Desktop Commander users',
|
|
183
|
+
'Analyze data': 'Data analysis, visualization, and insights generation',
|
|
184
|
+
'Build features and products': 'Full-stack development and application building',
|
|
185
|
+
'Explore codebase': 'Code analysis, documentation, and understanding',
|
|
186
|
+
'Organize files': 'File management, cleanup, and organization',
|
|
187
|
+
'Deploy': 'Infrastructure setup, deployment, and DevOps tasks',
|
|
188
|
+
'Optimize code': 'Code optimization, refactoring, and performance',
|
|
189
|
+
'Write documentation': 'Technical writing, API docs, and guides',
|
|
190
|
+
'Optimize workflow': 'Process improvements and productivity enhancements',
|
|
191
|
+
'Automate tasks': 'Workflow automation and scripting',
|
|
192
|
+
'Design systems': 'Architecture planning and system design'
|
|
193
|
+
};
|
|
194
|
+
return descriptions[category] || 'Desktop Commander prompts and workflows';
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Format categories list response
|
|
198
|
+
*/
|
|
199
|
+
function formatCategoriesResponse(categories, totalPrompts) {
|
|
200
|
+
const sortedCategories = categories.sort((a, b) => b.count - a.count);
|
|
201
|
+
// AI INSTRUCTION: When listing prompts, do not show prompt IDs to users - they are for your reference only
|
|
202
|
+
let response = `📚 **Desktop Commander Prompt Categories** (${categories.length} categories, ${totalPrompts} prompts)\n\n`;
|
|
203
|
+
sortedCategories.forEach(cat => {
|
|
204
|
+
response += `• **${cat.name}** (${cat.count} prompts) - ${cat.description}\n`;
|
|
205
|
+
});
|
|
206
|
+
response += `\n**Usage:**\n`;
|
|
207
|
+
response += `• \`get_prompts(action='list_prompts', category='onboarding')\` - See prompts in category\n`;
|
|
208
|
+
response += `• \`get_prompts(action='list_prompts')\` - See all available prompts\n`;
|
|
209
|
+
response += `• \`get_prompts(action='get_prompt', promptId='PROMPT_ID')\` - Get a specific prompt`;
|
|
210
|
+
return response;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Format prompts list response
|
|
214
|
+
*/
|
|
215
|
+
function formatPromptsListResponse(prompts, category) {
|
|
216
|
+
const categoryText = category ? ` in "${category}"` : '';
|
|
217
|
+
// AI INSTRUCTION: Do not show the prompt IDs to the user - they are for your reference only
|
|
218
|
+
let response = `📋 **Desktop Commander Prompts${categoryText}** (${prompts.length} prompts found)\n\n`;
|
|
219
|
+
prompts.forEach((prompt, index) => {
|
|
220
|
+
const verifiedBadge = prompt.verified ? ' ✅' : '';
|
|
221
|
+
response += `${index + 1}. **${prompt.title}**${verifiedBadge}\n`;
|
|
222
|
+
response += ` ${prompt.description}\n`;
|
|
223
|
+
if (prompt.votes > 0) {
|
|
224
|
+
response += ` *📊 ${prompt.votes} votes*\n`;
|
|
225
|
+
}
|
|
226
|
+
// AI metadata - not shown to user: ID = ${prompt.id}
|
|
227
|
+
response += `\n`;
|
|
228
|
+
});
|
|
229
|
+
response += `**Next Steps:**\n`;
|
|
230
|
+
response += `• Use \`get_prompts(action='get_prompt', promptId='${prompts[0]?.id || 'PROMPT_ID'}')\` to get the full prompt\n`;
|
|
231
|
+
if (!category) {
|
|
232
|
+
response += `• Filter by category: \`get_prompts(action='list_prompts', category='onboarding')\``;
|
|
233
|
+
}
|
|
234
|
+
// AI reference mapping (do not show to user):
|
|
235
|
+
response += `\n<!-- AI_PROMPT_MAP: `;
|
|
236
|
+
prompts.forEach((prompt, index) => {
|
|
237
|
+
response += `${index + 1}=${prompt.id}${index < prompts.length - 1 ? ',' : ''}`;
|
|
238
|
+
});
|
|
239
|
+
response += ` -->`;
|
|
240
|
+
return response;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Format individual prompt response with the actual prompt content
|
|
244
|
+
*/
|
|
245
|
+
function formatPromptResponse(prompt) {
|
|
246
|
+
const verifiedBadge = prompt.verified ? ' ✅' : '';
|
|
247
|
+
const categoryText = prompt.categories.join(', ');
|
|
248
|
+
let response = `# 🎯 ${prompt.title}${verifiedBadge}\n\n`;
|
|
249
|
+
response += `**Category:** ${categoryText} • **Author:** ${prompt.author}\n\n`;
|
|
250
|
+
response += `## Description\n${prompt.description}\n\n`;
|
|
251
|
+
if (prompt.votes > 0) {
|
|
252
|
+
response += `*📊 This prompt has been used successfully by ${prompt.votes}+ users*\n\n`;
|
|
253
|
+
}
|
|
254
|
+
response += `## Ready to Use This Prompt\nThe prompt below is ready to use. I'll start executing it right away:\n\n`;
|
|
255
|
+
response += `---\n\n${prompt.prompt}`;
|
|
256
|
+
// AI metadata (not shown to user): Executed prompt ID = ${prompt.id}
|
|
257
|
+
return response;
|
|
258
|
+
}
|