@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.
Files changed (263) hide show
  1. package/README.md +53 -2
  2. package/dist/handlers/filesystem-handlers.d.ts +5 -0
  3. package/dist/handlers/filesystem-handlers.js +14 -2
  4. package/dist/remote-device/desktop-commander-integration.js +1 -1
  5. package/dist/search-manager.js +31 -38
  6. package/dist/server.js +9 -4
  7. package/dist/terminal-manager.js +4 -2
  8. package/dist/tools/edit.js +34 -1
  9. package/dist/tools/filesystem.js +91 -3
  10. package/dist/tools/improved-process-tools.js +2 -1
  11. package/dist/ui/config-editor/config-editor-runtime.js +65 -14096
  12. package/dist/ui/config-editor/styles.css +2 -1
  13. package/dist/ui/file-preview/preview-runtime.js +435 -26533
  14. package/dist/ui/file-preview/shared/preview-file-types.d.ts +1 -1
  15. package/dist/ui/file-preview/src/app.d.ts +1 -5
  16. package/dist/ui/file-preview/src/app.js +384 -534
  17. package/dist/ui/file-preview/src/components/markdown-renderer.js +47 -9
  18. package/dist/ui/file-preview/src/directory-controller.d.ts +8 -0
  19. package/dist/ui/file-preview/src/directory-controller.js +233 -0
  20. package/dist/ui/file-preview/src/document-layout.d.ts +20 -0
  21. package/dist/ui/file-preview/src/document-layout.js +109 -0
  22. package/dist/ui/file-preview/src/document-outline.d.ts +17 -0
  23. package/dist/ui/file-preview/src/document-outline.js +97 -0
  24. package/dist/ui/file-preview/src/document-workspace.d.ts +19 -0
  25. package/dist/ui/file-preview/src/document-workspace.js +33 -0
  26. package/dist/ui/file-preview/src/file-type-handlers.d.ts +10 -0
  27. package/dist/ui/file-preview/src/file-type-handlers.js +98 -0
  28. package/dist/ui/file-preview/src/host/external-actions.d.ts +19 -0
  29. package/dist/ui/file-preview/src/host/external-actions.js +94 -0
  30. package/dist/ui/file-preview/src/host/selection-context.d.ts +9 -0
  31. package/dist/ui/file-preview/src/host/selection-context.js +106 -0
  32. package/dist/ui/file-preview/src/markdown/conflict-dialog.d.ts +40 -0
  33. package/dist/ui/file-preview/src/markdown/conflict-dialog.js +163 -0
  34. package/dist/ui/file-preview/src/markdown/controller.d.ts +44 -0
  35. package/dist/ui/file-preview/src/markdown/controller.js +1040 -0
  36. package/dist/ui/file-preview/src/markdown/editor.d.ts +131 -0
  37. package/dist/ui/file-preview/src/markdown/editor.js +1479 -0
  38. package/dist/ui/file-preview/src/markdown/linking.d.ts +16 -0
  39. package/dist/ui/file-preview/src/markdown/linking.js +228 -0
  40. package/dist/ui/file-preview/src/markdown/outline.d.ts +2 -0
  41. package/dist/ui/file-preview/src/markdown/outline.js +16 -0
  42. package/dist/ui/file-preview/src/markdown/parser.d.ts +30 -0
  43. package/dist/ui/file-preview/src/markdown/parser.js +38 -0
  44. package/dist/ui/file-preview/src/markdown/preview.d.ts +1 -0
  45. package/dist/ui/file-preview/src/markdown/preview.js +20 -0
  46. package/dist/ui/file-preview/src/markdown/slugify.d.ts +3 -0
  47. package/dist/ui/file-preview/src/markdown/slugify.js +31 -0
  48. package/dist/ui/file-preview/src/markdown/utils.d.ts +1 -0
  49. package/dist/ui/file-preview/src/markdown/utils.js +15 -0
  50. package/dist/ui/file-preview/src/model.d.ts +35 -0
  51. package/dist/ui/file-preview/src/panel-actions.d.ts +17 -0
  52. package/dist/ui/file-preview/src/panel-actions.js +182 -0
  53. package/dist/ui/file-preview/src/path-utils.d.ts +6 -0
  54. package/dist/ui/file-preview/src/path-utils.js +64 -0
  55. package/dist/ui/file-preview/src/payload-utils.d.ts +11 -0
  56. package/dist/ui/file-preview/src/payload-utils.js +94 -0
  57. package/dist/ui/file-preview/styles.css +1066 -233
  58. package/dist/ui/shared/widget-state.d.ts +6 -1
  59. package/dist/ui/shared/widget-state.js +102 -4
  60. package/dist/utils/capture.js +1 -1
  61. package/dist/utils/files/base.d.ts +2 -0
  62. package/dist/utils/open-browser.js +1 -1
  63. package/dist/utils/toolHistory.d.ts +13 -0
  64. package/dist/utils/toolHistory.js +65 -0
  65. package/dist/version.d.ts +1 -1
  66. package/dist/version.js +1 -1
  67. package/package.json +12 -1
  68. package/dist/data/spec-kit-prompts.json +0 -123
  69. package/dist/handlers/macos-control-handlers.d.ts +0 -16
  70. package/dist/handlers/macos-control-handlers.js +0 -81
  71. package/dist/handlers/node-handlers.d.ts +0 -6
  72. package/dist/handlers/node-handlers.js +0 -73
  73. package/dist/handlers/test-crash-handler.d.ts +0 -11
  74. package/dist/handlers/test-crash-handler.js +0 -26
  75. package/dist/http-index.d.ts +0 -45
  76. package/dist/http-index.js +0 -51
  77. package/dist/http-server-auto-tunnel.js +0 -667
  78. package/dist/http-server-named-tunnel.d.ts +0 -2
  79. package/dist/http-server-named-tunnel.js +0 -167
  80. package/dist/http-server-tunnel.d.ts +0 -2
  81. package/dist/http-server-tunnel.js +0 -111
  82. package/dist/http-server.d.ts +0 -2
  83. package/dist/http-server.js +0 -270
  84. package/dist/index-oauth.d.ts +0 -2
  85. package/dist/index-oauth.js +0 -201
  86. package/dist/lib.d.ts +0 -10
  87. package/dist/lib.js +0 -10
  88. package/dist/oauth/auth-middleware.d.ts +0 -20
  89. package/dist/oauth/auth-middleware.js +0 -62
  90. package/dist/oauth/index.d.ts +0 -3
  91. package/dist/oauth/index.js +0 -3
  92. package/dist/oauth/oauth-manager.d.ts +0 -80
  93. package/dist/oauth/oauth-manager.js +0 -179
  94. package/dist/oauth/oauth-routes.d.ts +0 -3
  95. package/dist/oauth/oauth-routes.js +0 -377
  96. package/dist/oauth/provider.d.ts +0 -22
  97. package/dist/oauth/provider.js +0 -124
  98. package/dist/oauth/server.d.ts +0 -18
  99. package/dist/oauth/server.js +0 -160
  100. package/dist/oauth/types.d.ts +0 -54
  101. package/dist/oauth/types.js +0 -2
  102. package/dist/remote-device/templates/auth-success.d.ts +0 -1
  103. package/dist/remote-device/templates/auth-success.js +0 -30
  104. package/dist/setup.log +0 -275
  105. package/dist/test-docx.d.ts +0 -1
  106. package/dist/test-setup.js +0 -14
  107. package/dist/tools/docx/builders/html-builder.d.ts +0 -17
  108. package/dist/tools/docx/builders/html-builder.js +0 -92
  109. package/dist/tools/docx/builders/image.d.ts +0 -14
  110. package/dist/tools/docx/builders/image.js +0 -84
  111. package/dist/tools/docx/builders/index.d.ts +0 -11
  112. package/dist/tools/docx/builders/index.js +0 -11
  113. package/dist/tools/docx/builders/markdown-builder.d.ts +0 -2
  114. package/dist/tools/docx/builders/markdown-builder.js +0 -260
  115. package/dist/tools/docx/builders/paragraph.d.ts +0 -12
  116. package/dist/tools/docx/builders/paragraph.js +0 -29
  117. package/dist/tools/docx/builders/table.d.ts +0 -10
  118. package/dist/tools/docx/builders/table.js +0 -138
  119. package/dist/tools/docx/builders/utils.d.ts +0 -5
  120. package/dist/tools/docx/builders/utils.js +0 -18
  121. package/dist/tools/docx/constants.d.ts +0 -32
  122. package/dist/tools/docx/constants.js +0 -61
  123. package/dist/tools/docx/converters/markdown-to-html.d.ts +0 -17
  124. package/dist/tools/docx/converters/markdown-to-html.js +0 -111
  125. package/dist/tools/docx/create.d.ts +0 -21
  126. package/dist/tools/docx/create.js +0 -386
  127. package/dist/tools/docx/dom.d.ts +0 -139
  128. package/dist/tools/docx/dom.js +0 -448
  129. package/dist/tools/docx/errors.d.ts +0 -28
  130. package/dist/tools/docx/errors.js +0 -48
  131. package/dist/tools/docx/extractors/images.d.ts +0 -14
  132. package/dist/tools/docx/extractors/images.js +0 -40
  133. package/dist/tools/docx/extractors/metadata.d.ts +0 -14
  134. package/dist/tools/docx/extractors/metadata.js +0 -64
  135. package/dist/tools/docx/extractors/sections.d.ts +0 -14
  136. package/dist/tools/docx/extractors/sections.js +0 -61
  137. package/dist/tools/docx/html.d.ts +0 -17
  138. package/dist/tools/docx/html.js +0 -111
  139. package/dist/tools/docx/index.d.ts +0 -10
  140. package/dist/tools/docx/index.js +0 -10
  141. package/dist/tools/docx/markdown.d.ts +0 -84
  142. package/dist/tools/docx/markdown.js +0 -507
  143. package/dist/tools/docx/modify.d.ts +0 -28
  144. package/dist/tools/docx/modify.js +0 -271
  145. package/dist/tools/docx/operations/handlers/index.d.ts +0 -39
  146. package/dist/tools/docx/operations/handlers/index.js +0 -152
  147. package/dist/tools/docx/operations/html-manipulator.d.ts +0 -24
  148. package/dist/tools/docx/operations/html-manipulator.js +0 -352
  149. package/dist/tools/docx/operations/index.d.ts +0 -14
  150. package/dist/tools/docx/operations/index.js +0 -61
  151. package/dist/tools/docx/operations/operation-handlers.d.ts +0 -3
  152. package/dist/tools/docx/operations/operation-handlers.js +0 -67
  153. package/dist/tools/docx/operations/preprocessor.d.ts +0 -14
  154. package/dist/tools/docx/operations/preprocessor.js +0 -44
  155. package/dist/tools/docx/operations/xml-replacer.d.ts +0 -9
  156. package/dist/tools/docx/operations/xml-replacer.js +0 -35
  157. package/dist/tools/docx/operations.d.ts +0 -13
  158. package/dist/tools/docx/operations.js +0 -13
  159. package/dist/tools/docx/ops/delete-paragraph-at-body-index.d.ts +0 -11
  160. package/dist/tools/docx/ops/delete-paragraph-at-body-index.js +0 -23
  161. package/dist/tools/docx/ops/header-replace-text-exact.d.ts +0 -13
  162. package/dist/tools/docx/ops/header-replace-text-exact.js +0 -55
  163. package/dist/tools/docx/ops/index.d.ts +0 -17
  164. package/dist/tools/docx/ops/index.js +0 -70
  165. package/dist/tools/docx/ops/insert-image-after-text.d.ts +0 -24
  166. package/dist/tools/docx/ops/insert-image-after-text.js +0 -128
  167. package/dist/tools/docx/ops/insert-paragraph-after-text.d.ts +0 -12
  168. package/dist/tools/docx/ops/insert-paragraph-after-text.js +0 -74
  169. package/dist/tools/docx/ops/insert-table-after-text.d.ts +0 -19
  170. package/dist/tools/docx/ops/insert-table-after-text.js +0 -57
  171. package/dist/tools/docx/ops/replace-hyperlink-url.d.ts +0 -12
  172. package/dist/tools/docx/ops/replace-hyperlink-url.js +0 -37
  173. package/dist/tools/docx/ops/replace-paragraph-at-body-index.d.ts +0 -9
  174. package/dist/tools/docx/ops/replace-paragraph-at-body-index.js +0 -25
  175. package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +0 -21
  176. package/dist/tools/docx/ops/replace-paragraph-text-exact.js +0 -36
  177. package/dist/tools/docx/ops/replace-table-cell-text.d.ts +0 -25
  178. package/dist/tools/docx/ops/replace-table-cell-text.js +0 -85
  179. package/dist/tools/docx/ops/set-color-for-paragraph-exact.d.ts +0 -9
  180. package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +0 -24
  181. package/dist/tools/docx/ops/set-color-for-style.d.ts +0 -13
  182. package/dist/tools/docx/ops/set-color-for-style.js +0 -31
  183. package/dist/tools/docx/ops/set-paragraph-style-at-body-index.d.ts +0 -8
  184. package/dist/tools/docx/ops/set-paragraph-style-at-body-index.js +0 -57
  185. package/dist/tools/docx/ops/table-set-cell-text.d.ts +0 -9
  186. package/dist/tools/docx/ops/table-set-cell-text.js +0 -40
  187. package/dist/tools/docx/parsers/image-extractor.d.ts +0 -18
  188. package/dist/tools/docx/parsers/image-extractor.js +0 -61
  189. package/dist/tools/docx/parsers/index.d.ts +0 -9
  190. package/dist/tools/docx/parsers/index.js +0 -9
  191. package/dist/tools/docx/parsers/paragraph-parser.d.ts +0 -2
  192. package/dist/tools/docx/parsers/paragraph-parser.js +0 -88
  193. package/dist/tools/docx/parsers/table-parser.d.ts +0 -9
  194. package/dist/tools/docx/parsers/table-parser.js +0 -72
  195. package/dist/tools/docx/parsers/xml-parser.d.ts +0 -25
  196. package/dist/tools/docx/parsers/xml-parser.js +0 -71
  197. package/dist/tools/docx/parsers/zip-reader.d.ts +0 -23
  198. package/dist/tools/docx/parsers/zip-reader.js +0 -52
  199. package/dist/tools/docx/read.d.ts +0 -27
  200. package/dist/tools/docx/read.js +0 -308
  201. package/dist/tools/docx/relationships.d.ts +0 -22
  202. package/dist/tools/docx/relationships.js +0 -76
  203. package/dist/tools/docx/structure.d.ts +0 -25
  204. package/dist/tools/docx/structure.js +0 -102
  205. package/dist/tools/docx/styled-html-parser.d.ts +0 -23
  206. package/dist/tools/docx/styled-html-parser.js +0 -1262
  207. package/dist/tools/docx/types.d.ts +0 -213
  208. package/dist/tools/docx/types.js +0 -5
  209. package/dist/tools/docx/utils/escaping.d.ts +0 -13
  210. package/dist/tools/docx/utils/escaping.js +0 -26
  211. package/dist/tools/docx/utils/images.d.ts +0 -9
  212. package/dist/tools/docx/utils/images.js +0 -26
  213. package/dist/tools/docx/utils/index.d.ts +0 -12
  214. package/dist/tools/docx/utils/index.js +0 -17
  215. package/dist/tools/docx/utils/markdown.d.ts +0 -13
  216. package/dist/tools/docx/utils/markdown.js +0 -32
  217. package/dist/tools/docx/utils/paths.d.ts +0 -15
  218. package/dist/tools/docx/utils/paths.js +0 -27
  219. package/dist/tools/docx/utils/versioning.d.ts +0 -25
  220. package/dist/tools/docx/utils/versioning.js +0 -55
  221. package/dist/tools/docx/utils.d.ts +0 -101
  222. package/dist/tools/docx/utils.js +0 -299
  223. package/dist/tools/docx/validate.d.ts +0 -33
  224. package/dist/tools/docx/validate.js +0 -49
  225. package/dist/tools/docx/validators.d.ts +0 -13
  226. package/dist/tools/docx/validators.js +0 -40
  227. package/dist/tools/docx/write.d.ts +0 -17
  228. package/dist/tools/docx/write.js +0 -88
  229. package/dist/tools/docx/xml-view-test.d.ts +0 -1
  230. package/dist/tools/docx/xml-view-test.js +0 -63
  231. package/dist/tools/docx/xml-view.d.ts +0 -56
  232. package/dist/tools/docx/xml-view.js +0 -169
  233. package/dist/tools/docx/zip.d.ts +0 -21
  234. package/dist/tools/docx/zip.js +0 -35
  235. package/dist/tools/macos-control/ax-adapter.d.ts +0 -55
  236. package/dist/tools/macos-control/ax-adapter.js +0 -438
  237. package/dist/tools/macos-control/cdp-adapter.d.ts +0 -23
  238. package/dist/tools/macos-control/cdp-adapter.js +0 -402
  239. package/dist/tools/macos-control/orchestrator.d.ts +0 -77
  240. package/dist/tools/macos-control/orchestrator.js +0 -136
  241. package/dist/tools/macos-control/role-aliases.d.ts +0 -5
  242. package/dist/tools/macos-control/role-aliases.js +0 -34
  243. package/dist/tools/macos-control/types.d.ts +0 -129
  244. package/dist/tools/macos-control/types.js +0 -1
  245. package/dist/tools/pdf-processor.d.ts +0 -1
  246. package/dist/tools/pdf-processor.js +0 -3
  247. package/dist/tools/search.d.ts +0 -32
  248. package/dist/tools/search.js +0 -202
  249. package/dist/ui/file-preview/src/components/toolbar.d.ts +0 -6
  250. package/dist/ui/file-preview/src/components/toolbar.js +0 -75
  251. package/dist/ui/shared/host-lifecycle.d.ts +0 -16
  252. package/dist/ui/shared/host-lifecycle.js +0 -35
  253. package/dist/ui/shared/rpc-client.d.ts +0 -14
  254. package/dist/ui/shared/rpc-client.js +0 -72
  255. package/dist/ui/shared/theme-adaptation.d.ts +0 -10
  256. package/dist/ui/shared/theme-adaptation.js +0 -118
  257. package/dist/ui/shared/tool-header.d.ts +0 -9
  258. package/dist/ui/shared/tool-header.js +0 -25
  259. package/dist/utils/crash-logger.d.ts +0 -18
  260. package/dist/utils/crash-logger.js +0 -44
  261. package/dist/utils/dedent.d.ts +0 -8
  262. package/dist/utils/dedent.js +0 -38
  263. /package/dist/{http-server-auto-tunnel.d.ts → ui/file-preview/src/model.js} +0 -0
@@ -0,0 +1,98 @@
1
+ import { formatJsonIfPossible, inferLanguageFromPath, renderCodeViewer } from './components/code-viewer.js';
2
+ import { escapeHtml } from './components/highlighting.js';
3
+ import { renderHtmlPreview } from './components/html-renderer.js';
4
+ import { renderDirectoryBody } from './directory-controller.js';
5
+ import { stripReadStatusLine } from './document-workspace.js';
6
+ import { isAllowedImageMimeType, normalizeImageMimeType } from './image-preview.js';
7
+ import { isLikelyUrl } from './payload-utils.js';
8
+ function renderRawFallback(source) {
9
+ return `<pre class="code-viewer"><code class="hljs language-text">${escapeHtml(source)}</code></pre>`;
10
+ }
11
+ function renderImageBody(payload) {
12
+ const mimeType = normalizeImageMimeType(payload.mimeType);
13
+ if (!isAllowedImageMimeType(mimeType)) {
14
+ return {
15
+ notice: 'Preview is unavailable for this image format.',
16
+ html: '<div class="panel-content source-content"></div>',
17
+ };
18
+ }
19
+ if (!payload.imageData || payload.imageData.trim().length === 0) {
20
+ return {
21
+ notice: 'Preview is unavailable because image data is missing.',
22
+ html: '<div class="panel-content source-content"></div>',
23
+ };
24
+ }
25
+ const src = `data:${mimeType};base64,${payload.imageData}`;
26
+ return {
27
+ html: `<div class="panel-content image-content"><div class="image-preview"><img src="${escapeHtml(src)}" alt="${escapeHtml(payload.fileName)}" loading="eager" decoding="async"></div></div>`,
28
+ };
29
+ }
30
+ function buildPreviewCapabilities(payload, canCopy) {
31
+ return {
32
+ supportsPreview: true,
33
+ canCopy,
34
+ canOpenInFolder: !isLikelyUrl(payload.filePath),
35
+ };
36
+ }
37
+ const handlerRegistry = {
38
+ directory: {
39
+ getCapabilities: (payload) => buildPreviewCapabilities(payload, false),
40
+ renderBody: ({ payload }) => renderDirectoryBody(stripReadStatusLine(payload.content), payload.filePath),
41
+ },
42
+ html: {
43
+ getCapabilities: (payload) => buildPreviewCapabilities(payload, true),
44
+ renderBody: ({ payload, htmlMode }) => renderHtmlPreview(stripReadStatusLine(payload.content), htmlMode),
45
+ },
46
+ image: {
47
+ getCapabilities: (payload) => buildPreviewCapabilities(payload, false),
48
+ renderBody: ({ payload }) => renderImageBody(payload),
49
+ },
50
+ markdown: {
51
+ getCapabilities: (payload) => buildPreviewCapabilities(payload, false),
52
+ renderBody: ({ payload, markdownController }) => {
53
+ try {
54
+ return markdownController.buildBody(payload);
55
+ }
56
+ catch {
57
+ return {
58
+ notice: 'Markdown renderer failed. Showing raw source instead.',
59
+ html: `<div class="panel-content source-content">${renderRawFallback(stripReadStatusLine(payload.content))}</div>`,
60
+ };
61
+ }
62
+ },
63
+ },
64
+ text: {
65
+ getCapabilities: (payload) => buildPreviewCapabilities(payload, true),
66
+ renderBody: ({ payload, startLine }) => {
67
+ const cleanedContent = stripReadStatusLine(payload.content);
68
+ const detectedLanguage = inferLanguageFromPath(payload.filePath);
69
+ const formatted = formatJsonIfPossible(cleanedContent, payload.filePath);
70
+ return {
71
+ notice: formatted.notice,
72
+ html: `<div class="panel-content source-content">${renderCodeViewer(formatted.content, detectedLanguage, startLine)}</div>`,
73
+ };
74
+ },
75
+ },
76
+ unsupported: {
77
+ getCapabilities: () => ({
78
+ supportsPreview: false,
79
+ canCopy: false,
80
+ canOpenInFolder: true,
81
+ }),
82
+ renderBody: () => ({
83
+ notice: 'Preview is not available for this file type.',
84
+ html: '<div class="panel-content source-content"></div>',
85
+ }),
86
+ },
87
+ };
88
+ export function getFileTypeCapabilities(payload) {
89
+ return handlerRegistry[payload.fileType]?.getCapabilities(payload) ?? {
90
+ supportsPreview: false,
91
+ canCopy: false,
92
+ canOpenInFolder: !isLikelyUrl(payload.filePath),
93
+ };
94
+ }
95
+ export function renderPayloadBody(options) {
96
+ const handler = handlerRegistry[options.payload.fileType] ?? handlerRegistry.text;
97
+ return handler.renderBody(options);
98
+ }
@@ -0,0 +1,19 @@
1
+ export declare function shellQuote(value: string): string;
2
+ export declare function encodePowerShellCommand(script: string): string;
3
+ export declare function buildOpenInFolderCommand(filePath: string, isLikelyUrl: (filePath: string) => boolean): string | undefined;
4
+ export declare function buildOpenInEditorCommand(filePath: string, isLikelyUrl: (filePath: string) => boolean, editorAppCache: Map<string, {
5
+ appName: string;
6
+ appPath?: string;
7
+ }>): string | undefined;
8
+ export declare function detectDefaultMarkdownEditor(options: {
9
+ filePath: string;
10
+ editorAppCache: Map<string, {
11
+ appName: string;
12
+ appPath?: string;
13
+ }>;
14
+ editorAppPending: Set<string>;
15
+ callTool?: (name: string, args: Record<string, unknown>) => Promise<unknown | undefined>;
16
+ extractToolText: (value: unknown) => string | undefined;
17
+ onDetected?: () => void;
18
+ }): Promise<void>;
19
+ export declare function renderMarkdownEditorAppIcon(): string;
@@ -0,0 +1,94 @@
1
+ import { getParentDirectory } from '../path-utils.js';
2
+ export function shellQuote(value) {
3
+ return `'${value.replace(/'/g, `'\\''`)}'`;
4
+ }
5
+ export function encodePowerShellCommand(script) {
6
+ const utf16leBytes = [];
7
+ for (let index = 0; index < script.length; index += 1) {
8
+ const codeUnit = script.charCodeAt(index);
9
+ utf16leBytes.push(codeUnit & 0xff, codeUnit >> 8);
10
+ }
11
+ let binary = '';
12
+ for (const byte of utf16leBytes) {
13
+ binary += String.fromCharCode(byte);
14
+ }
15
+ return btoa(binary);
16
+ }
17
+ export function buildOpenInFolderCommand(filePath, isLikelyUrl) {
18
+ const trimmedPath = filePath.trim();
19
+ if (!trimmedPath || isLikelyUrl(trimmedPath)) {
20
+ return undefined;
21
+ }
22
+ const userAgent = navigator.userAgent.toLowerCase();
23
+ if (userAgent.includes('win')) {
24
+ const escapedForPowerShell = trimmedPath.replace(/'/g, "''");
25
+ const script = `Start-Process -FilePath explorer.exe -ArgumentList @('/select,','${escapedForPowerShell}')`;
26
+ return `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encodePowerShellCommand(script)}`;
27
+ }
28
+ if (userAgent.includes('mac')) {
29
+ return `open -R ${shellQuote(trimmedPath)}`;
30
+ }
31
+ return `xdg-open ${shellQuote(getParentDirectory(trimmedPath))}`;
32
+ }
33
+ export function buildOpenInEditorCommand(filePath, isLikelyUrl, editorAppCache) {
34
+ const trimmedPath = filePath.trim();
35
+ if (!trimmedPath || isLikelyUrl(trimmedPath)) {
36
+ return undefined;
37
+ }
38
+ const cachedApp = editorAppCache.get(trimmedPath);
39
+ if (cachedApp?.appPath && navigator.userAgent.toLowerCase().includes('mac')) {
40
+ return `open -a ${shellQuote(cachedApp.appPath)} ${shellQuote(trimmedPath)}`;
41
+ }
42
+ const userAgent = navigator.userAgent.toLowerCase();
43
+ if (userAgent.includes('win')) {
44
+ const escapedForPowerShell = trimmedPath.replace(/'/g, "''");
45
+ const script = `Start-Process -FilePath '${escapedForPowerShell}'`;
46
+ return `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encodePowerShellCommand(script)}`;
47
+ }
48
+ if (userAgent.includes('mac')) {
49
+ return `open ${shellQuote(trimmedPath)}`;
50
+ }
51
+ return `xdg-open ${shellQuote(trimmedPath)}`;
52
+ }
53
+ export async function detectDefaultMarkdownEditor(options) {
54
+ const trimmedPath = options.filePath.trim();
55
+ if (!trimmedPath || options.editorAppCache.has(trimmedPath) || options.editorAppPending.has(trimmedPath)) {
56
+ return;
57
+ }
58
+ const userAgent = navigator.userAgent.toLowerCase();
59
+ if (!userAgent.includes('mac')) {
60
+ return;
61
+ }
62
+ options.editorAppPending.add(trimmedPath);
63
+ try {
64
+ const detectCommand = `osascript -e ${shellQuote(`set appAlias to default application of (info for POSIX file "${trimmedPath.replace(/"/g, '\\"')}")
65
+ return (name of (info for appAlias)) & linefeed & POSIX path of appAlias`)}`;
66
+ const detectResult = await options.callTool?.('start_process', {
67
+ command: detectCommand,
68
+ timeout_ms: 12000,
69
+ });
70
+ const text = options.extractToolText(detectResult) ?? '';
71
+ if (!text || text.toLowerCase().includes('error') || text.toLowerCase().includes('execution')) {
72
+ return;
73
+ }
74
+ const lines = text.split('\n').map((line) => line.trim()).filter(Boolean);
75
+ const appName = lines[lines.length - 2]?.replace(/\.app$/i, '') ?? '';
76
+ const appPath = lines[lines.length - 1] ?? '';
77
+ if (appName && appPath.startsWith('/')) {
78
+ options.editorAppCache.set(trimmedPath, {
79
+ appName,
80
+ appPath,
81
+ });
82
+ options.onDetected?.();
83
+ }
84
+ }
85
+ catch {
86
+ // Fall back to generic editor label.
87
+ }
88
+ finally {
89
+ options.editorAppPending.delete(trimmedPath);
90
+ }
91
+ }
92
+ export function renderMarkdownEditorAppIcon() {
93
+ return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 1 1 3 3L7 19l-4 1 1-4Z"/></svg>';
94
+ }
@@ -0,0 +1,9 @@
1
+ import type { RenderPayload } from '../model.js';
2
+ export declare function attachSelectionContext(options: {
3
+ payload: RenderPayload;
4
+ isMarkdownEditing: boolean;
5
+ updateContext?: (text: string) => void;
6
+ trackUiEvent?: (event: string, params?: Record<string, unknown>) => void;
7
+ getFileExtensionForAnalytics: (filePath: string) => string;
8
+ previousAbortController: AbortController | null;
9
+ }): AbortController | null;
@@ -0,0 +1,106 @@
1
+ export function attachSelectionContext(options) {
2
+ if (options.isMarkdownEditing) {
3
+ if (options.previousAbortController) {
4
+ options.previousAbortController.abort();
5
+ }
6
+ return null;
7
+ }
8
+ const contentWrapper = document.querySelector('.panel-content-wrapper');
9
+ if (!contentWrapper) {
10
+ return options.previousAbortController;
11
+ }
12
+ const wrapper = contentWrapper;
13
+ options.previousAbortController?.abort();
14
+ const abortController = new AbortController();
15
+ let hintEl = null;
16
+ let lastSelectedText = '';
17
+ let hideTimer = null;
18
+ function positionHint(selection) {
19
+ if (!hintEl)
20
+ return;
21
+ const range = selection.getRangeAt(0);
22
+ const rect = range.getBoundingClientRect();
23
+ const wrapperRect = wrapper.getBoundingClientRect();
24
+ let left = rect.left + rect.width / 2 - wrapperRect.left;
25
+ let top = rect.top - wrapperRect.top + wrapper.scrollTop - 32;
26
+ const hintWidth = hintEl.offsetWidth || 200;
27
+ left = Math.max(8, Math.min(left - hintWidth / 2, wrapper.clientWidth - hintWidth - 8));
28
+ top = Math.max(4, top);
29
+ hintEl.style.left = `${left}px`;
30
+ hintEl.style.top = `${top}px`;
31
+ }
32
+ function showHint(selection) {
33
+ if (hideTimer) {
34
+ clearTimeout(hideTimer);
35
+ hideTimer = null;
36
+ }
37
+ if (!hintEl) {
38
+ hintEl = document.createElement('div');
39
+ hintEl.className = 'selection-hint';
40
+ hintEl.textContent = 'AI can see your selection';
41
+ wrapper.appendChild(hintEl);
42
+ }
43
+ hintEl.classList.add('visible');
44
+ positionHint(selection);
45
+ }
46
+ function hideHint() {
47
+ if (!hintEl)
48
+ return;
49
+ hintEl.classList.remove('visible');
50
+ hideTimer = setTimeout(() => {
51
+ hintEl?.remove();
52
+ hintEl = null;
53
+ }, 200);
54
+ }
55
+ function getLineInfo(selection) {
56
+ const anchorRow = selection.anchorNode?.parentElement?.closest('.code-line');
57
+ const focusRow = selection.focusNode?.parentElement?.closest('.code-line');
58
+ if (anchorRow && focusRow) {
59
+ const anchorLine = parseInt(anchorRow.dataset.line ?? '', 10);
60
+ const focusLine = parseInt(focusRow.dataset.line ?? '', 10);
61
+ if (!isNaN(anchorLine) && !isNaN(focusLine)) {
62
+ const low = Math.min(anchorLine, focusLine);
63
+ const high = Math.max(anchorLine, focusLine);
64
+ return low === high ? `line ${low}` : `lines ${low}–${high}`;
65
+ }
66
+ }
67
+ return '';
68
+ }
69
+ document.addEventListener('selectionchange', () => {
70
+ const selection = document.getSelection();
71
+ if (!selection || selection.isCollapsed) {
72
+ if (lastSelectedText) {
73
+ lastSelectedText = '';
74
+ options.updateContext?.('');
75
+ hideHint();
76
+ }
77
+ return;
78
+ }
79
+ const text = selection.toString().trim();
80
+ if (!text || text === lastSelectedText) {
81
+ return;
82
+ }
83
+ const anchorInContent = wrapper.contains(selection.anchorNode);
84
+ const focusInContent = wrapper.contains(selection.focusNode);
85
+ if (!anchorInContent && !focusInContent) {
86
+ if (lastSelectedText) {
87
+ lastSelectedText = '';
88
+ options.updateContext?.('');
89
+ hideHint();
90
+ }
91
+ return;
92
+ }
93
+ lastSelectedText = text;
94
+ const lineInfo = getLineInfo(selection);
95
+ const locationPart = lineInfo ? ` (${lineInfo})` : '';
96
+ const context = `User selected text from file ${options.payload.filePath}${locationPart}:\n\`\`\`\n${text}\n\`\`\``;
97
+ options.updateContext?.(context);
98
+ showHint(selection);
99
+ options.trackUiEvent?.('text_selected', {
100
+ file_type: options.payload.fileType,
101
+ file_extension: options.getFileExtensionForAnalytics(options.payload.filePath),
102
+ char_count: text.length,
103
+ });
104
+ }, { signal: abortController.signal });
105
+ return abortController;
106
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The "file changed on disk" conflict resolver.
3
+ *
4
+ * Shown when saveDocument detected that disk differs from what the editor
5
+ * thought it had. The editor has already re-synced its sourceContent to the
6
+ * fresh disk content with keepDraft: true — so the dialog's two actions map
7
+ * onto concrete state transitions:
8
+ *
9
+ * "Use disk version" — replace the draft with disk content
10
+ * (syncStateFromContent without keepDraft).
11
+ * Destroys unsaved edits.
12
+ *
13
+ * "Save my changes" — close the dialog and re-run saveDocument.
14
+ * computeEditBlocks will now diff against the fresh
15
+ * disk content, so non-overlapping edits merge in
16
+ * and overlapping edits win over disk for the
17
+ * lines the user actually touched.
18
+ *
19
+ * The dialog is modal (dimmed backdrop, keyboard-trapped, click-outside does
20
+ * not dismiss). Escape and the ✕ button both close it without taking either
21
+ * action — equivalent to "I'll deal with this later"; the save button stays
22
+ * dirty so the user can retry or keep editing.
23
+ */
24
+ export interface OpenConflictDialogOptions {
25
+ fileName: string;
26
+ onUseDiskVersion: () => void;
27
+ onSaveMyChanges: () => void;
28
+ onCancel?: () => void;
29
+ }
30
+ export interface ConflictDialogController {
31
+ open: (options: OpenConflictDialogOptions) => void;
32
+ close: () => void;
33
+ isOpen: () => boolean;
34
+ }
35
+ export declare function renderConflictDialogMarkup(): string;
36
+ interface CreateConflictDialogOptions {
37
+ container: ParentNode;
38
+ }
39
+ export declare function createConflictDialogController(options: CreateConflictDialogOptions): ConflictDialogController;
40
+ export {};
@@ -0,0 +1,163 @@
1
+ /**
2
+ * The "file changed on disk" conflict resolver.
3
+ *
4
+ * Shown when saveDocument detected that disk differs from what the editor
5
+ * thought it had. The editor has already re-synced its sourceContent to the
6
+ * fresh disk content with keepDraft: true — so the dialog's two actions map
7
+ * onto concrete state transitions:
8
+ *
9
+ * "Use disk version" — replace the draft with disk content
10
+ * (syncStateFromContent without keepDraft).
11
+ * Destroys unsaved edits.
12
+ *
13
+ * "Save my changes" — close the dialog and re-run saveDocument.
14
+ * computeEditBlocks will now diff against the fresh
15
+ * disk content, so non-overlapping edits merge in
16
+ * and overlapping edits win over disk for the
17
+ * lines the user actually touched.
18
+ *
19
+ * The dialog is modal (dimmed backdrop, keyboard-trapped, click-outside does
20
+ * not dismiss). Escape and the ✕ button both close it without taking either
21
+ * action — equivalent to "I'll deal with this later"; the save button stays
22
+ * dirty so the user can retry or keep editing.
23
+ */
24
+ export function renderConflictDialogMarkup() {
25
+ return `
26
+ <div class="md-conflict-modal" id="md-conflict-modal" role="dialog" aria-modal="true" aria-labelledby="md-conflict-title" aria-describedby="md-conflict-body" hidden>
27
+ <div class="md-conflict-card">
28
+ <header class="md-conflict-header">
29
+ <h3 id="md-conflict-title">⚠ This file changed on disk</h3>
30
+ <button type="button" class="md-conflict-close" id="md-conflict-close" aria-label="Close">
31
+ <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
32
+ <path d="M7 7l10 10M17 7L7 17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
33
+ </svg>
34
+ </button>
35
+ </header>
36
+ <div class="md-conflict-body" id="md-conflict-body">
37
+ <p>
38
+ Something else modified
39
+ <strong class="md-conflict-filename" id="md-conflict-filename"></strong>
40
+ while you were editing. Your unsaved edits are preserved.
41
+ </p>
42
+ <p>If you save now:</p>
43
+ <ul>
44
+ <li>Changes you made to lines the external edit didn't touch will be saved alongside the external changes.</li>
45
+ <li>Changes you made to lines that also changed externally will overwrite the external version on those lines.</li>
46
+ </ul>
47
+ </div>
48
+ <footer class="md-conflict-footer">
49
+ <button type="button" class="md-conflict-btn md-conflict-btn--secondary" id="md-conflict-use-disk">
50
+ Use disk version
51
+ </button>
52
+ <button type="button" class="md-conflict-btn md-conflict-btn--primary" id="md-conflict-save-mine">
53
+ Save my changes
54
+ </button>
55
+ </footer>
56
+ </div>
57
+ </div>
58
+ `;
59
+ }
60
+ export function createConflictDialogController(options) {
61
+ const { container } = options;
62
+ const modal = container.querySelector('#md-conflict-modal');
63
+ const filenameEl = container.querySelector('#md-conflict-filename');
64
+ const useDiskBtn = container.querySelector('#md-conflict-use-disk');
65
+ const saveMineBtn = container.querySelector('#md-conflict-save-mine');
66
+ const closeBtn = container.querySelector('#md-conflict-close');
67
+ let currentOptions = null;
68
+ let previousActiveElement = null;
69
+ const close = () => {
70
+ if (!modal || modal.hidden) {
71
+ return;
72
+ }
73
+ modal.hidden = true;
74
+ document.removeEventListener('keydown', handleKeyDown, true);
75
+ modal.removeEventListener('click', handleBackdropClick);
76
+ const cancel = currentOptions?.onCancel;
77
+ currentOptions = null;
78
+ // Restore focus to whatever the user was on before the dialog opened.
79
+ if (previousActiveElement && document.contains(previousActiveElement)) {
80
+ try {
81
+ previousActiveElement.focus();
82
+ }
83
+ catch {
84
+ /* focus can throw on removed nodes — ignore */
85
+ }
86
+ }
87
+ previousActiveElement = null;
88
+ cancel?.();
89
+ };
90
+ const handleKeyDown = (event) => {
91
+ if (!modal || modal.hidden) {
92
+ return;
93
+ }
94
+ if (event.key === 'Escape') {
95
+ event.stopPropagation();
96
+ event.preventDefault();
97
+ close();
98
+ return;
99
+ }
100
+ if (event.key === 'Tab') {
101
+ // Minimal focus trap between the three buttons.
102
+ const focusable = [useDiskBtn, saveMineBtn, closeBtn].filter((el) => !!el);
103
+ if (focusable.length === 0)
104
+ return;
105
+ const active = document.activeElement;
106
+ const currentIndex = active ? focusable.indexOf(active) : -1;
107
+ const direction = event.shiftKey ? -1 : 1;
108
+ const nextIndex = currentIndex === -1
109
+ ? (direction === 1 ? 0 : focusable.length - 1)
110
+ : (currentIndex + direction + focusable.length) % focusable.length;
111
+ event.preventDefault();
112
+ focusable[nextIndex].focus();
113
+ }
114
+ };
115
+ const handleBackdropClick = (event) => {
116
+ // Click on the dimmed backdrop (the modal element itself, not the card)
117
+ // is deliberately not a dismiss — the user must make a choice or hit ✕.
118
+ if (event.target === modal) {
119
+ event.stopPropagation();
120
+ }
121
+ };
122
+ const handleUseDisk = () => {
123
+ const cb = currentOptions?.onUseDiskVersion;
124
+ // Clear currentOptions first so close() doesn't also fire onCancel.
125
+ currentOptions = null;
126
+ close();
127
+ cb?.();
128
+ };
129
+ const handleSaveMine = () => {
130
+ const cb = currentOptions?.onSaveMyChanges;
131
+ currentOptions = null;
132
+ close();
133
+ cb?.();
134
+ };
135
+ useDiskBtn?.addEventListener('click', handleUseDisk);
136
+ saveMineBtn?.addEventListener('click', handleSaveMine);
137
+ closeBtn?.addEventListener('click', close);
138
+ return {
139
+ open: (options) => {
140
+ if (!modal) {
141
+ // No-op if the markup wasn't injected — fall back to cancel callback
142
+ // so the editor can still notify the user via the inline path.
143
+ options.onCancel?.();
144
+ return;
145
+ }
146
+ currentOptions = options;
147
+ if (filenameEl) {
148
+ filenameEl.textContent = options.fileName;
149
+ }
150
+ previousActiveElement = document.activeElement ?? null;
151
+ modal.hidden = false;
152
+ document.addEventListener('keydown', handleKeyDown, true);
153
+ modal.addEventListener('click', handleBackdropClick);
154
+ // Default focus goes to the safer action ("Save my changes" is the
155
+ // non-destructive intent — it doesn't discard the user's draft).
156
+ window.requestAnimationFrame(() => {
157
+ saveMineBtn?.focus();
158
+ });
159
+ },
160
+ close,
161
+ isOpen: () => !!modal && !modal.hidden,
162
+ };
163
+ }
@@ -0,0 +1,44 @@
1
+ import type { MarkdownWorkspaceState, RenderBodyResult, RenderPayload } from '../model.js';
2
+ import { type MarkdownEditRange, type MarkdownEditorView } from './editor.js';
3
+ import type { OpenConflictDialogOptions } from './conflict-dialog.js';
4
+ export interface MarkdownControllerDependencies {
5
+ callTool?: (name: string, args: Record<string, unknown>) => Promise<unknown | undefined>;
6
+ openExternalLink?: (url: string) => Promise<boolean | undefined>;
7
+ requestDisplayMode?: (mode: 'inline' | 'fullscreen') => Promise<string | null | undefined>;
8
+ getAvailableDisplayModes: () => string[];
9
+ getCurrentDisplayMode: () => string | null;
10
+ getCurrentPayload: () => RenderPayload | undefined;
11
+ setExpanded: (expanded: boolean) => void;
12
+ syncPayload?: (payload?: RenderPayload) => void;
13
+ storePayloadOverride: (payload: RenderPayload) => void;
14
+ rerender: () => void;
15
+ updateSaveStatus: (label: string, statusClass: string) => void;
16
+ trackUiEvent?: (event: string, params?: Record<string, unknown>) => void;
17
+ showConflictDialog?: (options: OpenConflictDialogOptions) => void;
18
+ }
19
+ interface EditBlock {
20
+ old_string: string;
21
+ new_string: string;
22
+ }
23
+ export declare function computeEditBlocks(oldText: string, newText: string, changedRanges?: MarkdownEditRange[]): EditBlock[];
24
+ export declare function createMarkdownController(dependencies: MarkdownControllerDependencies): {
25
+ attachHandlers: (payload: RenderPayload) => void;
26
+ buildBody: (payload: RenderPayload) => RenderBodyResult;
27
+ clear: () => void;
28
+ disposeHandles: () => void;
29
+ ensureCompletePayload: (payload: RenderPayload) => Promise<RenderPayload>;
30
+ getCopyText: (payload: RenderPayload) => string | null;
31
+ getState: (payload: RenderPayload) => MarkdownWorkspaceState;
32
+ handleInlineExitFromFullscreen: (originalPayload?: RenderPayload) => Promise<RenderPayload | undefined>;
33
+ isUndoAvailable: (state: MarkdownWorkspaceState) => boolean;
34
+ readCompletePayload: (filePath: string) => Promise<RenderPayload | null>;
35
+ readPayload: (filePath: string, length?: number, offset?: number) => Promise<RenderPayload | null>;
36
+ readPayloadContent: (payload: RenderPayload) => string;
37
+ refreshFromDisk: (payload: RenderPayload) => Promise<void>;
38
+ requestEditMode: (payload: RenderPayload) => Promise<void>;
39
+ requestFullscreen: () => Promise<boolean>;
40
+ saveDocument: () => Promise<void>;
41
+ setEditorView: (payload: RenderPayload, view: MarkdownEditorView) => void;
42
+ };
43
+ export type MarkdownController = ReturnType<typeof createMarkdownController>;
44
+ export {};