@wonderwhy-er/desktop-commander 0.2.35 → 0.2.36

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 (115) hide show
  1. package/README.md +2 -0
  2. package/dist/handlers/filesystem-handlers.js +58 -11
  3. package/dist/handlers/history-handlers.d.ts +7 -0
  4. package/dist/handlers/history-handlers.js +33 -1
  5. package/dist/server.js +30 -4
  6. package/dist/tools/docx/builders/image.d.ts +14 -0
  7. package/dist/tools/docx/builders/image.js +84 -0
  8. package/dist/tools/docx/builders/index.d.ts +9 -3
  9. package/dist/tools/docx/builders/index.js +9 -3
  10. package/dist/tools/docx/builders/paragraph.d.ts +12 -0
  11. package/dist/tools/docx/builders/paragraph.js +29 -0
  12. package/dist/tools/docx/builders/table.d.ts +8 -0
  13. package/dist/tools/docx/builders/table.js +94 -0
  14. package/dist/tools/docx/builders/utils.d.ts +5 -0
  15. package/dist/tools/docx/builders/utils.js +18 -0
  16. package/dist/tools/docx/constants.d.ts +28 -32
  17. package/dist/tools/docx/constants.js +56 -52
  18. package/dist/tools/docx/create.d.ts +21 -0
  19. package/dist/tools/docx/create.js +386 -0
  20. package/dist/tools/docx/dom.d.ts +66 -0
  21. package/dist/tools/docx/dom.js +228 -0
  22. package/dist/tools/docx/index.d.ts +8 -12
  23. package/dist/tools/docx/index.js +8 -14
  24. package/dist/tools/docx/modify.d.ts +28 -0
  25. package/dist/tools/docx/modify.js +271 -0
  26. package/dist/tools/docx/ops/delete-paragraph-at-body-index.d.ts +11 -0
  27. package/dist/tools/docx/ops/delete-paragraph-at-body-index.js +23 -0
  28. package/dist/tools/docx/ops/header-replace-text-exact.d.ts +13 -0
  29. package/dist/tools/docx/ops/header-replace-text-exact.js +55 -0
  30. package/dist/tools/docx/ops/index.d.ts +17 -0
  31. package/dist/tools/docx/ops/index.js +67 -0
  32. package/dist/tools/docx/ops/insert-image-after-text.d.ts +24 -0
  33. package/dist/tools/docx/ops/insert-image-after-text.js +128 -0
  34. package/dist/tools/docx/ops/insert-paragraph-after-text.d.ts +12 -0
  35. package/dist/tools/docx/ops/insert-paragraph-after-text.js +74 -0
  36. package/dist/tools/docx/ops/insert-table-after-text.d.ts +19 -0
  37. package/dist/tools/docx/ops/insert-table-after-text.js +57 -0
  38. package/dist/tools/docx/ops/replace-hyperlink-url.d.ts +12 -0
  39. package/dist/tools/docx/ops/replace-hyperlink-url.js +37 -0
  40. package/dist/tools/docx/ops/replace-paragraph-at-body-index.d.ts +9 -0
  41. package/dist/tools/docx/ops/replace-paragraph-at-body-index.js +25 -0
  42. package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +9 -0
  43. package/dist/tools/docx/ops/replace-paragraph-text-exact.js +21 -0
  44. package/dist/tools/docx/ops/set-color-for-paragraph-exact.d.ts +8 -0
  45. package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +23 -0
  46. package/dist/tools/docx/ops/set-color-for-style.d.ts +9 -0
  47. package/dist/tools/docx/ops/set-color-for-style.js +27 -0
  48. package/dist/tools/docx/ops/set-paragraph-style-at-body-index.d.ts +8 -0
  49. package/dist/tools/docx/ops/set-paragraph-style-at-body-index.js +57 -0
  50. package/dist/tools/docx/ops/table-set-cell-text.d.ts +9 -0
  51. package/dist/tools/docx/ops/table-set-cell-text.js +72 -0
  52. package/dist/tools/docx/read.d.ts +27 -0
  53. package/dist/tools/docx/read.js +188 -0
  54. package/dist/tools/docx/relationships.d.ts +22 -0
  55. package/dist/tools/docx/relationships.js +76 -0
  56. package/dist/tools/docx/types.d.ts +174 -104
  57. package/dist/tools/docx/types.js +2 -5
  58. package/dist/tools/docx/validate.d.ts +33 -0
  59. package/dist/tools/docx/validate.js +49 -0
  60. package/dist/tools/docx/write.d.ts +17 -0
  61. package/dist/tools/docx/write.js +88 -0
  62. package/dist/tools/docx/zip.d.ts +21 -0
  63. package/dist/tools/docx/zip.js +35 -0
  64. package/dist/tools/schemas.d.ts +13 -0
  65. package/dist/tools/schemas.js +5 -0
  66. package/dist/types.d.ts +10 -0
  67. package/dist/ui/contracts.d.ts +14 -0
  68. package/dist/ui/contracts.js +18 -0
  69. package/dist/ui/file-preview/index.html +16 -0
  70. package/dist/ui/file-preview/preview-runtime.js +13977 -0
  71. package/dist/ui/file-preview/shared/preview-file-types.d.ts +5 -0
  72. package/dist/ui/file-preview/shared/preview-file-types.js +57 -0
  73. package/dist/ui/file-preview/src/app.d.ts +4 -0
  74. package/dist/ui/file-preview/src/app.js +800 -0
  75. package/dist/ui/file-preview/src/components/code-viewer.d.ts +6 -0
  76. package/dist/ui/file-preview/src/components/code-viewer.js +73 -0
  77. package/dist/ui/file-preview/src/components/highlighting.d.ts +2 -0
  78. package/dist/ui/file-preview/src/components/highlighting.js +54 -0
  79. package/dist/ui/file-preview/src/components/html-renderer.d.ts +9 -0
  80. package/dist/ui/file-preview/src/components/html-renderer.js +63 -0
  81. package/dist/ui/file-preview/src/components/markdown-renderer.d.ts +1 -0
  82. package/dist/ui/file-preview/src/components/markdown-renderer.js +21 -0
  83. package/dist/ui/file-preview/src/components/toolbar.d.ts +6 -0
  84. package/dist/ui/file-preview/src/components/toolbar.js +75 -0
  85. package/dist/ui/file-preview/src/image-preview.d.ts +3 -0
  86. package/dist/ui/file-preview/src/image-preview.js +21 -0
  87. package/dist/ui/file-preview/src/main.d.ts +1 -0
  88. package/dist/ui/file-preview/src/main.js +5 -0
  89. package/dist/ui/file-preview/src/types.d.ts +1 -0
  90. package/dist/ui/file-preview/src/types.js +1 -0
  91. package/dist/ui/file-preview/styles.css +764 -0
  92. package/dist/ui/resources.d.ts +21 -0
  93. package/dist/ui/resources.js +72 -0
  94. package/dist/ui/shared/escape-html.d.ts +4 -0
  95. package/dist/ui/shared/escape-html.js +11 -0
  96. package/dist/ui/shared/host-lifecycle.d.ts +16 -0
  97. package/dist/ui/shared/host-lifecycle.js +35 -0
  98. package/dist/ui/shared/rpc-client.d.ts +14 -0
  99. package/dist/ui/shared/rpc-client.js +72 -0
  100. package/dist/ui/shared/theme-adaptation.d.ts +10 -0
  101. package/dist/ui/shared/theme-adaptation.js +118 -0
  102. package/dist/ui/shared/tool-header.d.ts +9 -0
  103. package/dist/ui/shared/tool-header.js +25 -0
  104. package/dist/ui/shared/tool-shell.d.ts +16 -0
  105. package/dist/ui/shared/tool-shell.js +65 -0
  106. package/dist/ui/shared/widget-state.d.ts +28 -0
  107. package/dist/ui/shared/widget-state.js +60 -0
  108. package/dist/utils/capture.d.ts +1 -0
  109. package/dist/utils/capture.js +6 -0
  110. package/dist/utils/files/docx.d.ts +8 -15
  111. package/dist/utils/files/docx.js +76 -176
  112. package/dist/utils/files/text.js +9 -1
  113. package/dist/version.d.ts +1 -1
  114. package/dist/version.js +1 -1
  115. package/package.json +5 -2
@@ -0,0 +1,21 @@
1
+ export declare const FILE_PREVIEW_RESOURCE: {
2
+ uri: string;
3
+ name: string;
4
+ description: string;
5
+ mimeType: string;
6
+ };
7
+ export declare function getFilePreviewResourceText(): Promise<string>;
8
+ export declare function listUiResources(): {
9
+ uri: string;
10
+ name: string;
11
+ description: string;
12
+ mimeType: string;
13
+ }[];
14
+ export declare function readUiResource(uri: string): Promise<{
15
+ contents: {
16
+ _meta?: Record<string, unknown> | undefined;
17
+ uri: string;
18
+ mimeType: string;
19
+ text: string;
20
+ }[];
21
+ } | null>;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Server-side UI resource loading helpers for MCP responses. It resolves packaged UI assets, reads files safely, and exposes structured resource payloads to clients.
3
+ */
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { FILE_PREVIEW_RESOURCE_URI } from './contracts.js';
8
+ const UI_RESOURCE_MIME_TYPE = 'text/html;profile=mcp-app';
9
+ export const FILE_PREVIEW_RESOURCE = {
10
+ uri: FILE_PREVIEW_RESOURCE_URI,
11
+ name: 'Desktop Commander File Preview',
12
+ description: 'Markdown-first preview surface for read_file structured content.',
13
+ mimeType: UI_RESOURCE_MIME_TYPE
14
+ };
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+ const DIST_FILE_PREVIEW_DIR = path.resolve(__dirname, 'file-preview');
18
+ function replaceOrThrow(source, pattern, replacement, context) {
19
+ if (!pattern.test(source)) {
20
+ throw new Error(`UI template is missing expected ${context}`);
21
+ }
22
+ return source.replace(pattern, () => replacement);
23
+ }
24
+ function inlineTemplateAssets(templateHtml, css, runtime) {
25
+ const safeCss = css.replace(/<\/style/gi, '<\\/style');
26
+ const safeRuntime = runtime.replace(/<\/script/gi, '<\\/script');
27
+ const cssInlined = replaceOrThrow(templateHtml, /<link[^>]*href=["']\.\/styles\.css["'][^>]*>/i, `<style>${safeCss}</style>`, 'styles.css link tag');
28
+ const runtimeInlined = replaceOrThrow(cssInlined, /<script[^>]*src=["']\.\/[^"']+["'][^>]*>\s*<\/script>/i, `<script>${safeRuntime}</script>`, 'runtime script tag');
29
+ if (/href=["']\.\/styles\.css["']/i.test(runtimeInlined) ||
30
+ /<script[^>]*src=["']\.\/[^"']+["']/i.test(runtimeInlined)) {
31
+ throw new Error('UI template still contains external static asset references after inlining');
32
+ }
33
+ return runtimeInlined;
34
+ }
35
+ async function readInlinedResourceHtml(distDir, runtimeFileName) {
36
+ const [templateHtml, css, runtime] = await Promise.all([
37
+ fs.readFile(path.join(distDir, 'index.html'), 'utf8'),
38
+ fs.readFile(path.join(distDir, 'styles.css'), 'utf8'),
39
+ fs.readFile(path.join(distDir, runtimeFileName), 'utf8')
40
+ ]);
41
+ return inlineTemplateAssets(templateHtml, css, runtime);
42
+ }
43
+ export async function getFilePreviewResourceText() {
44
+ return readInlinedResourceHtml(DIST_FILE_PREVIEW_DIR, 'preview-runtime.js');
45
+ }
46
+ const READABLE_UI_RESOURCES = {
47
+ [FILE_PREVIEW_RESOURCE_URI]: {
48
+ mimeType: FILE_PREVIEW_RESOURCE.mimeType,
49
+ getText: getFilePreviewResourceText
50
+ }
51
+ };
52
+ export function listUiResources() {
53
+ return [FILE_PREVIEW_RESOURCE];
54
+ }
55
+ export async function readUiResource(uri) {
56
+ const resource = READABLE_UI_RESOURCES[uri];
57
+ if (!resource) {
58
+ return null;
59
+ }
60
+ const resourceText = await resource.getText();
61
+ const resourceMeta = resource.getMeta?.();
62
+ return {
63
+ contents: [
64
+ {
65
+ uri,
66
+ mimeType: resource.mimeType,
67
+ text: resourceText,
68
+ ...(resourceMeta ? { _meta: resourceMeta } : {})
69
+ }
70
+ ]
71
+ };
72
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Shared HTML escaping helper for UI string interpolation.
3
+ */
4
+ export declare function escapeHtml(value: string): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared HTML escaping helper for UI string interpolation.
3
+ */
4
+ export function escapeHtml(value) {
5
+ return value
6
+ .replace(/&/g, '&amp;')
7
+ .replace(/</g, '&lt;')
8
+ .replace(/>/g, '&gt;')
9
+ .replace(/"/g, '&quot;')
10
+ .replace(/'/g, '&#39;');
11
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Cross-tool lifecycle helpers for host readiness, teardown, and event subscriptions. It standardizes app lifecycle behavior across UI surfaces.
3
+ */
4
+ import type { RpcClient } from './rpc-client.js';
5
+ interface UiHostLifecycleOptions {
6
+ appName: string;
7
+ appVersion?: string;
8
+ getRootElement?: () => Element | null;
9
+ }
10
+ export interface UiHostLifecycle {
11
+ notifyRender: () => void;
12
+ observeResize: () => void;
13
+ initialize: () => void;
14
+ }
15
+ export declare function createUiHostLifecycle(rpcClient: RpcClient, options: UiHostLifecycleOptions): UiHostLifecycle;
16
+ export {};
@@ -0,0 +1,35 @@
1
+ export function createUiHostLifecycle(rpcClient, options) {
2
+ const { appName, appVersion = '1.0.0', getRootElement } = options;
3
+ const resolveRootElement = () => getRootElement?.() ?? (document.getElementById('app')?.firstElementChild ?? document.getElementById('app'));
4
+ const notifySizeChanged = () => {
5
+ const node = resolveRootElement();
6
+ const height = Math.max(28, Math.ceil(node?.getBoundingClientRect().height ?? 0));
7
+ rpcClient.notify('ui/notifications/size-changed', { height });
8
+ };
9
+ return {
10
+ notifyRender: () => {
11
+ notifySizeChanged();
12
+ setTimeout(notifySizeChanged, 80);
13
+ },
14
+ observeResize: () => {
15
+ if (!window.ResizeObserver) {
16
+ return;
17
+ }
18
+ const observer = new ResizeObserver(() => notifySizeChanged());
19
+ observer.observe(document.documentElement);
20
+ },
21
+ initialize: () => {
22
+ void rpcClient.request('ui/initialize', {
23
+ appInfo: { name: appName, version: appVersion },
24
+ appCapabilities: {},
25
+ protocolVersion: '2026-01-26',
26
+ }).then(() => {
27
+ rpcClient.notify('ui/notifications/initialized', {});
28
+ }).catch(() => {
29
+ // Initialization handshake failure should not break rendering.
30
+ // Still send initialized in case host is lenient.
31
+ rpcClient.notify('ui/notifications/initialized', {});
32
+ });
33
+ },
34
+ };
35
+ }
@@ -0,0 +1,14 @@
1
+ export interface RpcClient {
2
+ notify: (method: string, params: Record<string, unknown>) => void;
3
+ request: (method: string, params: Record<string, unknown>) => Promise<unknown>;
4
+ handleMessageEvent: (event: MessageEvent) => boolean;
5
+ dispose: () => void;
6
+ }
7
+ export interface RpcClientOptions {
8
+ targetWindow: Window;
9
+ targetOrigin?: string;
10
+ timeoutMs?: number;
11
+ isTrustedSource?: (source: MessageEvent['source'] | null) => boolean;
12
+ }
13
+ export declare function isTrustedParentMessageSource(source: MessageEvent['source'] | null, expectedSource: Window): boolean;
14
+ export declare function createWindowRpcClient(options: RpcClientOptions): RpcClient;
@@ -0,0 +1,72 @@
1
+ function isObject(value) {
2
+ return typeof value === 'object' && value !== null;
3
+ }
4
+ export function isTrustedParentMessageSource(source, expectedSource) {
5
+ return source === expectedSource;
6
+ }
7
+ export function createWindowRpcClient(options) {
8
+ const { targetWindow, targetOrigin = '*', timeoutMs = 15000, isTrustedSource = () => true, } = options;
9
+ let requestId = 1;
10
+ const pendingRequests = new Map();
11
+ const postMessage = (payload) => {
12
+ targetWindow.postMessage(payload, targetOrigin);
13
+ };
14
+ const notify = (method, params) => {
15
+ postMessage({
16
+ jsonrpc: '2.0',
17
+ method,
18
+ params,
19
+ });
20
+ };
21
+ const request = (method, params) => {
22
+ const id = requestId++;
23
+ postMessage({
24
+ jsonrpc: '2.0',
25
+ id,
26
+ method,
27
+ params,
28
+ });
29
+ return new Promise((resolve, reject) => {
30
+ const timer = setTimeout(() => {
31
+ pendingRequests.delete(id);
32
+ reject(new Error(`Request timed out for method ${method}`));
33
+ }, timeoutMs);
34
+ pendingRequests.set(id, { resolve, reject, timer });
35
+ });
36
+ };
37
+ const handleMessageEvent = (event) => {
38
+ if (!isTrustedSource(event.source)) {
39
+ return false;
40
+ }
41
+ if (!isObject(event.data) || typeof event.data.id !== 'number') {
42
+ return false;
43
+ }
44
+ const pending = pendingRequests.get(event.data.id);
45
+ if (!pending) {
46
+ return false;
47
+ }
48
+ clearTimeout(pending.timer);
49
+ pendingRequests.delete(event.data.id);
50
+ if (isObject(event.data.error)) {
51
+ const errorShape = event.data.error;
52
+ const message = typeof errorShape.message === 'string' ? errorShape.message : 'Unknown RPC error';
53
+ pending.reject(new Error(message));
54
+ return true;
55
+ }
56
+ pending.resolve(event.data.result);
57
+ return true;
58
+ };
59
+ const dispose = () => {
60
+ for (const [id, pending] of pendingRequests) {
61
+ clearTimeout(pending.timer);
62
+ pending.reject(new Error('RPC client disposed'));
63
+ pendingRequests.delete(id);
64
+ }
65
+ };
66
+ return {
67
+ notify,
68
+ request,
69
+ handleMessageEvent,
70
+ dispose,
71
+ };
72
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Theme synchronization utilities that adapt embedded UI styles to host light/dark context. It centralizes theme event handling and class/token updates.
3
+ */
4
+ type ThemeMode = 'light' | 'dark';
5
+ export declare function resolveThemeMode(value: unknown): ThemeMode | undefined;
6
+ export interface UiThemeAdapter {
7
+ applyFromData: (data: unknown) => boolean;
8
+ }
9
+ export declare function createUiThemeAdapter(root?: HTMLElement): UiThemeAdapter;
10
+ export {};
@@ -0,0 +1,118 @@
1
+ function isObject(value) {
2
+ return typeof value === 'object' && value !== null;
3
+ }
4
+ function normalizeThemeMode(value) {
5
+ if (typeof value !== 'string') {
6
+ return undefined;
7
+ }
8
+ const normalized = value.trim().toLowerCase();
9
+ if (normalized === 'dark' || normalized === 'night') {
10
+ return 'dark';
11
+ }
12
+ if (normalized === 'light' || normalized === 'day') {
13
+ return 'light';
14
+ }
15
+ return undefined;
16
+ }
17
+ function pickThemeCandidate(value) {
18
+ if (!isObject(value)) {
19
+ return [];
20
+ }
21
+ const candidates = [
22
+ value.theme,
23
+ value.colorScheme,
24
+ value.appearance,
25
+ value.mode,
26
+ ];
27
+ if (isObject(value.context)) {
28
+ candidates.push(value.context, value.context.theme, value.context.colorScheme);
29
+ }
30
+ if (isObject(value.params)) {
31
+ candidates.push(value.params, value.params.theme, value.params.colorScheme, value.params.context);
32
+ }
33
+ return candidates;
34
+ }
35
+ export function resolveThemeMode(value) {
36
+ const direct = normalizeThemeMode(value);
37
+ if (direct) {
38
+ return direct;
39
+ }
40
+ if (!isObject(value)) {
41
+ return undefined;
42
+ }
43
+ for (const candidate of pickThemeCandidate(value)) {
44
+ const resolved = resolveThemeMode(candidate);
45
+ if (resolved) {
46
+ return resolved;
47
+ }
48
+ }
49
+ return undefined;
50
+ }
51
+ function isSafeCssVariableName(name) {
52
+ return /^--[a-zA-Z0-9_-]+$/.test(name);
53
+ }
54
+ function extractCssVariableMap(value) {
55
+ if (!isObject(value)) {
56
+ return {};
57
+ }
58
+ const params = isObject(value.params) ? value.params : undefined;
59
+ const paramsContext = params && isObject(params.context) ? params.context : undefined;
60
+ const rawMapCandidates = [
61
+ value.cssVariables,
62
+ value.variables,
63
+ value.tokens,
64
+ isObject(value.theme) ? value.theme.cssVariables : undefined,
65
+ isObject(value.theme) ? value.theme.variables : undefined,
66
+ isObject(value.theme) ? value.theme.tokens : undefined,
67
+ isObject(value.context) ? value.context.cssVariables : undefined,
68
+ isObject(value.context) ? value.context.variables : undefined,
69
+ isObject(value.context) ? value.context.tokens : undefined,
70
+ params ? params.cssVariables : undefined,
71
+ params ? params.variables : undefined,
72
+ params ? params.tokens : undefined,
73
+ paramsContext ? paramsContext.cssVariables : undefined,
74
+ paramsContext ? paramsContext.variables : undefined,
75
+ paramsContext ? paramsContext.tokens : undefined,
76
+ ];
77
+ for (const candidate of rawMapCandidates) {
78
+ if (!isObject(candidate)) {
79
+ continue;
80
+ }
81
+ const next = {};
82
+ for (const [rawKey, rawValue] of Object.entries(candidate)) {
83
+ if (typeof rawValue !== 'string') {
84
+ continue;
85
+ }
86
+ const key = rawKey.startsWith('--') ? rawKey : `--${rawKey}`;
87
+ if (!isSafeCssVariableName(key)) {
88
+ continue;
89
+ }
90
+ next[key] = rawValue.trim();
91
+ }
92
+ return next;
93
+ }
94
+ return {};
95
+ }
96
+ function applyCssVariables(root, variableMap) {
97
+ for (const [name, value] of Object.entries(variableMap)) {
98
+ root.style.setProperty(name, value);
99
+ }
100
+ }
101
+ export function createUiThemeAdapter(root = document.documentElement) {
102
+ const applyFromData = (data) => {
103
+ const mode = resolveThemeMode(data);
104
+ const variableMap = extractCssVariableMap(data);
105
+ const hasChanges = Boolean(mode) || Object.keys(variableMap).length > 0;
106
+ if (mode) {
107
+ root.dataset.theme = mode;
108
+ root.style.colorScheme = mode;
109
+ }
110
+ if (Object.keys(variableMap).length > 0) {
111
+ applyCssVariables(root, variableMap);
112
+ }
113
+ return hasChanges;
114
+ };
115
+ return {
116
+ applyFromData,
117
+ };
118
+ }
@@ -0,0 +1,9 @@
1
+ export interface ToolHeaderConfig {
2
+ pillLabel: string;
3
+ pillClassName?: string;
4
+ title: string;
5
+ subtitle: string;
6
+ badges: string[];
7
+ actionsHtml: string;
8
+ }
9
+ export declare function renderToolHeader(config: ToolHeaderConfig): string;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Reusable header renderer for MCP tool UIs. It provides a consistent title/description/status pattern so each app presents uniform top-of-page context.
3
+ */
4
+ import { escapeHtml } from './escape-html.js';
5
+ export function renderToolHeader(config) {
6
+ return `
7
+ <header class="toolbar">
8
+ <div class="meta">
9
+ <div class="meta-main">
10
+ <span class="file-pill ${escapeHtml(config.pillClassName ?? '')}">${escapeHtml(config.pillLabel)}</span>
11
+ <div class="meta-text">
12
+ <span class="filename" title="${escapeHtml(config.title)}">${escapeHtml(config.title)}</span>
13
+ <span class="filepath" title="${escapeHtml(config.subtitle)}">${escapeHtml(config.subtitle)}</span>
14
+ </div>
15
+ </div>
16
+ <div class="meta-badges">
17
+ ${config.badges.map((badge) => `<span class="badge">${escapeHtml(badge)}</span>`).join('')}
18
+ </div>
19
+ </div>
20
+ <div class="actions">
21
+ ${config.actionsHtml}
22
+ </div>
23
+ </header>
24
+ `;
25
+ }
@@ -0,0 +1,16 @@
1
+ export interface ToolShellController {
2
+ getExpanded: () => boolean;
3
+ setExpanded: (nextExpanded: boolean) => void;
4
+ toggle: () => void;
5
+ dispose: () => void;
6
+ }
7
+ interface CreateToolShellControllerOptions {
8
+ shell: HTMLElement | null;
9
+ toggleButton: HTMLButtonElement | null;
10
+ initialExpanded: boolean;
11
+ onToggle?: (expanded: boolean) => void;
12
+ onScrollAfterExpand?: () => void;
13
+ onRender?: () => void;
14
+ }
15
+ export declare function createToolShellController(options: CreateToolShellControllerOptions): ToolShellController;
16
+ export {};
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Shared shell behavior for collapsible sections and common UI affordances. It keeps repeated interaction patterns consistent across tool apps.
3
+ */
4
+ const EXPAND_ICON = '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M7 10l5 5 5-5z"></path></svg>';
5
+ const COLLAPSE_ICON = '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M7 14l5-5 5 5z"></path></svg>';
6
+ function syncExpandButton(toggleButton, expanded) {
7
+ if (!toggleButton) {
8
+ return;
9
+ }
10
+ const label = expanded ? 'Collapse' : 'Expand';
11
+ toggleButton.title = label;
12
+ toggleButton.setAttribute('aria-label', label);
13
+ toggleButton.innerHTML = expanded ? COLLAPSE_ICON : EXPAND_ICON;
14
+ }
15
+ function syncShellClasses(shell, expanded) {
16
+ if (!shell) {
17
+ return;
18
+ }
19
+ shell.classList.toggle('expanded', expanded);
20
+ shell.classList.toggle('collapsed', !expanded);
21
+ }
22
+ export function createToolShellController(options) {
23
+ const { shell, toggleButton, initialExpanded, onToggle, onScrollAfterExpand, onRender } = options;
24
+ let isExpanded = initialExpanded;
25
+ let scrollTrackedForCurrentExpand = false;
26
+ const applyExpandedState = (nextExpanded) => {
27
+ const wasExpanded = isExpanded;
28
+ isExpanded = nextExpanded;
29
+ syncShellClasses(shell, isExpanded);
30
+ syncExpandButton(toggleButton, isExpanded);
31
+ if (!wasExpanded && isExpanded) {
32
+ scrollTrackedForCurrentExpand = false;
33
+ }
34
+ };
35
+ const toggle = () => {
36
+ applyExpandedState(!isExpanded);
37
+ onToggle?.(isExpanded);
38
+ onRender?.();
39
+ };
40
+ const handleScroll = (event) => {
41
+ if (!isExpanded || scrollTrackedForCurrentExpand) {
42
+ return;
43
+ }
44
+ const targetNode = event?.target instanceof Node ? event.target : null;
45
+ if (targetNode && shell && !shell.contains(targetNode)) {
46
+ return;
47
+ }
48
+ scrollTrackedForCurrentExpand = true;
49
+ onScrollAfterExpand?.();
50
+ };
51
+ applyExpandedState(isExpanded);
52
+ toggleButton?.addEventListener('click', toggle);
53
+ shell?.addEventListener('scroll', handleScroll, { passive: true });
54
+ document.addEventListener('scroll', handleScroll, { passive: true, capture: true });
55
+ return {
56
+ getExpanded: () => isExpanded,
57
+ setExpanded: applyExpandedState,
58
+ toggle,
59
+ dispose: () => {
60
+ toggleButton?.removeEventListener('click', toggle);
61
+ shell?.removeEventListener('scroll', handleScroll);
62
+ document.removeEventListener('scroll', handleScroll, true);
63
+ },
64
+ };
65
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Widget state persistence for MCP Apps hosts.
3
+ *
4
+ * ChatGPT has a special extension (window.openai.widgetState) for persisting
5
+ * widget state across page refreshes. Other hosts use the standard MCP Apps
6
+ * pattern where ui/notifications/tool-result is re-sent when needed.
7
+ *
8
+ * This module provides a simple abstraction:
9
+ * - ChatGPT: Uses window.openai.widgetState
10
+ * - Other hosts: No-op (rely on standard ui/notifications/tool-result)
11
+ */
12
+ export interface WidgetStateStorage<T> {
13
+ /** Read persisted state, returns undefined if not found or not supported */
14
+ read(): T | undefined;
15
+ /** Persist state for recovery after refresh (no-op on unsupported hosts) */
16
+ write(state: T): void;
17
+ }
18
+ /**
19
+ * Check if we're running in ChatGPT (has special widget state API)
20
+ */
21
+ export declare function isChatGPT(): boolean;
22
+ /**
23
+ * Create a widget state storage adapter.
24
+ *
25
+ * On ChatGPT: Uses window.openai.widgetState for persistence
26
+ * On other hosts: Returns no-op adapter (state comes from ui/notifications/tool-result)
27
+ */
28
+ export declare function createWidgetStateStorage<T>(validator?: (state: unknown) => boolean): WidgetStateStorage<T>;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Widget state persistence for MCP Apps hosts.
3
+ *
4
+ * ChatGPT has a special extension (window.openai.widgetState) for persisting
5
+ * widget state across page refreshes. Other hosts use the standard MCP Apps
6
+ * pattern where ui/notifications/tool-result is re-sent when needed.
7
+ *
8
+ * This module provides a simple abstraction:
9
+ * - ChatGPT: Uses window.openai.widgetState
10
+ * - Other hosts: No-op (rely on standard ui/notifications/tool-result)
11
+ */
12
+ /**
13
+ * Check if we're running in ChatGPT (has special widget state API)
14
+ */
15
+ export function isChatGPT() {
16
+ return typeof window !== 'undefined' &&
17
+ typeof window.openai?.setWidgetState === 'function';
18
+ }
19
+ /**
20
+ * Create a widget state storage adapter.
21
+ *
22
+ * On ChatGPT: Uses window.openai.widgetState for persistence
23
+ * On other hosts: Returns no-op adapter (state comes from ui/notifications/tool-result)
24
+ */
25
+ export function createWidgetStateStorage(validator) {
26
+ if (!isChatGPT()) {
27
+ // Other hosts don't have widget state persistence - return no-op
28
+ return {
29
+ read: () => undefined,
30
+ write: () => { }
31
+ };
32
+ }
33
+ // ChatGPT-specific implementation
34
+ return {
35
+ read() {
36
+ try {
37
+ const state = window.openai?.widgetState;
38
+ if (state === undefined || state === null)
39
+ return undefined;
40
+ const payload = state.payload;
41
+ if (payload === undefined)
42
+ return undefined;
43
+ if (validator && !validator(payload))
44
+ return undefined;
45
+ return payload;
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ },
51
+ write(state) {
52
+ try {
53
+ window.openai?.setWidgetState?.({ payload: state });
54
+ }
55
+ catch {
56
+ // Ignore write failures
57
+ }
58
+ }
59
+ };
60
+ }
@@ -15,6 +15,7 @@ export declare function sanitizeError(error: any): {
15
15
  export declare const captureBase: (captureURL: string, event: string, properties?: any) => Promise<void>;
16
16
  export declare const capture_call_tool: (event: string, properties?: any) => Promise<void>;
17
17
  export declare const capture: (event: string, properties?: any) => Promise<void>;
18
+ export declare const capture_ui_event: (event: string, properties?: any) => Promise<void>;
18
19
  /**
19
20
  * Wrapper for capture() that automatically adds remote flag for remote-device telemetry
20
21
  * Also adds additional privacy filtering to remove sensitive identity information
@@ -239,6 +239,12 @@ export const capture = async (event, properties) => {
239
239
  const GA_DEBUG_BASE_URL = `https://www.google-analytics.com/debug/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
240
240
  return await captureBase(GA_BASE_URL, event, properties);
241
241
  };
242
+ export const capture_ui_event = async (event, properties) => {
243
+ const GA_MEASUREMENT_ID = 'G-MPFSWEGQ0T';
244
+ const GA_API_SECRET = 'BeK3uyAOQ6-TK6wnaDG2Ww';
245
+ const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
246
+ return await captureBase(GA_BASE_URL, event, properties);
247
+ };
242
248
  /**
243
249
  * Wrapper for capture() that automatically adds remote flag for remote-device telemetry
244
250
  * Also adds additional privacy filtering to remove sensitive identity information
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * DOCX File Handler
3
- * Implements FileHandler interface for Microsoft Word documents
3
+ * Implements FileHandler interface for DOCX documents
4
+ * Handles reading, writing, and modifying DOCX files while preserving formatting
4
5
  */
5
6
  import { FileHandler, FileResult, FileInfo, ReadOptions, EditResult } from './base.js';
6
7
  /**
7
8
  * File handler for DOCX documents
8
- * Extracts text as markdown with embedded images
9
+ * Extracts text and metadata, supports paragraph-based pagination
9
10
  */
10
11
  export declare class DocxFileHandler implements FileHandler {
11
12
  private readonly extensions;
@@ -14,28 +15,20 @@ export declare class DocxFileHandler implements FileHandler {
14
15
  */
15
16
  canHandle(path: string): boolean;
16
17
  /**
17
- * Read DOCX content extracts text as styled HTML (with embedded images).
18
- * Uses direct DOCX XML parsing for style preservation, with mammoth.js fallback.
18
+ * Read DOCX content - returns body XML for LLM modification
19
19
  */
20
20
  read(path: string, options?: ReadOptions): Promise<FileResult>;
21
21
  /**
22
- * Write DOCX file.
23
- *
24
- * - String content + 'rewrite': create new DOCX from HTML/markdown.
25
- * - String content + 'append': append to existing DOCX → writes to {name}_v1.docx.
26
- * - Array content: apply DocxOperation[] edits → writes to {name}_v1.docx.
27
- * Original file is always preserved.
22
+ * Write DOCX - NOT SUPPORTED via write_file
23
+ * Use write_docx tool instead to preserve styles
28
24
  */
29
25
  write(path: string, content: any, mode?: 'rewrite' | 'append'): Promise<void>;
30
26
  /**
31
- * Edit DOCX by applying high-level operations.
32
- * Writes to {name}_v1.docx unless `options.outputPath` is provided.
27
+ * Edit DOCX by applying modifications
33
28
  */
34
29
  editRange(path: string, range: string, content: any, options?: Record<string, any>): Promise<EditResult>;
35
30
  /**
36
- * Get DOCX file information including metadata.
31
+ * Get DOCX file information
37
32
  */
38
33
  getInfo(path: string): Promise<FileInfo>;
39
- /** Get base directory for resolving relative image paths. */
40
- private getBaseDir;
41
34
  }