@wonderwhy-er/desktop-commander 0.2.35 → 0.2.37

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 (132) hide show
  1. package/README.md +3 -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/remote-device/remote-channel.d.ts +8 -3
  6. package/dist/remote-device/remote-channel.js +68 -21
  7. package/dist/search-manager.d.ts +13 -0
  8. package/dist/search-manager.js +146 -0
  9. package/dist/server.js +56 -4
  10. package/dist/test-docx.d.ts +1 -0
  11. package/dist/tools/docx/builders/image.d.ts +14 -0
  12. package/dist/tools/docx/builders/image.js +84 -0
  13. package/dist/tools/docx/builders/index.d.ts +9 -3
  14. package/dist/tools/docx/builders/index.js +9 -3
  15. package/dist/tools/docx/builders/paragraph.d.ts +12 -0
  16. package/dist/tools/docx/builders/paragraph.js +29 -0
  17. package/dist/tools/docx/builders/table.d.ts +10 -0
  18. package/dist/tools/docx/builders/table.js +138 -0
  19. package/dist/tools/docx/builders/utils.d.ts +5 -0
  20. package/dist/tools/docx/builders/utils.js +18 -0
  21. package/dist/tools/docx/constants.d.ts +28 -32
  22. package/dist/tools/docx/constants.js +56 -52
  23. package/dist/tools/docx/create.d.ts +21 -0
  24. package/dist/tools/docx/create.js +386 -0
  25. package/dist/tools/docx/dom.d.ts +139 -0
  26. package/dist/tools/docx/dom.js +448 -0
  27. package/dist/tools/docx/index.d.ts +8 -12
  28. package/dist/tools/docx/index.js +8 -14
  29. package/dist/tools/docx/modify.d.ts +28 -0
  30. package/dist/tools/docx/modify.js +271 -0
  31. package/dist/tools/docx/ops/delete-paragraph-at-body-index.d.ts +11 -0
  32. package/dist/tools/docx/ops/delete-paragraph-at-body-index.js +23 -0
  33. package/dist/tools/docx/ops/header-replace-text-exact.d.ts +13 -0
  34. package/dist/tools/docx/ops/header-replace-text-exact.js +55 -0
  35. package/dist/tools/docx/ops/index.d.ts +17 -0
  36. package/dist/tools/docx/ops/index.js +70 -0
  37. package/dist/tools/docx/ops/insert-image-after-text.d.ts +24 -0
  38. package/dist/tools/docx/ops/insert-image-after-text.js +128 -0
  39. package/dist/tools/docx/ops/insert-paragraph-after-text.d.ts +12 -0
  40. package/dist/tools/docx/ops/insert-paragraph-after-text.js +74 -0
  41. package/dist/tools/docx/ops/insert-table-after-text.d.ts +19 -0
  42. package/dist/tools/docx/ops/insert-table-after-text.js +57 -0
  43. package/dist/tools/docx/ops/replace-hyperlink-url.d.ts +12 -0
  44. package/dist/tools/docx/ops/replace-hyperlink-url.js +37 -0
  45. package/dist/tools/docx/ops/replace-paragraph-at-body-index.d.ts +9 -0
  46. package/dist/tools/docx/ops/replace-paragraph-at-body-index.js +25 -0
  47. package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +21 -0
  48. package/dist/tools/docx/ops/replace-paragraph-text-exact.js +36 -0
  49. package/dist/tools/docx/ops/replace-table-cell-text.d.ts +25 -0
  50. package/dist/tools/docx/ops/replace-table-cell-text.js +85 -0
  51. package/dist/tools/docx/ops/set-color-for-paragraph-exact.d.ts +9 -0
  52. package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +24 -0
  53. package/dist/tools/docx/ops/set-color-for-style.d.ts +13 -0
  54. package/dist/tools/docx/ops/set-color-for-style.js +31 -0
  55. package/dist/tools/docx/ops/set-paragraph-style-at-body-index.d.ts +8 -0
  56. package/dist/tools/docx/ops/set-paragraph-style-at-body-index.js +57 -0
  57. package/dist/tools/docx/ops/table-set-cell-text.d.ts +9 -0
  58. package/dist/tools/docx/ops/table-set-cell-text.js +40 -0
  59. package/dist/tools/docx/read.d.ts +27 -0
  60. package/dist/tools/docx/read.js +308 -0
  61. package/dist/tools/docx/relationships.d.ts +22 -0
  62. package/dist/tools/docx/relationships.js +76 -0
  63. package/dist/tools/docx/types.d.ts +202 -103
  64. package/dist/tools/docx/types.js +2 -5
  65. package/dist/tools/docx/validate.d.ts +33 -0
  66. package/dist/tools/docx/validate.js +49 -0
  67. package/dist/tools/docx/write.d.ts +17 -0
  68. package/dist/tools/docx/write.js +88 -0
  69. package/dist/tools/docx/xml-view-test.d.ts +1 -0
  70. package/dist/tools/docx/xml-view-test.js +63 -0
  71. package/dist/tools/docx/xml-view.d.ts +56 -0
  72. package/dist/tools/docx/xml-view.js +169 -0
  73. package/dist/tools/docx/zip.d.ts +21 -0
  74. package/dist/tools/docx/zip.js +35 -0
  75. package/dist/tools/edit.js +57 -27
  76. package/dist/tools/schemas.d.ts +13 -0
  77. package/dist/tools/schemas.js +6 -1
  78. package/dist/types.d.ts +10 -0
  79. package/dist/ui/contracts.d.ts +14 -0
  80. package/dist/ui/contracts.js +18 -0
  81. package/dist/ui/file-preview/index.html +16 -0
  82. package/dist/ui/file-preview/preview-runtime.js +13983 -0
  83. package/dist/ui/file-preview/shared/preview-file-types.d.ts +5 -0
  84. package/dist/ui/file-preview/shared/preview-file-types.js +57 -0
  85. package/dist/ui/file-preview/src/app.d.ts +4 -0
  86. package/dist/ui/file-preview/src/app.js +800 -0
  87. package/dist/ui/file-preview/src/components/code-viewer.d.ts +6 -0
  88. package/dist/ui/file-preview/src/components/code-viewer.js +73 -0
  89. package/dist/ui/file-preview/src/components/highlighting.d.ts +2 -0
  90. package/dist/ui/file-preview/src/components/highlighting.js +54 -0
  91. package/dist/ui/file-preview/src/components/html-renderer.d.ts +9 -0
  92. package/dist/ui/file-preview/src/components/html-renderer.js +63 -0
  93. package/dist/ui/file-preview/src/components/markdown-renderer.d.ts +1 -0
  94. package/dist/ui/file-preview/src/components/markdown-renderer.js +21 -0
  95. package/dist/ui/file-preview/src/components/toolbar.d.ts +6 -0
  96. package/dist/ui/file-preview/src/components/toolbar.js +75 -0
  97. package/dist/ui/file-preview/src/image-preview.d.ts +3 -0
  98. package/dist/ui/file-preview/src/image-preview.js +21 -0
  99. package/dist/ui/file-preview/src/main.d.ts +1 -0
  100. package/dist/ui/file-preview/src/main.js +5 -0
  101. package/dist/ui/file-preview/src/types.d.ts +1 -0
  102. package/dist/ui/file-preview/src/types.js +1 -0
  103. package/dist/ui/file-preview/styles.css +764 -0
  104. package/dist/ui/resources.d.ts +21 -0
  105. package/dist/ui/resources.js +72 -0
  106. package/dist/ui/shared/escape-html.d.ts +4 -0
  107. package/dist/ui/shared/escape-html.js +11 -0
  108. package/dist/ui/shared/host-lifecycle.d.ts +16 -0
  109. package/dist/ui/shared/host-lifecycle.js +35 -0
  110. package/dist/ui/shared/rpc-client.d.ts +14 -0
  111. package/dist/ui/shared/rpc-client.js +72 -0
  112. package/dist/ui/shared/theme-adaptation.d.ts +10 -0
  113. package/dist/ui/shared/theme-adaptation.js +118 -0
  114. package/dist/ui/shared/tool-header.d.ts +9 -0
  115. package/dist/ui/shared/tool-header.js +25 -0
  116. package/dist/ui/shared/tool-shell.d.ts +16 -0
  117. package/dist/ui/shared/tool-shell.js +65 -0
  118. package/dist/ui/shared/widget-state.d.ts +28 -0
  119. package/dist/ui/shared/widget-state.js +60 -0
  120. package/dist/utils/capture.d.ts +1 -0
  121. package/dist/utils/capture.js +176 -8
  122. package/dist/utils/files/base.d.ts +3 -1
  123. package/dist/utils/files/docx.d.ts +28 -22
  124. package/dist/utils/files/docx.js +630 -196
  125. package/dist/utils/files/factory.d.ts +6 -5
  126. package/dist/utils/files/factory.js +18 -6
  127. package/dist/utils/files/text.js +9 -1
  128. package/dist/utils/system-info.js +1 -1
  129. package/dist/utils/usageTracker.js +5 -0
  130. package/dist/version.d.ts +1 -1
  131. package/dist/version.js +1 -1
  132. package/package.json +6 -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