@wonderwhy-er/desktop-commander 0.2.38 → 0.2.40
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 +53 -2
- package/dist/handlers/filesystem-handlers.d.ts +5 -0
- package/dist/handlers/filesystem-handlers.js +14 -2
- package/dist/remote-device/desktop-commander-integration.js +1 -1
- package/dist/search-manager.js +31 -38
- package/dist/server.js +9 -4
- package/dist/terminal-manager.js +4 -2
- package/dist/tools/edit.js +34 -1
- package/dist/tools/filesystem.js +91 -3
- package/dist/tools/improved-process-tools.js +2 -1
- package/dist/ui/config-editor/config-editor-runtime.js +65 -14096
- package/dist/ui/config-editor/styles.css +2 -1
- package/dist/ui/file-preview/preview-runtime.js +435 -26533
- package/dist/ui/file-preview/shared/preview-file-types.d.ts +1 -1
- package/dist/ui/file-preview/src/app.d.ts +1 -5
- package/dist/ui/file-preview/src/app.js +384 -534
- package/dist/ui/file-preview/src/components/markdown-renderer.js +47 -9
- package/dist/ui/file-preview/src/directory-controller.d.ts +8 -0
- package/dist/ui/file-preview/src/directory-controller.js +233 -0
- package/dist/ui/file-preview/src/document-layout.d.ts +20 -0
- package/dist/ui/file-preview/src/document-layout.js +109 -0
- package/dist/ui/file-preview/src/document-outline.d.ts +17 -0
- package/dist/ui/file-preview/src/document-outline.js +97 -0
- package/dist/ui/file-preview/src/document-workspace.d.ts +19 -0
- package/dist/ui/file-preview/src/document-workspace.js +33 -0
- package/dist/ui/file-preview/src/file-type-handlers.d.ts +10 -0
- package/dist/ui/file-preview/src/file-type-handlers.js +98 -0
- package/dist/ui/file-preview/src/host/external-actions.d.ts +19 -0
- package/dist/ui/file-preview/src/host/external-actions.js +94 -0
- package/dist/ui/file-preview/src/host/selection-context.d.ts +9 -0
- package/dist/ui/file-preview/src/host/selection-context.js +106 -0
- package/dist/ui/file-preview/src/markdown/conflict-dialog.d.ts +40 -0
- package/dist/ui/file-preview/src/markdown/conflict-dialog.js +163 -0
- package/dist/ui/file-preview/src/markdown/controller.d.ts +44 -0
- package/dist/ui/file-preview/src/markdown/controller.js +1040 -0
- package/dist/ui/file-preview/src/markdown/editor.d.ts +131 -0
- package/dist/ui/file-preview/src/markdown/editor.js +1479 -0
- package/dist/ui/file-preview/src/markdown/linking.d.ts +16 -0
- package/dist/ui/file-preview/src/markdown/linking.js +228 -0
- package/dist/ui/file-preview/src/markdown/outline.d.ts +2 -0
- package/dist/ui/file-preview/src/markdown/outline.js +16 -0
- package/dist/ui/file-preview/src/markdown/parser.d.ts +30 -0
- package/dist/ui/file-preview/src/markdown/parser.js +38 -0
- package/dist/ui/file-preview/src/markdown/preview.d.ts +1 -0
- package/dist/ui/file-preview/src/markdown/preview.js +20 -0
- package/dist/ui/file-preview/src/markdown/slugify.d.ts +3 -0
- package/dist/ui/file-preview/src/markdown/slugify.js +31 -0
- package/dist/ui/file-preview/src/markdown/utils.d.ts +1 -0
- package/dist/ui/file-preview/src/markdown/utils.js +15 -0
- package/dist/ui/file-preview/src/model.d.ts +35 -0
- package/dist/ui/file-preview/src/panel-actions.d.ts +17 -0
- package/dist/ui/file-preview/src/panel-actions.js +182 -0
- package/dist/ui/file-preview/src/path-utils.d.ts +6 -0
- package/dist/ui/file-preview/src/path-utils.js +64 -0
- package/dist/ui/file-preview/src/payload-utils.d.ts +11 -0
- package/dist/ui/file-preview/src/payload-utils.js +94 -0
- package/dist/ui/file-preview/styles.css +1066 -233
- package/dist/ui/shared/widget-state.d.ts +6 -1
- package/dist/ui/shared/widget-state.js +102 -4
- package/dist/utils/capture.js +1 -1
- package/dist/utils/files/base.d.ts +2 -0
- package/dist/utils/open-browser.js +1 -1
- package/dist/utils/toolHistory.d.ts +13 -0
- package/dist/utils/toolHistory.js +65 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +12 -1
- package/dist/data/spec-kit-prompts.json +0 -123
- package/dist/handlers/macos-control-handlers.d.ts +0 -16
- package/dist/handlers/macos-control-handlers.js +0 -81
- package/dist/handlers/node-handlers.d.ts +0 -6
- package/dist/handlers/node-handlers.js +0 -73
- package/dist/handlers/test-crash-handler.d.ts +0 -11
- package/dist/handlers/test-crash-handler.js +0 -26
- package/dist/http-index.d.ts +0 -45
- package/dist/http-index.js +0 -51
- package/dist/http-server-auto-tunnel.js +0 -667
- package/dist/http-server-named-tunnel.d.ts +0 -2
- package/dist/http-server-named-tunnel.js +0 -167
- package/dist/http-server-tunnel.d.ts +0 -2
- package/dist/http-server-tunnel.js +0 -111
- package/dist/http-server.d.ts +0 -2
- package/dist/http-server.js +0 -270
- package/dist/index-oauth.d.ts +0 -2
- package/dist/index-oauth.js +0 -201
- package/dist/lib.d.ts +0 -10
- package/dist/lib.js +0 -10
- package/dist/oauth/auth-middleware.d.ts +0 -20
- package/dist/oauth/auth-middleware.js +0 -62
- package/dist/oauth/index.d.ts +0 -3
- package/dist/oauth/index.js +0 -3
- package/dist/oauth/oauth-manager.d.ts +0 -80
- package/dist/oauth/oauth-manager.js +0 -179
- package/dist/oauth/oauth-routes.d.ts +0 -3
- package/dist/oauth/oauth-routes.js +0 -377
- package/dist/oauth/provider.d.ts +0 -22
- package/dist/oauth/provider.js +0 -124
- package/dist/oauth/server.d.ts +0 -18
- package/dist/oauth/server.js +0 -160
- package/dist/oauth/types.d.ts +0 -54
- package/dist/oauth/types.js +0 -2
- package/dist/remote-device/templates/auth-success.d.ts +0 -1
- package/dist/remote-device/templates/auth-success.js +0 -30
- package/dist/setup.log +0 -275
- package/dist/test-docx.d.ts +0 -1
- package/dist/test-setup.js +0 -14
- package/dist/tools/docx/builders/html-builder.d.ts +0 -17
- package/dist/tools/docx/builders/html-builder.js +0 -92
- package/dist/tools/docx/builders/image.d.ts +0 -14
- package/dist/tools/docx/builders/image.js +0 -84
- package/dist/tools/docx/builders/index.d.ts +0 -11
- package/dist/tools/docx/builders/index.js +0 -11
- package/dist/tools/docx/builders/markdown-builder.d.ts +0 -2
- package/dist/tools/docx/builders/markdown-builder.js +0 -260
- package/dist/tools/docx/builders/paragraph.d.ts +0 -12
- package/dist/tools/docx/builders/paragraph.js +0 -29
- package/dist/tools/docx/builders/table.d.ts +0 -10
- package/dist/tools/docx/builders/table.js +0 -138
- package/dist/tools/docx/builders/utils.d.ts +0 -5
- package/dist/tools/docx/builders/utils.js +0 -18
- package/dist/tools/docx/constants.d.ts +0 -32
- package/dist/tools/docx/constants.js +0 -61
- package/dist/tools/docx/converters/markdown-to-html.d.ts +0 -17
- package/dist/tools/docx/converters/markdown-to-html.js +0 -111
- package/dist/tools/docx/create.d.ts +0 -21
- package/dist/tools/docx/create.js +0 -386
- package/dist/tools/docx/dom.d.ts +0 -139
- package/dist/tools/docx/dom.js +0 -448
- package/dist/tools/docx/errors.d.ts +0 -28
- package/dist/tools/docx/errors.js +0 -48
- package/dist/tools/docx/extractors/images.d.ts +0 -14
- package/dist/tools/docx/extractors/images.js +0 -40
- package/dist/tools/docx/extractors/metadata.d.ts +0 -14
- package/dist/tools/docx/extractors/metadata.js +0 -64
- package/dist/tools/docx/extractors/sections.d.ts +0 -14
- package/dist/tools/docx/extractors/sections.js +0 -61
- package/dist/tools/docx/html.d.ts +0 -17
- package/dist/tools/docx/html.js +0 -111
- package/dist/tools/docx/index.d.ts +0 -10
- package/dist/tools/docx/index.js +0 -10
- package/dist/tools/docx/markdown.d.ts +0 -84
- package/dist/tools/docx/markdown.js +0 -507
- package/dist/tools/docx/modify.d.ts +0 -28
- package/dist/tools/docx/modify.js +0 -271
- package/dist/tools/docx/operations/handlers/index.d.ts +0 -39
- package/dist/tools/docx/operations/handlers/index.js +0 -152
- package/dist/tools/docx/operations/html-manipulator.d.ts +0 -24
- package/dist/tools/docx/operations/html-manipulator.js +0 -352
- package/dist/tools/docx/operations/index.d.ts +0 -14
- package/dist/tools/docx/operations/index.js +0 -61
- package/dist/tools/docx/operations/operation-handlers.d.ts +0 -3
- package/dist/tools/docx/operations/operation-handlers.js +0 -67
- package/dist/tools/docx/operations/preprocessor.d.ts +0 -14
- package/dist/tools/docx/operations/preprocessor.js +0 -44
- package/dist/tools/docx/operations/xml-replacer.d.ts +0 -9
- package/dist/tools/docx/operations/xml-replacer.js +0 -35
- package/dist/tools/docx/operations.d.ts +0 -13
- package/dist/tools/docx/operations.js +0 -13
- package/dist/tools/docx/ops/delete-paragraph-at-body-index.d.ts +0 -11
- package/dist/tools/docx/ops/delete-paragraph-at-body-index.js +0 -23
- package/dist/tools/docx/ops/header-replace-text-exact.d.ts +0 -13
- package/dist/tools/docx/ops/header-replace-text-exact.js +0 -55
- package/dist/tools/docx/ops/index.d.ts +0 -17
- package/dist/tools/docx/ops/index.js +0 -70
- package/dist/tools/docx/ops/insert-image-after-text.d.ts +0 -24
- package/dist/tools/docx/ops/insert-image-after-text.js +0 -128
- package/dist/tools/docx/ops/insert-paragraph-after-text.d.ts +0 -12
- package/dist/tools/docx/ops/insert-paragraph-after-text.js +0 -74
- package/dist/tools/docx/ops/insert-table-after-text.d.ts +0 -19
- package/dist/tools/docx/ops/insert-table-after-text.js +0 -57
- package/dist/tools/docx/ops/replace-hyperlink-url.d.ts +0 -12
- package/dist/tools/docx/ops/replace-hyperlink-url.js +0 -37
- package/dist/tools/docx/ops/replace-paragraph-at-body-index.d.ts +0 -9
- package/dist/tools/docx/ops/replace-paragraph-at-body-index.js +0 -25
- package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +0 -21
- package/dist/tools/docx/ops/replace-paragraph-text-exact.js +0 -36
- package/dist/tools/docx/ops/replace-table-cell-text.d.ts +0 -25
- package/dist/tools/docx/ops/replace-table-cell-text.js +0 -85
- package/dist/tools/docx/ops/set-color-for-paragraph-exact.d.ts +0 -9
- package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +0 -24
- package/dist/tools/docx/ops/set-color-for-style.d.ts +0 -13
- package/dist/tools/docx/ops/set-color-for-style.js +0 -31
- package/dist/tools/docx/ops/set-paragraph-style-at-body-index.d.ts +0 -8
- package/dist/tools/docx/ops/set-paragraph-style-at-body-index.js +0 -57
- package/dist/tools/docx/ops/table-set-cell-text.d.ts +0 -9
- package/dist/tools/docx/ops/table-set-cell-text.js +0 -40
- package/dist/tools/docx/parsers/image-extractor.d.ts +0 -18
- package/dist/tools/docx/parsers/image-extractor.js +0 -61
- package/dist/tools/docx/parsers/index.d.ts +0 -9
- package/dist/tools/docx/parsers/index.js +0 -9
- package/dist/tools/docx/parsers/paragraph-parser.d.ts +0 -2
- package/dist/tools/docx/parsers/paragraph-parser.js +0 -88
- package/dist/tools/docx/parsers/table-parser.d.ts +0 -9
- package/dist/tools/docx/parsers/table-parser.js +0 -72
- package/dist/tools/docx/parsers/xml-parser.d.ts +0 -25
- package/dist/tools/docx/parsers/xml-parser.js +0 -71
- package/dist/tools/docx/parsers/zip-reader.d.ts +0 -23
- package/dist/tools/docx/parsers/zip-reader.js +0 -52
- package/dist/tools/docx/read.d.ts +0 -27
- package/dist/tools/docx/read.js +0 -308
- package/dist/tools/docx/relationships.d.ts +0 -22
- package/dist/tools/docx/relationships.js +0 -76
- package/dist/tools/docx/structure.d.ts +0 -25
- package/dist/tools/docx/structure.js +0 -102
- package/dist/tools/docx/styled-html-parser.d.ts +0 -23
- package/dist/tools/docx/styled-html-parser.js +0 -1262
- package/dist/tools/docx/types.d.ts +0 -213
- package/dist/tools/docx/types.js +0 -5
- package/dist/tools/docx/utils/escaping.d.ts +0 -13
- package/dist/tools/docx/utils/escaping.js +0 -26
- package/dist/tools/docx/utils/images.d.ts +0 -9
- package/dist/tools/docx/utils/images.js +0 -26
- package/dist/tools/docx/utils/index.d.ts +0 -12
- package/dist/tools/docx/utils/index.js +0 -17
- package/dist/tools/docx/utils/markdown.d.ts +0 -13
- package/dist/tools/docx/utils/markdown.js +0 -32
- package/dist/tools/docx/utils/paths.d.ts +0 -15
- package/dist/tools/docx/utils/paths.js +0 -27
- package/dist/tools/docx/utils/versioning.d.ts +0 -25
- package/dist/tools/docx/utils/versioning.js +0 -55
- package/dist/tools/docx/utils.d.ts +0 -101
- package/dist/tools/docx/utils.js +0 -299
- package/dist/tools/docx/validate.d.ts +0 -33
- package/dist/tools/docx/validate.js +0 -49
- package/dist/tools/docx/validators.d.ts +0 -13
- package/dist/tools/docx/validators.js +0 -40
- package/dist/tools/docx/write.d.ts +0 -17
- package/dist/tools/docx/write.js +0 -88
- package/dist/tools/docx/xml-view-test.d.ts +0 -1
- package/dist/tools/docx/xml-view-test.js +0 -63
- package/dist/tools/docx/xml-view.d.ts +0 -56
- package/dist/tools/docx/xml-view.js +0 -169
- package/dist/tools/docx/zip.d.ts +0 -21
- package/dist/tools/docx/zip.js +0 -35
- package/dist/tools/macos-control/ax-adapter.d.ts +0 -55
- package/dist/tools/macos-control/ax-adapter.js +0 -438
- package/dist/tools/macos-control/cdp-adapter.d.ts +0 -23
- package/dist/tools/macos-control/cdp-adapter.js +0 -402
- package/dist/tools/macos-control/orchestrator.d.ts +0 -77
- package/dist/tools/macos-control/orchestrator.js +0 -136
- package/dist/tools/macos-control/role-aliases.d.ts +0 -5
- package/dist/tools/macos-control/role-aliases.js +0 -34
- package/dist/tools/macos-control/types.d.ts +0 -129
- package/dist/tools/macos-control/types.js +0 -1
- package/dist/tools/pdf-processor.d.ts +0 -1
- package/dist/tools/pdf-processor.js +0 -3
- package/dist/tools/search.d.ts +0 -32
- package/dist/tools/search.js +0 -202
- package/dist/ui/file-preview/src/components/toolbar.d.ts +0 -6
- package/dist/ui/file-preview/src/components/toolbar.js +0 -75
- package/dist/ui/shared/host-lifecycle.d.ts +0 -16
- package/dist/ui/shared/host-lifecycle.js +0 -35
- package/dist/ui/shared/rpc-client.d.ts +0 -14
- package/dist/ui/shared/rpc-client.js +0 -72
- package/dist/ui/shared/theme-adaptation.d.ts +0 -10
- package/dist/ui/shared/theme-adaptation.js +0 -118
- package/dist/ui/shared/tool-header.d.ts +0 -9
- package/dist/ui/shared/tool-header.js +0 -25
- package/dist/utils/crash-logger.d.ts +0 -18
- package/dist/utils/crash-logger.js +0 -44
- package/dist/utils/dedent.d.ts +0 -8
- package/dist/utils/dedent.js +0 -38
- /package/dist/{http-server-auto-tunnel.d.ts → ui/file-preview/src/model.js} +0 -0
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ Work with code and text, run processes, and automate tasks, going far beyond oth
|
|
|
37
37
|
- [How to install](#how-to-install)
|
|
38
38
|
- [Getting Started](#getting-started)
|
|
39
39
|
- [Usage](#usage)
|
|
40
|
+
- [File Preview UI & Markdown Editor](#file-preview-ui--markdown-editor)
|
|
40
41
|
- [Handling Long-Running Commands](#handling-long-running-commands)
|
|
41
42
|
- [Work in Progress and TODOs](#roadmap)
|
|
42
43
|
- [Sponsors and Supporters](#support-desktop-commander)
|
|
@@ -54,7 +55,7 @@ Execute long-running terminal commands on your computer and manage processes thr
|
|
|
54
55
|
## Features
|
|
55
56
|
|
|
56
57
|
- **Remote AI Control** - Use Desktop Commander from ChatGPT, Claude web, and other AI services via [Remote MCP](https://mcp.desktopcommander.app)
|
|
57
|
-
- **File Preview UI** - Visual file previews in Claude Desktop with rendered markdown, inline images, expandable content, and quick "Open in folder" access
|
|
58
|
+
- **File Preview UI** - Visual file previews in Claude Desktop with rendered markdown, inline images, expandable content, built-in markdown editor, and quick "Open in folder" access
|
|
58
59
|
- **Enhanced terminal commands with interactive process control**
|
|
59
60
|
- **Execute code in memory (Python, Node.js, R) without saving files**
|
|
60
61
|
- **Instant data analysis - just ask to analyze CSV/JSON/Excel files**
|
|
@@ -336,7 +337,7 @@ Add this to your client's MCP configuration file at the locations below:
|
|
|
336
337
|
<details>
|
|
337
338
|
<summary><b>Cursor</b></summary>
|
|
338
339
|
|
|
339
|
-
[
|
|
340
|
+
[Install MCP Server in Cursor](https://cursor.directory/mcp/desktop-commander-mcp)
|
|
340
341
|
|
|
341
342
|
Or add manually to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` in your project folder (project-specific).
|
|
342
343
|
|
|
@@ -694,6 +695,56 @@ Desktop Commander can be run in Docker containers for **complete isolation from
|
|
|
694
695
|
- Claude can see and analyze the actual image content
|
|
695
696
|
- Default 30-second timeout for URL requests
|
|
696
697
|
|
|
698
|
+
## File Preview UI & Markdown Editor
|
|
699
|
+
|
|
700
|
+
Desktop Commander includes a rich file preview widget in Claude Desktop that renders files visually as AI works with them.
|
|
701
|
+
|
|
702
|
+
### Supported file types
|
|
703
|
+
- **Markdown** — rendered preview with a built-in editor
|
|
704
|
+
- **Images** — inline display (PNG, JPEG, GIF, WebP, etc.)
|
|
705
|
+
- **Code files** — syntax-highlighted source view
|
|
706
|
+
- **HTML** — rendered preview with toggle to source view
|
|
707
|
+
- **Directories** — interactive tree with expand/collapse and lazy loading
|
|
708
|
+
- **PDF, Excel, DOCX** — native content extraction and display
|
|
709
|
+
|
|
710
|
+
### Markdown Editor
|
|
711
|
+
|
|
712
|
+
When viewing a `.md` file in Claude Desktop, you can edit it directly inside the preview panel — no need to open a separate app.
|
|
713
|
+
|
|
714
|
+
**How to use:**
|
|
715
|
+
1. Ask Claude to read or create a markdown file
|
|
716
|
+
2. Expand the file preview to fullscreen using the **⤢ Expand** button
|
|
717
|
+
3. The editor activates automatically in fullscreen mode
|
|
718
|
+
4. Edit your content with a live preview toggle, copy, undo, and save controls
|
|
719
|
+
5. Changes are saved back to disk; collapse to return to inline view
|
|
720
|
+
|
|
721
|
+
**Editor features:**
|
|
722
|
+
- Live **edit / preview toggle** — switch between raw markdown and rendered output
|
|
723
|
+
- **Auto-save** to disk with save status indicator
|
|
724
|
+
- **Undo** support to revert unsaved changes
|
|
725
|
+
- **Copy** button to grab the full markdown source
|
|
726
|
+
- **Open in editor** — launch your default markdown app directly from the panel
|
|
727
|
+
- Partial-file awareness — loads and merges surrounding lines when the file was only partially read
|
|
728
|
+
- Text selection context — select text in preview mode and the AI can reference your selection
|
|
729
|
+
|
|
730
|
+
### Directory Browser
|
|
731
|
+
|
|
732
|
+
When Claude runs `list_directory`, the result opens as an interactive file tree inside the preview panel — not just raw text output.
|
|
733
|
+
|
|
734
|
+
**Features:**
|
|
735
|
+
- **Expandable tree** — folders expand and collapse on click; top-level contents shown immediately
|
|
736
|
+
- **Lazy loading** — subfolders load on demand to keep the initial view fast
|
|
737
|
+
- **Large directory handling** — directories with many items show a `⚠ click to load all` button instead of overwhelming the view
|
|
738
|
+
- **Open in Finder/Explorer** — each folder has a quick-open button to reveal it in your file manager
|
|
739
|
+
- **Click to preview** — clicking any file in the tree opens it in the file preview panel directly
|
|
740
|
+
- **Back navigation** — after opening a file from the tree, a ← Back button returns you to the directory view
|
|
741
|
+
|
|
742
|
+
### Other preview features
|
|
743
|
+
- **Expand / collapse** — toggle between compact summary row and full panel
|
|
744
|
+
- **Open in folder** — reveal the file in Finder/Explorer with one click
|
|
745
|
+
- **Load more lines** — incrementally load content above or below a partial read window
|
|
746
|
+
- **Text selection** — highlight text in any preview; the AI can see and reference your selection
|
|
747
|
+
|
|
697
748
|
## Fuzzy Search Log Analysis (npm scripts)
|
|
698
749
|
|
|
699
750
|
The fuzzy search logging system includes convenient npm scripts for analyzing logs outside of the MCP environment:
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { ServerResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a file path to an absolute path for use in structured content.
|
|
4
|
+
* This ensures "Open in folder" always has a valid absolute path.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveAbsolutePath(filePath: string): string;
|
|
2
7
|
/**
|
|
3
8
|
* Handle read_file command
|
|
4
9
|
*/
|
|
@@ -19,7 +19,7 @@ function expandHome(filePath) {
|
|
|
19
19
|
* Resolve a file path to an absolute path for use in structured content.
|
|
20
20
|
* This ensures "Open in folder" always has a valid absolute path.
|
|
21
21
|
*/
|
|
22
|
-
function resolveAbsolutePath(filePath) {
|
|
22
|
+
export function resolveAbsolutePath(filePath) {
|
|
23
23
|
const expanded = expandHome(filePath);
|
|
24
24
|
return path.isAbsolute(expanded)
|
|
25
25
|
? path.resolve(expanded)
|
|
@@ -133,7 +133,7 @@ export async function handleReadFile(args) {
|
|
|
133
133
|
const textContent = typeof fileResult.content === 'string'
|
|
134
134
|
? fileResult.content
|
|
135
135
|
: fileResult.content.toString('utf8');
|
|
136
|
-
const fileType = resolvePreviewFileType(resolvedFilePath);
|
|
136
|
+
const fileType = fileResult.metadata?.isDirectory ? 'directory' : resolvePreviewFileType(resolvedFilePath);
|
|
137
137
|
return {
|
|
138
138
|
content: [{ type: "text", text: textContent }],
|
|
139
139
|
structuredContent: {
|
|
@@ -236,11 +236,17 @@ export async function handleWriteFile(args) {
|
|
|
236
236
|
await writeFile(parsed.path, parsed.content, parsed.mode);
|
|
237
237
|
// Provide more informative message based on mode
|
|
238
238
|
const modeMessage = parsed.mode === 'append' ? 'appended to' : 'wrote to';
|
|
239
|
+
const resolvedWritePath = resolveAbsolutePath(parsed.path);
|
|
239
240
|
return {
|
|
240
241
|
content: [{
|
|
241
242
|
type: "text",
|
|
242
243
|
text: `Successfully ${modeMessage} ${parsed.path} (${lineCount} lines) ${errorMessage}`
|
|
243
244
|
}],
|
|
245
|
+
structuredContent: {
|
|
246
|
+
fileName: path.basename(resolvedWritePath),
|
|
247
|
+
filePath: resolvedWritePath,
|
|
248
|
+
fileType: resolvePreviewFileType(resolvedWritePath),
|
|
249
|
+
},
|
|
244
250
|
};
|
|
245
251
|
}
|
|
246
252
|
catch (error) {
|
|
@@ -274,8 +280,14 @@ export async function handleListDirectory(args) {
|
|
|
274
280
|
const entries = await listDirectory(parsed.path, parsed.depth);
|
|
275
281
|
const duration = Date.now() - startTime;
|
|
276
282
|
const resultText = entries.join('\n');
|
|
283
|
+
const resolvedPath = resolveAbsolutePath(parsed.path);
|
|
277
284
|
return {
|
|
278
285
|
content: [{ type: "text", text: resultText }],
|
|
286
|
+
structuredContent: {
|
|
287
|
+
fileName: path.basename(resolvedPath),
|
|
288
|
+
filePath: resolvedPath,
|
|
289
|
+
fileType: 'directory',
|
|
290
|
+
},
|
|
279
291
|
};
|
|
280
292
|
}
|
|
281
293
|
catch (error) {
|
|
@@ -76,7 +76,7 @@ export class DesktopCommanderIntegration {
|
|
|
76
76
|
// We can't run it directly as it's an stdio MCP server that waits for input
|
|
77
77
|
const whichCommand = process.platform === 'win32' ? 'where' : 'which';
|
|
78
78
|
console.debug('[DEBUG] Using platform command:', whichCommand, 'on platform:', process.platform);
|
|
79
|
-
const check = spawn(whichCommand, [commandName]);
|
|
79
|
+
const check = spawn(whichCommand, [commandName], { windowsHide: true }); // Prevent visible console windows on Windows
|
|
80
80
|
check.on('error', (err) => {
|
|
81
81
|
console.debug('[DEBUG] Spawn error for', whichCommand, ':', err.message);
|
|
82
82
|
reject(err);
|
package/dist/search-manager.js
CHANGED
|
@@ -33,7 +33,7 @@ import PizZip from 'pizzip';
|
|
|
33
33
|
throw new Error(`Failed to locate ripgrep binary: ${err instanceof Error ? err.message : String(err)}`);
|
|
34
34
|
}
|
|
35
35
|
// Start ripgrep process
|
|
36
|
-
const rgProcess = spawn(rgPath, args);
|
|
36
|
+
const rgProcess = spawn(rgPath, args, { windowsHide: true }); // Prevent visible console windows on Windows
|
|
37
37
|
if (!rgProcess.pid) {
|
|
38
38
|
throw new Error('Failed to start ripgrep process');
|
|
39
39
|
}
|
|
@@ -94,7 +94,8 @@ import PizZip from 'pizzip';
|
|
|
94
94
|
const shouldSearchExcel = options.searchType === 'content' &&
|
|
95
95
|
this.shouldIncludeExcelSearch(options.filePattern, validPath);
|
|
96
96
|
if (shouldSearchExcel) {
|
|
97
|
-
this.searchExcelFiles(validPath, options.pattern, options.ignoreCase !== false, options.maxResults, options.filePattern // Pass filePattern to filter Excel files too
|
|
97
|
+
this.searchExcelFiles(validPath, options.pattern, options.ignoreCase !== false, options.maxResults, options.filePattern, // Pass filePattern to filter Excel files too
|
|
98
|
+
options.literalSearch // Respect literalSearch flag for Office files
|
|
98
99
|
).then(excelResults => {
|
|
99
100
|
// Add Excel results to session (merged after initial response)
|
|
100
101
|
for (const result of excelResults) {
|
|
@@ -110,7 +111,8 @@ import PizZip from 'pizzip';
|
|
|
110
111
|
const shouldSearchDocx = options.searchType === 'content' &&
|
|
111
112
|
this.shouldIncludeDocxSearch(options.filePattern, validPath);
|
|
112
113
|
if (shouldSearchDocx) {
|
|
113
|
-
this.searchDocxFiles(validPath, options.pattern, options.ignoreCase !== false, options.maxResults, options.filePattern
|
|
114
|
+
this.searchDocxFiles(validPath, options.pattern, options.ignoreCase !== false, options.maxResults, options.filePattern, options.literalSearch // Respect literalSearch flag for Office files
|
|
115
|
+
).then(docxResults => {
|
|
114
116
|
for (const result of docxResults) {
|
|
115
117
|
session.results.push(result);
|
|
116
118
|
session.totalMatches++;
|
|
@@ -223,19 +225,11 @@ import PizZip from 'pizzip';
|
|
|
223
225
|
* and inject into SearchManager, similar to how file handlers are structured in src/utils/files/
|
|
224
226
|
* This would allow adding other file type searches (PDF, etc.) without bloating search-manager.ts
|
|
225
227
|
*/
|
|
226
|
-
async searchExcelFiles(rootPath, pattern, ignoreCase, maxResults, filePattern) {
|
|
228
|
+
async searchExcelFiles(rootPath, pattern, ignoreCase, maxResults, filePattern, _literalSearch) {
|
|
227
229
|
const results = [];
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
try {
|
|
232
|
-
regex = new RegExp(pattern, flags);
|
|
233
|
-
}
|
|
234
|
-
catch {
|
|
235
|
-
// If pattern is not valid regex, escape it for literal matching
|
|
236
|
-
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
237
|
-
regex = new RegExp(escaped, flags);
|
|
238
|
-
}
|
|
230
|
+
// Office file search always uses literal matching to prevent ReDoS.
|
|
231
|
+
// Regex patterns are treated as literal strings — this is intentional.
|
|
232
|
+
const searchTerm = ignoreCase ? pattern.toLowerCase() : pattern;
|
|
239
233
|
// Find Excel files recursively
|
|
240
234
|
let excelFiles = await this.findExcelFiles(rootPath);
|
|
241
235
|
// Filter by filePattern if provided
|
|
@@ -246,7 +240,13 @@ import PizZip from 'pizzip';
|
|
|
246
240
|
return patterns.some(pat => {
|
|
247
241
|
// Support glob-like patterns
|
|
248
242
|
if (pat.includes('*')) {
|
|
249
|
-
|
|
243
|
+
// Escape all regex metacharacters first (preserving * for glob expansion),
|
|
244
|
+
// then convert the remaining * wildcards to .* for glob matching.
|
|
245
|
+
// Without this, patterns like report(2024).xlsx or [draft].xlsx would be
|
|
246
|
+
// misinterpreted as regex groups/character-classes.
|
|
247
|
+
const regexPat = pat
|
|
248
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape metacharacters except *
|
|
249
|
+
.replace(/\*/g, '.*'); // glob * → regex .*
|
|
250
250
|
return new RegExp(`^${regexPat}$`, 'i').test(fileName);
|
|
251
251
|
}
|
|
252
252
|
// Exact match (case-insensitive)
|
|
@@ -300,12 +300,10 @@ import PizZip from 'pizzip';
|
|
|
300
300
|
});
|
|
301
301
|
// Join all cell values with space for cross-column matching
|
|
302
302
|
const rowText = rowValues.join(' ');
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const matchContext =
|
|
307
|
-
? this.getMatchContext(rowText, match.index || 0, match[0].length)
|
|
308
|
-
: rowText.substring(0, 150);
|
|
303
|
+
const textToSearch = ignoreCase ? rowText.toLowerCase() : rowText;
|
|
304
|
+
const matchIndex = textToSearch.indexOf(searchTerm);
|
|
305
|
+
if (matchIndex !== -1) {
|
|
306
|
+
const matchContext = this.getMatchContext(rowText, matchIndex, searchTerm.length);
|
|
309
307
|
results.push({
|
|
310
308
|
file: `${filePath}:${sheetName}!Row${rowNumber}`,
|
|
311
309
|
line: rowNumber,
|
|
@@ -386,17 +384,11 @@ import PizZip from 'pizzip';
|
|
|
386
384
|
* Search DOCX files for content matches
|
|
387
385
|
* Extracts <w:t> text from document.xml and searches it
|
|
388
386
|
*/
|
|
389
|
-
async searchDocxFiles(rootPath, pattern, ignoreCase, maxResults, filePattern) {
|
|
387
|
+
async searchDocxFiles(rootPath, pattern, ignoreCase, maxResults, filePattern, _literalSearch) {
|
|
390
388
|
const results = [];
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
regex = new RegExp(pattern, flags);
|
|
395
|
-
}
|
|
396
|
-
catch {
|
|
397
|
-
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
398
|
-
regex = new RegExp(escaped, flags);
|
|
399
|
-
}
|
|
389
|
+
// Office file search always uses literal matching to prevent ReDoS.
|
|
390
|
+
// Regex patterns are treated as literal strings — this is intentional.
|
|
391
|
+
const searchTerm = ignoreCase ? pattern.toLowerCase() : pattern;
|
|
400
392
|
let docxFiles = await this.findDocxFiles(rootPath);
|
|
401
393
|
if (filePattern) {
|
|
402
394
|
const patterns = filePattern.split('|').map(p => p.trim()).filter(Boolean);
|
|
@@ -404,7 +396,9 @@ import PizZip from 'pizzip';
|
|
|
404
396
|
const fileName = path.basename(filePath);
|
|
405
397
|
return patterns.some(pat => {
|
|
406
398
|
if (pat.includes('*')) {
|
|
407
|
-
const regexPat = pat
|
|
399
|
+
const regexPat = pat
|
|
400
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape metacharacters except *
|
|
401
|
+
.replace(/\*/g, '.*'); // glob * → regex .*
|
|
408
402
|
return new RegExp(`^${regexPat}$`, 'i').test(fileName);
|
|
409
403
|
}
|
|
410
404
|
return fileName.toLowerCase() === pat.toLowerCase();
|
|
@@ -438,11 +432,10 @@ import PizZip from 'pizzip';
|
|
|
438
432
|
if (!text || !text.trim())
|
|
439
433
|
continue;
|
|
440
434
|
lineNum++;
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
: text.substring(0, 150);
|
|
435
|
+
const textToSearch = ignoreCase ? text.toLowerCase() : text;
|
|
436
|
+
const matchIndex = textToSearch.indexOf(searchTerm);
|
|
437
|
+
if (matchIndex !== -1) {
|
|
438
|
+
const matchContext = this.getMatchContext(text, matchIndex, searchTerm.length);
|
|
446
439
|
const partName = xmlPath === 'word/document.xml' ? '' : `:${xmlPath.replace('word/', '')}`;
|
|
447
440
|
results.push({
|
|
448
441
|
file: `${filePath}${partName}`,
|
package/dist/server.js
CHANGED
|
@@ -137,9 +137,11 @@ deferLog('info', 'Setting up request handlers...');
|
|
|
137
137
|
* Check if a tool should be included based on current client
|
|
138
138
|
*/
|
|
139
139
|
function shouldIncludeTool(toolName) {
|
|
140
|
-
// Exclude
|
|
141
|
-
if (
|
|
142
|
-
|
|
140
|
+
// Exclude these tools for desktop-commander client (DC-specific meta-tools not useful when DC itself is the client)
|
|
141
|
+
if (currentClient?.name === 'desktop-commander-app') {
|
|
142
|
+
if (toolName === 'give_feedback_to_desktop_commander' || toolName === 'get_prompts') {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
143
145
|
}
|
|
144
146
|
// Add more conditional tool logic here as needed
|
|
145
147
|
// Example: if (toolName === 'some_tool' && currentClient?.name === 'some_client') return false;
|
|
@@ -328,6 +330,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
328
330
|
${PATH_GUIDANCE}
|
|
329
331
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
330
332
|
inputSchema: zodToJsonSchema(WriteFileArgsSchema),
|
|
333
|
+
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true),
|
|
331
334
|
annotations: {
|
|
332
335
|
title: "Write File",
|
|
333
336
|
readOnlyHint: false,
|
|
@@ -447,6 +450,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
447
450
|
${PATH_GUIDANCE}
|
|
448
451
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
449
452
|
inputSchema: zodToJsonSchema(ListDirectoryArgsSchema),
|
|
453
|
+
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true),
|
|
450
454
|
annotations: {
|
|
451
455
|
title: "List Directory Contents",
|
|
452
456
|
readOnlyHint: true,
|
|
@@ -705,6 +709,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
705
709
|
${PATH_GUIDANCE}
|
|
706
710
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
707
711
|
inputSchema: zodToJsonSchema(EditBlockArgsSchema),
|
|
712
|
+
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true),
|
|
708
713
|
annotations: {
|
|
709
714
|
title: "Edit Block",
|
|
710
715
|
readOnlyHint: false,
|
|
@@ -1087,7 +1092,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1087
1092
|
const startTime = Date.now();
|
|
1088
1093
|
try {
|
|
1089
1094
|
// Prepare telemetry data - add config key for set_config_value
|
|
1090
|
-
const telemetryData = { name };
|
|
1095
|
+
const telemetryData = { tool_name: name };
|
|
1091
1096
|
// Extract metadata from _meta field if present
|
|
1092
1097
|
const metadata = request.params._meta;
|
|
1093
1098
|
if (metadata && typeof metadata === 'object') {
|
package/dist/terminal-manager.js
CHANGED
|
@@ -119,7 +119,8 @@ export class TerminalManager {
|
|
|
119
119
|
env: {
|
|
120
120
|
...process.env,
|
|
121
121
|
TERM: 'xterm-256color' // Better terminal compatibility
|
|
122
|
-
}
|
|
122
|
+
},
|
|
123
|
+
windowsHide: true // Prevent visible console windows on Windows
|
|
123
124
|
};
|
|
124
125
|
// Add shell option if needed (for unknown shells)
|
|
125
126
|
if (spawnConfig.useShellOption) {
|
|
@@ -138,7 +139,8 @@ export class TerminalManager {
|
|
|
138
139
|
env: {
|
|
139
140
|
...process.env,
|
|
140
141
|
TERM: 'xterm-256color'
|
|
141
|
-
}
|
|
142
|
+
},
|
|
143
|
+
windowsHide: true // Prevent visible console windows on Windows
|
|
142
144
|
};
|
|
143
145
|
}
|
|
144
146
|
// Spawn the process with appropriate arguments
|
package/dist/tools/edit.js
CHANGED
|
@@ -23,6 +23,8 @@ import path from 'path';
|
|
|
23
23
|
import { detectLineEnding, normalizeLineEndings } from '../utils/lineEndingHandler.js';
|
|
24
24
|
import { configManager } from '../config-manager.js';
|
|
25
25
|
import { fuzzySearchLogger } from '../utils/fuzzySearchLogger.js';
|
|
26
|
+
import { resolvePreviewFileType } from '../ui/file-preview/shared/preview-file-types.js';
|
|
27
|
+
import { resolveAbsolutePath } from '../handlers/filesystem-handlers.js';
|
|
26
28
|
/**
|
|
27
29
|
* Threshold for fuzzy matching - similarity must be at least this value to be considered
|
|
28
30
|
* (0-1 scale where 1 is perfect match and 0 is completely different)
|
|
@@ -155,11 +157,30 @@ RECOMMENDATION: For large search/replace operations, consider breaking them into
|
|
|
155
157
|
}
|
|
156
158
|
await writeFile(filePath, newContent);
|
|
157
159
|
capture('server_edit_block_exact_success', { fileExtension: fileExtension, expectedReplacements, hasWarning: warningMessage !== "" });
|
|
160
|
+
const resolvedEditPath = resolveAbsolutePath(filePath);
|
|
161
|
+
// Show a partial preview centered on the edited area
|
|
162
|
+
const newLines = newContent.split('\n');
|
|
163
|
+
const totalLines = newLines.length;
|
|
164
|
+
const changePos = content.indexOf(normalizedSearch);
|
|
165
|
+
const changeStartLine = changePos >= 0 ? newContent.substring(0, changePos).split('\n').length - 1 : 0;
|
|
166
|
+
const changeLineCount = block.replace.split('\n').length;
|
|
167
|
+
const contextLines = 10;
|
|
168
|
+
const previewStart = Math.max(0, changeStartLine - contextLines);
|
|
169
|
+
const previewEnd = Math.min(totalLines, changeStartLine + changeLineCount + contextLines);
|
|
170
|
+
const previewContent = newLines.slice(previewStart, previewEnd).join('\n');
|
|
171
|
+
const previewLineCount = previewEnd - previewStart;
|
|
172
|
+
const remaining = totalLines - previewEnd;
|
|
173
|
+
const statusLine = `[Reading ${previewLineCount} lines from ${previewStart === 0 ? 'start' : `line ${previewStart}`} (total: ${totalLines} lines, ${remaining} remaining)]\n\n`;
|
|
158
174
|
return {
|
|
159
175
|
content: [{
|
|
160
176
|
type: "text",
|
|
161
|
-
text:
|
|
177
|
+
text: `${statusLine}${previewContent}`
|
|
162
178
|
}],
|
|
179
|
+
structuredContent: {
|
|
180
|
+
fileName: path.basename(resolvedEditPath),
|
|
181
|
+
filePath: resolvedEditPath,
|
|
182
|
+
fileType: resolvePreviewFileType(resolvedEditPath),
|
|
183
|
+
},
|
|
163
184
|
};
|
|
164
185
|
}
|
|
165
186
|
// If exact match found but count doesn't match expected, inform the user
|
|
@@ -337,11 +358,17 @@ export async function handleEditBlock(args) {
|
|
|
337
358
|
try {
|
|
338
359
|
// parsed.range is guaranteed non-empty string by hasRange check above
|
|
339
360
|
await handler.editRange(validatedPath, parsed.range, content, parsed.options);
|
|
361
|
+
const resolvedRangePath = resolveAbsolutePath(parsed.file_path);
|
|
340
362
|
return {
|
|
341
363
|
content: [{
|
|
342
364
|
type: "text",
|
|
343
365
|
text: `Successfully updated range ${parsed.range} in ${parsed.file_path}`
|
|
344
366
|
}],
|
|
367
|
+
structuredContent: {
|
|
368
|
+
fileName: path.basename(resolvedRangePath),
|
|
369
|
+
filePath: resolvedRangePath,
|
|
370
|
+
fileType: resolvePreviewFileType(resolvedRangePath),
|
|
371
|
+
},
|
|
345
372
|
};
|
|
346
373
|
}
|
|
347
374
|
catch (error) {
|
|
@@ -366,11 +393,17 @@ export async function handleEditBlock(args) {
|
|
|
366
393
|
expected_replacements: parsed.expected_replacements,
|
|
367
394
|
});
|
|
368
395
|
if (result.success) {
|
|
396
|
+
const resolvedEditRangePath = resolveAbsolutePath(parsed.file_path);
|
|
369
397
|
return {
|
|
370
398
|
content: [{
|
|
371
399
|
type: "text",
|
|
372
400
|
text: `Successfully applied ${result.editsApplied} edit(s) to ${parsed.file_path}`
|
|
373
401
|
}],
|
|
402
|
+
structuredContent: {
|
|
403
|
+
fileName: path.basename(resolvedEditRangePath),
|
|
404
|
+
filePath: resolvedEditRangePath,
|
|
405
|
+
fileType: resolvePreviewFileType(resolvedEditRangePath),
|
|
406
|
+
},
|
|
374
407
|
};
|
|
375
408
|
}
|
|
376
409
|
const errorMsg = result.errors?.map(e => e.error).join('; ') || 'Unknown error';
|
package/dist/tools/filesystem.js
CHANGED
|
@@ -46,6 +46,45 @@ async function getDefaultReadLength() {
|
|
|
46
46
|
const config = await configManager.getConfig();
|
|
47
47
|
return config.fileReadLineLimit ?? 1000; // Default to 1000 lines if not set
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns a helpful error message when a file operation fails with a permission
|
|
51
|
+
* or timeout error.
|
|
52
|
+
*
|
|
53
|
+
* Lists all common causes without path-based detection — the AI receiving this
|
|
54
|
+
* error should inspect the path and inform the user which cause is most likely
|
|
55
|
+
* (e.g. cloud storage folder, network drive, system file, locked file, etc.)
|
|
56
|
+
*/
|
|
57
|
+
function buildPermissionError(filePath, errCode) {
|
|
58
|
+
const isMac = process.platform === 'darwin';
|
|
59
|
+
const isTimeout = errCode === 'ETIMEDOUT';
|
|
60
|
+
const lines = [
|
|
61
|
+
`Cannot read file — ${isTimeout ? 'operation timed out' : 'permission denied'} (${errCode}).`,
|
|
62
|
+
`Path: ${filePath}`,
|
|
63
|
+
``,
|
|
64
|
+
`[AI: based on the path above, determine which cause is most likely and explain it to the user.]`,
|
|
65
|
+
``,
|
|
66
|
+
`Possible causes and fixes:`,
|
|
67
|
+
` 1. File is in cloud storage (Google Drive / iCloud / Dropbox / OneDrive) but not downloaded locally.`,
|
|
68
|
+
` → Right-click the file and choose "Download Now", "Make Available Offline", or "Keep on This Device".`,
|
|
69
|
+
` 2. Cloud storage app is not running or not signed in.`,
|
|
70
|
+
` → Open your cloud storage app and make sure it is syncing.`,
|
|
71
|
+
` 3. File is on a network drive or virtual filesystem that is currently unavailable.`,
|
|
72
|
+
` → Check that the network share or drive is mounted and accessible.`,
|
|
73
|
+
` 4. File has restricted permissions (e.g. system file, locked by another process, or chmod 000).`,
|
|
74
|
+
` → Check file permissions or close any app that may have the file open.`,
|
|
75
|
+
` 5. The app does not have permission to access this location (macOS Full Disk Access).`,
|
|
76
|
+
];
|
|
77
|
+
if (isMac) {
|
|
78
|
+
lines.push(` → Go to System Settings → Privacy & Security → Full Disk Access and enable Claude.`);
|
|
79
|
+
lines.push(` → To open that pane directly, run in terminal:`);
|
|
80
|
+
lines.push(` open "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"`);
|
|
81
|
+
lines.push(` Then find "Claude" in the list and enable the toggle next to it.`);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
lines.push(` → Check that the app has permission to access this file location.`);
|
|
85
|
+
}
|
|
86
|
+
return new Error(lines.join('\n'));
|
|
87
|
+
}
|
|
49
88
|
// Initialize allowed directories from configuration
|
|
50
89
|
async function getAllowedDirs() {
|
|
51
90
|
try {
|
|
@@ -307,6 +346,35 @@ export async function readFileFromDisk(filePath, options) {
|
|
|
307
346
|
const validPath = await validatePath(filePath);
|
|
308
347
|
// Get file extension for telemetry
|
|
309
348
|
const fileExtension = getFileExtension(validPath);
|
|
349
|
+
// Check if path is a directory — return listing instead of EISDIR error
|
|
350
|
+
try {
|
|
351
|
+
const stats = await fs.stat(validPath);
|
|
352
|
+
if (stats.isDirectory()) {
|
|
353
|
+
const dirListOp = async () => {
|
|
354
|
+
const entries = await listDirectory(validPath);
|
|
355
|
+
const listing = entries.join('\n');
|
|
356
|
+
return {
|
|
357
|
+
content: `This is a directory, not a file. Use the list_directory tool instead of read_file for directories.\n\n${listing}`,
|
|
358
|
+
mimeType: 'text/plain',
|
|
359
|
+
metadata: { isImage: false, isDirectory: true }
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
const dirResult = await withTimeout(dirListOp(), FILE_OPERATION_TIMEOUTS.FILE_READ, 'Directory listing fallback', null);
|
|
363
|
+
if (dirResult === null) {
|
|
364
|
+
throw new Error(`Directory listing timed out for: ${filePath}`);
|
|
365
|
+
}
|
|
366
|
+
return dirResult;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
// If stat itself failed, fall through to the read path which will produce a proper error.
|
|
371
|
+
// But if this was a directory-listing error, re-throw — don't let it fall into the file-read path.
|
|
372
|
+
const err = error;
|
|
373
|
+
if (err.message?.includes('Directory listing') || err.message?.includes('list_directory')) {
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
// stat() failed (e.g. ENOENT) — fall through to the read path below
|
|
377
|
+
}
|
|
310
378
|
// Check file size before attempting to read
|
|
311
379
|
try {
|
|
312
380
|
const stats = await fs.stat(validPath);
|
|
@@ -357,7 +425,20 @@ export async function readFileFromDisk(filePath, options) {
|
|
|
357
425
|
};
|
|
358
426
|
};
|
|
359
427
|
// Execute with timeout
|
|
360
|
-
|
|
428
|
+
let result;
|
|
429
|
+
try {
|
|
430
|
+
result = await withTimeout(readOperation(), FILE_OPERATION_TIMEOUTS.FILE_READ, `Read file operation for ${filePath}`, null);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
const err = error;
|
|
434
|
+
// withTimeout rejects with a plain string "__ERROR__: ... timed out after N seconds"
|
|
435
|
+
// when defaultValue is null — it has no .code property, so check for that too.
|
|
436
|
+
const isWithTimeoutString = typeof error === 'string' && error.startsWith('__ERROR__:');
|
|
437
|
+
if (isWithTimeoutString || err.code === 'EPERM' || err.code === 'EACCES' || err.code === 'ETIMEDOUT') {
|
|
438
|
+
throw buildPermissionError(filePath, isWithTimeoutString ? 'ETIMEDOUT' : err.code);
|
|
439
|
+
}
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
361
442
|
if (result == null) {
|
|
362
443
|
// Handles the impossible case where withTimeout resolves to null instead of throwing
|
|
363
444
|
throw new Error('Failed to read the file');
|
|
@@ -490,9 +571,16 @@ export async function listDirectory(dirPath, depth = 2) {
|
|
|
490
571
|
entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
491
572
|
}
|
|
492
573
|
catch (error) {
|
|
493
|
-
|
|
574
|
+
const err = error;
|
|
494
575
|
const displayPath = relativePath || path.basename(currentPath);
|
|
495
|
-
|
|
576
|
+
// Keep [DENIED] prefix so UI parser regex still matches.
|
|
577
|
+
// Append a hint for permission/timeout errors so user gets context.
|
|
578
|
+
if (err.code === 'EPERM' || err.code === 'EACCES' || err.code === 'ETIMEDOUT') {
|
|
579
|
+
results.push(`[DENIED] ${displayPath} — not accessible (permission denied, cloud-only file, or Full Disk Access not granted)`);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
results.push(`[DENIED] ${displayPath}`);
|
|
583
|
+
}
|
|
496
584
|
return;
|
|
497
585
|
}
|
|
498
586
|
// Apply filtering for nested directories (not top level)
|
|
@@ -27,7 +27,8 @@ async function executeNodeCode(code, timeout_ms = 30000) {
|
|
|
27
27
|
const result = await new Promise((resolve) => {
|
|
28
28
|
const proc = spawn(process.execPath, [tempFile], {
|
|
29
29
|
cwd: mcpRoot,
|
|
30
|
-
timeout: timeout_ms
|
|
30
|
+
timeout: timeout_ms,
|
|
31
|
+
windowsHide: true // Prevent visible console windows on Windows
|
|
31
32
|
});
|
|
32
33
|
let stdout = '';
|
|
33
34
|
let stderr = '';
|