touchdesigner-mcp-server 1.0.0 → 1.1.0

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 (27) hide show
  1. package/README.ja.md +75 -26
  2. package/README.md +74 -25
  3. package/dist/core/constants.js +1 -0
  4. package/dist/features/tools/handlers/tdTools.js +162 -50
  5. package/dist/features/tools/metadata/touchDesignerToolMetadata.js +402 -0
  6. package/dist/features/tools/presenter/classListFormatter.js +187 -0
  7. package/dist/features/tools/presenter/index.js +11 -0
  8. package/dist/features/tools/presenter/markdownRenderer.js +25 -0
  9. package/dist/features/tools/presenter/nodeDetailsFormatter.js +140 -0
  10. package/dist/features/tools/presenter/nodeListFormatter.js +124 -0
  11. package/dist/features/tools/presenter/operationFormatter.js +117 -0
  12. package/dist/features/tools/presenter/presenter.js +62 -0
  13. package/dist/features/tools/presenter/responseFormatter.js +66 -0
  14. package/dist/features/tools/presenter/scriptResultFormatter.js +171 -0
  15. package/dist/features/tools/presenter/templates/markdown/classDetailsSummary.md +13 -0
  16. package/dist/features/tools/presenter/templates/markdown/classListSummary.md +7 -0
  17. package/dist/features/tools/presenter/templates/markdown/default.md +3 -0
  18. package/dist/features/tools/presenter/templates/markdown/detailedPayload.md +5 -0
  19. package/dist/features/tools/presenter/templates/markdown/nodeDetailsSummary.md +10 -0
  20. package/dist/features/tools/presenter/templates/markdown/nodeListSummary.md +8 -0
  21. package/dist/features/tools/presenter/templates/markdown/scriptSummary.md +15 -0
  22. package/dist/features/tools/presenter/toolMetadataFormatter.js +118 -0
  23. package/dist/features/tools/types.js +26 -1
  24. package/dist/gen/endpoints/TouchDesignerAPI.js +1 -1
  25. package/dist/gen/mcp/touchDesignerAPI.zod.js +1 -1
  26. package/dist/server/touchDesignerServer.js +1 -1
  27. package/package.json +4 -3
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Node Details Formatter
3
+ *
4
+ * Formats TouchDesigner node parameter details with token optimization.
5
+ * Used by GET_TD_NODE_PARAMETERS tool.
6
+ */
7
+ import { DEFAULT_PRESENTER_FORMAT, presentStructuredData, } from "./presenter.js";
8
+ import { finalizeFormattedText, limitArray, mergeFormatterOptions, } from "./responseFormatter.js";
9
+ /**
10
+ * Format node parameter details
11
+ */
12
+ export function formatNodeDetails(data, options) {
13
+ const opts = mergeFormatterOptions(options);
14
+ if (!data) {
15
+ return "No node details available.";
16
+ }
17
+ const nodePath = data.path;
18
+ const properties = data.properties;
19
+ const propertyKeys = properties ? Object.keys(properties) : [];
20
+ if (propertyKeys.length === 0) {
21
+ return `Node: ${nodePath}\nNo properties found.`;
22
+ }
23
+ if (opts.detailLevel === "detailed") {
24
+ return formatDetailed(data, opts.responseFormat);
25
+ }
26
+ let result;
27
+ if (opts.detailLevel === "minimal") {
28
+ result = formatMinimal(nodePath, propertyKeys, opts.limit);
29
+ }
30
+ else {
31
+ result = formatSummary(nodePath, data, opts.limit);
32
+ }
33
+ const context = result.context;
34
+ return finalizeFormattedText(result.text, opts, {
35
+ template: "nodeDetailsSummary",
36
+ context,
37
+ structured: context,
38
+ });
39
+ }
40
+ /**
41
+ * Minimal mode: Property names only
42
+ */
43
+ function formatMinimal(nodePath, propertyKeys, limit) {
44
+ const { items, truncated } = limitArray(propertyKeys, limit);
45
+ let text = `Node: ${nodePath}\nProperties (${propertyKeys.length}):\n${items.join(", ")}`;
46
+ if (truncated) {
47
+ text += `\nšŸ’” ${propertyKeys.length - items.length} more properties omitted.`;
48
+ }
49
+ return {
50
+ text,
51
+ context: {
52
+ nodePath,
53
+ type: "",
54
+ id: 0,
55
+ name: "",
56
+ total: propertyKeys.length,
57
+ displayed: items.length,
58
+ properties: items.map((name) => ({ name, value: "" })),
59
+ truncated,
60
+ omittedCount: Math.max(propertyKeys.length - items.length, 0),
61
+ },
62
+ };
63
+ }
64
+ /**
65
+ * Summary mode: Key properties with values
66
+ */
67
+ function formatSummary(nodePath, data, limit) {
68
+ const properties = data.properties || {};
69
+ const propertyEntries = Object.entries(properties);
70
+ const { items, truncated } = limitArray(propertyEntries, limit);
71
+ let text = `Node: ${nodePath}\n`;
72
+ text += `Type: ${data.opType} (ID: ${data.id})\n`;
73
+ text += `Name: ${data.name}\n`;
74
+ text += `\nProperties (${propertyEntries.length}):\n\n`;
75
+ const propsForContext = [];
76
+ for (const [key, value] of items) {
77
+ const formattedValue = formatPropertyValue(value);
78
+ text += ` ${key}: ${formattedValue}\n`;
79
+ propsForContext.push({ name: key, value: formattedValue });
80
+ }
81
+ if (truncated) {
82
+ text += `\nšŸ’” ${propertyEntries.length - items.length} more properties omitted.`;
83
+ }
84
+ return {
85
+ text,
86
+ context: {
87
+ nodePath,
88
+ type: data.opType,
89
+ id: data.id,
90
+ name: data.name,
91
+ total: propertyEntries.length,
92
+ displayed: items.length,
93
+ properties: propsForContext,
94
+ truncated,
95
+ omittedCount: Math.max(propertyEntries.length - items.length, 0),
96
+ },
97
+ };
98
+ }
99
+ /**
100
+ * Detailed mode: Full JSON
101
+ */
102
+ function formatDetailed(data, format) {
103
+ const title = `Node ${data.path ?? data.name ?? "details"}`;
104
+ const payloadFormat = format ?? DEFAULT_PRESENTER_FORMAT;
105
+ return presentStructuredData({
106
+ text: title,
107
+ detailLevel: "detailed",
108
+ structured: data,
109
+ context: {
110
+ title,
111
+ payloadFormat,
112
+ },
113
+ template: "detailedPayload",
114
+ }, payloadFormat);
115
+ }
116
+ /**
117
+ * Format property value for display
118
+ */
119
+ function formatPropertyValue(value) {
120
+ if (value === undefined || value === null) {
121
+ return "(none)";
122
+ }
123
+ if (typeof value === "string") {
124
+ return value.length > 50 ? `"${value.substring(0, 50)}..."` : `"${value}"`;
125
+ }
126
+ if (typeof value === "number" || typeof value === "boolean") {
127
+ return String(value);
128
+ }
129
+ if (Array.isArray(value)) {
130
+ if (value.length === 0)
131
+ return "[]";
132
+ if (value.length <= 3)
133
+ return `[${value.join(", ")}]`;
134
+ return `[${value.slice(0, 3).join(", ")}, ... +${value.length - 3}]`;
135
+ }
136
+ if (typeof value === "object") {
137
+ return `{${Object.keys(value).length} keys}`;
138
+ }
139
+ return String(value);
140
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Node List Formatter
3
+ *
4
+ * Formats TouchDesigner node lists with token-optimized output.
5
+ * Used by GET_TD_NODES tool.
6
+ */
7
+ import { DEFAULT_PRESENTER_FORMAT, presentStructuredData, } from "./presenter.js";
8
+ import { finalizeFormattedText, formatOmissionHint, limitArray, mergeFormatterOptions, } from "./responseFormatter.js";
9
+ /**
10
+ * Format node list based on detail level
11
+ */
12
+ export function formatNodeList(data, options) {
13
+ const opts = mergeFormatterOptions(options);
14
+ if (!data?.nodes || data.nodes.length === 0) {
15
+ return "No nodes found.";
16
+ }
17
+ const nodes = data.nodes;
18
+ const totalCount = nodes.length;
19
+ const parentPath = data.parentPath ?? "project";
20
+ // Apply limit
21
+ const { items: limitedNodes, truncated } = limitArray(nodes, opts.limit);
22
+ if (opts.detailLevel === "detailed") {
23
+ return formatDetailed(nodes, data, opts.responseFormat, parentPath);
24
+ }
25
+ let result;
26
+ if (opts.detailLevel === "minimal") {
27
+ result = formatMinimal(limitedNodes, parentPath, totalCount, truncated);
28
+ }
29
+ else {
30
+ result = formatSummary(limitedNodes, parentPath, totalCount, truncated);
31
+ }
32
+ const hintEnabled = truncated && opts.includeHints;
33
+ let output = result.text;
34
+ if (hintEnabled) {
35
+ output += formatOmissionHint(totalCount, limitedNodes.length, "node");
36
+ }
37
+ const context = result.context;
38
+ context.truncated = hintEnabled;
39
+ if (!hintEnabled) {
40
+ context.omittedCount = 0;
41
+ }
42
+ return finalizeFormattedText(output, opts, {
43
+ template: "nodeListSummary",
44
+ context,
45
+ structured: context,
46
+ });
47
+ }
48
+ /**
49
+ * Minimal mode: Only node paths
50
+ */
51
+ function formatMinimal(nodes, parentPath, totalCount, truncated) {
52
+ const paths = nodes.map((n) => n.path || n.name);
53
+ const text = `Found ${nodes.length} nodes in ${parentPath}:
54
+ ${paths.join("\n")}`;
55
+ return {
56
+ text,
57
+ context: buildNodeListContext(nodes, parentPath, totalCount, truncated),
58
+ };
59
+ }
60
+ /**
61
+ * Summary mode: Essential info with types
62
+ */
63
+ function formatSummary(nodes, parentPath, totalCount, truncated) {
64
+ const header = `Found ${totalCount} nodes in ${parentPath}:
65
+
66
+ `;
67
+ const groups = buildGroups(nodes);
68
+ const sections = groups.map((group) => {
69
+ const nodeLines = group.nodes.map((n) => ` • ${n.name} [${n.path}]`);
70
+ return `${group.type}:
71
+ ${nodeLines.join("\n")}`;
72
+ });
73
+ return {
74
+ text: header + sections.join("\n\n"),
75
+ context: {
76
+ parentPath,
77
+ totalCount,
78
+ groups,
79
+ truncated,
80
+ omittedCount: Math.max(totalCount - nodes.length, 0),
81
+ },
82
+ };
83
+ }
84
+ /**
85
+ * Detailed mode: Full information (original behavior)
86
+ */
87
+ function formatDetailed(nodes, data, format, parentPath) {
88
+ const title = `Nodes in ${parentPath}`;
89
+ const payloadFormat = format ?? DEFAULT_PRESENTER_FORMAT;
90
+ return presentStructuredData({
91
+ text: title,
92
+ detailLevel: "detailed",
93
+ structured: { ...data, nodes },
94
+ context: {
95
+ title,
96
+ payloadFormat,
97
+ },
98
+ template: "detailedPayload",
99
+ }, payloadFormat);
100
+ }
101
+ function buildNodeListContext(nodes, parentPath, totalCount, truncated) {
102
+ return {
103
+ parentPath,
104
+ totalCount,
105
+ groups: buildGroups(nodes),
106
+ truncated,
107
+ omittedCount: Math.max(totalCount - nodes.length, 0),
108
+ };
109
+ }
110
+ function buildGroups(nodes) {
111
+ const byType = new Map();
112
+ for (const node of nodes) {
113
+ const type = node.opType || "unknown";
114
+ if (!byType.has(type)) {
115
+ byType.set(type, []);
116
+ }
117
+ byType.get(type)?.push(node);
118
+ }
119
+ return Array.from(byType.entries()).map(([type, typeNodes]) => ({
120
+ type,
121
+ count: typeNodes.length,
122
+ nodes: typeNodes.map((n) => ({ name: n.name, path: n.path })),
123
+ }));
124
+ }
@@ -0,0 +1,117 @@
1
+ import { finalizeFormattedText, mergeFormatterOptions, } from "./responseFormatter.js";
2
+ export function formatTdInfo(data, options) {
3
+ const opts = mergeFormatterOptions(options);
4
+ if (!data) {
5
+ return finalizeFormattedText("TouchDesigner info not available.", opts, {
6
+ context: { title: "TouchDesigner Info" },
7
+ });
8
+ }
9
+ const summary = `Server: ${data.server}\nVersion: ${data.version}`;
10
+ const osLine = data.osName
11
+ ? `\nOS: ${data.osName} ${data.osVersion ?? ""}`
12
+ : "";
13
+ const text = opts.detailLevel === "minimal"
14
+ ? `Server: ${data.server}, v${data.version}`
15
+ : `${summary}${osLine}`;
16
+ return finalizeFormattedText(text.trim(), opts, {
17
+ template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
18
+ context: { title: "TouchDesigner Info", ...data },
19
+ structured: data,
20
+ });
21
+ }
22
+ export function formatCreateNodeResult(data, options) {
23
+ const opts = mergeFormatterOptions(options);
24
+ const node = data?.result;
25
+ if (!node) {
26
+ return finalizeFormattedText("Node created but no metadata returned.", opts, {
27
+ context: { title: "Create Node" },
28
+ structured: data,
29
+ });
30
+ }
31
+ const name = node.name ?? "(unknown)";
32
+ const path = node.path ?? "(path unknown)";
33
+ const opType = node.opType ?? "unknown";
34
+ const base = `āœ“ Created node '${name}' (${opType}) at ${path}`;
35
+ const propCount = Object.keys(node.properties ?? {}).length;
36
+ const text = opts.detailLevel === "minimal"
37
+ ? base
38
+ : `${base}\nProperties detected: ${propCount}`;
39
+ return finalizeFormattedText(text, opts, {
40
+ template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
41
+ context: { title: "Create Node", path, opType },
42
+ structured: data,
43
+ });
44
+ }
45
+ export function formatUpdateNodeResult(data, options) {
46
+ const opts = mergeFormatterOptions(options);
47
+ const updatedCount = data?.updated?.length ?? 0;
48
+ const failedCount = data?.failed?.length ?? 0;
49
+ const base = `āœ“ Updated ${updatedCount} parameter(s)`;
50
+ const text = opts.detailLevel === "minimal"
51
+ ? base
52
+ : `${base}${failedCount ? `, ${failedCount} failed` : ""}`;
53
+ const context = {
54
+ title: "Update Node",
55
+ updated: data?.updated,
56
+ failed: data?.failed,
57
+ message: data?.message,
58
+ };
59
+ return finalizeFormattedText(text, opts, {
60
+ template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
61
+ context,
62
+ structured: data,
63
+ });
64
+ }
65
+ export function formatDeleteNodeResult(data, options) {
66
+ const opts = mergeFormatterOptions(options);
67
+ const deleted = data?.deleted ?? false;
68
+ const name = data?.node?.name ?? "node";
69
+ const path = data?.node?.path ?? "(path unknown)";
70
+ const text = deleted
71
+ ? `šŸ—‘ļø Deleted '${name}' at ${path}`
72
+ : `Deletion status unknown for '${name}' at ${path}`;
73
+ return finalizeFormattedText(text, opts, {
74
+ template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
75
+ context: { title: "Delete Node", path, deleted },
76
+ structured: data,
77
+ });
78
+ }
79
+ export function formatExecNodeMethodResult(data, context, options) {
80
+ const opts = mergeFormatterOptions(options);
81
+ const callSignature = buildCallSignature(context);
82
+ const resultPreview = summarizeValue(data?.result);
83
+ const text = `${callSignature}\nResult: ${resultPreview}`;
84
+ return finalizeFormattedText(text, opts, {
85
+ template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
86
+ context: { title: "Execute Node Method", callSignature },
87
+ structured: { ...context, result: data?.result },
88
+ });
89
+ }
90
+ function buildCallSignature(params) {
91
+ const argPart = params.args ?? [];
92
+ const kwPart = params.kwargs
93
+ ? Object.entries(params.kwargs).map(([key, value]) => `${key}=${JSON.stringify(value)}`)
94
+ : [];
95
+ const joinedArgs = [...argPart.map(stringifyValue), ...kwPart].join(", ");
96
+ return `op('${params.nodePath}').${params.method}(${joinedArgs})`;
97
+ }
98
+ function summarizeValue(value) {
99
+ if (value === undefined)
100
+ return "(no result)";
101
+ if (value === null)
102
+ return "null";
103
+ if (typeof value === "string")
104
+ return value.length > 120 ? `${value.slice(0, 117)}...` : value;
105
+ if (typeof value === "number" || typeof value === "boolean")
106
+ return String(value);
107
+ if (Array.isArray(value))
108
+ return `Array[${value.length}]`;
109
+ if (typeof value === "object")
110
+ return `Object{${Object.keys(value).length} keys}`;
111
+ return String(value);
112
+ }
113
+ function stringifyValue(value) {
114
+ if (typeof value === "string")
115
+ return `'${value}'`;
116
+ return JSON.stringify(value);
117
+ }
@@ -0,0 +1,62 @@
1
+ import { stringify as toYaml } from "yaml";
2
+ import { renderMarkdownTemplate } from "./markdownRenderer.js";
3
+ export const DEFAULT_PRESENTER_FORMAT = "yaml";
4
+ export function presentStructuredData(payload, format = DEFAULT_PRESENTER_FORMAT) {
5
+ switch (format) {
6
+ case "json":
7
+ return JSON.stringify(resolveStructured(payload), null, 2);
8
+ case "yaml":
9
+ return toYaml(resolveStructured(payload));
10
+ case "markdown":
11
+ return formatMarkdown(payload, format);
12
+ default:
13
+ return toYaml(resolveStructured(payload));
14
+ }
15
+ }
16
+ function resolveStructured(payload) {
17
+ if (payload.structured !== undefined) {
18
+ return payload.structured;
19
+ }
20
+ return {
21
+ mode: payload.detailLevel,
22
+ text: payload.text,
23
+ ...(payload.context ?? {}),
24
+ };
25
+ }
26
+ function formatMarkdown(payload, requestedFormat) {
27
+ const context = { ...(payload.context ?? {}) };
28
+ const inferredTitle = payload.detailLevel
29
+ ? `${payload.detailLevel.charAt(0).toUpperCase()}${payload.detailLevel.slice(1)}`
30
+ : "Response";
31
+ const effectiveTemplate = payload.template ??
32
+ (payload.detailLevel === "detailed" ? "detailedPayload" : "default");
33
+ const payloadFormat = context.payloadFormat ??
34
+ (requestedFormat === "markdown"
35
+ ? DEFAULT_PRESENTER_FORMAT
36
+ : requestedFormat);
37
+ const resolveStructuredText = () => {
38
+ if (typeof payload.structured === "string")
39
+ return payload.structured;
40
+ if (payload.structured === undefined)
41
+ return payload.text ?? "";
42
+ return payloadFormat === "json"
43
+ ? JSON.stringify(payload.structured, null, 2)
44
+ : toYaml(payload.structured);
45
+ };
46
+ let bodyText = payload.text?.trim() ?? "";
47
+ if (!bodyText) {
48
+ bodyText = resolveStructuredText();
49
+ }
50
+ if (effectiveTemplate === "detailedPayload") {
51
+ context.title ??= inferredTitle;
52
+ context.payload ??= resolveStructuredText();
53
+ context.payloadFormat ??= payloadFormat;
54
+ }
55
+ if (effectiveTemplate === "default" && !("title" in context)) {
56
+ context.title = inferredTitle;
57
+ }
58
+ return renderMarkdownTemplate(effectiveTemplate, {
59
+ ...context,
60
+ text: bodyText,
61
+ });
62
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Response Formatter Utilities
3
+ *
4
+ * Provides token-optimized formatting for MCP tool responses.
5
+ * Based on the design document: docs/context-optimization-design.md
6
+ */
7
+ /**
8
+ * Common formatting options
9
+ */
10
+ import { presentStructuredData } from "./presenter.js";
11
+ /**
12
+ * Default formatter options
13
+ */
14
+ export const DEFAULT_FORMATTER_OPTIONS = {
15
+ detailLevel: "summary",
16
+ limit: undefined,
17
+ includeHints: true,
18
+ responseFormat: undefined,
19
+ };
20
+ /**
21
+ * Merge user options with defaults
22
+ */
23
+ export function mergeFormatterOptions(options) {
24
+ const merged = { ...DEFAULT_FORMATTER_OPTIONS, ...options };
25
+ const detailLevel = options?.detailLevel ??
26
+ options?.mode ??
27
+ DEFAULT_FORMATTER_OPTIONS.detailLevel;
28
+ return {
29
+ detailLevel,
30
+ limit: merged.limit,
31
+ includeHints: merged.includeHints ?? true,
32
+ responseFormat: merged.responseFormat,
33
+ };
34
+ }
35
+ export function finalizeFormattedText(text, opts, metadata) {
36
+ const chosenFormat = opts.responseFormat ??
37
+ (opts.detailLevel === "detailed" ? "yaml" : "markdown");
38
+ return presentStructuredData({
39
+ text,
40
+ detailLevel: opts.detailLevel,
41
+ structured: metadata?.structured,
42
+ context: metadata?.context,
43
+ template: metadata?.template,
44
+ }, chosenFormat);
45
+ }
46
+ /**
47
+ * Format a hint message for omitted content
48
+ */
49
+ export function formatOmissionHint(totalCount, shownCount, itemType) {
50
+ const omitted = totalCount - shownCount;
51
+ if (omitted <= 0)
52
+ return "";
53
+ return `\nšŸ’” ${omitted} more ${itemType}(s) omitted. Use detailLevel='detailed' or increase limit to see all.`;
54
+ }
55
+ /**
56
+ * Truncate array based on limit option
57
+ */
58
+ export function limitArray(items, limit) {
59
+ if (limit === undefined || limit >= items.length) {
60
+ return { items, truncated: false };
61
+ }
62
+ return {
63
+ items: items.slice(0, limit),
64
+ truncated: true,
65
+ };
66
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Script Result Formatter
3
+ *
4
+ * Formats Python script execution results with token optimization.
5
+ * Used by EXECUTE_PYTHON_SCRIPT tool.
6
+ */
7
+ import { DEFAULT_PRESENTER_FORMAT, presentStructuredData, } from "./presenter.js";
8
+ import { finalizeFormattedText, mergeFormatterOptions, } from "./responseFormatter.js";
9
+ /**
10
+ * Format script execution result
11
+ */
12
+ export function formatScriptResult(data, scriptSnippet, options) {
13
+ const opts = mergeFormatterOptions(options);
14
+ if (!data) {
15
+ return "No result returned.";
16
+ }
17
+ const success = data.success ?? true;
18
+ const result = data.data?.result;
19
+ const output = data.data?.output;
20
+ const error = data.data?.error;
21
+ // Error case - always show full details
22
+ if (!success || error) {
23
+ return formatError(error, scriptSnippet);
24
+ }
25
+ if (opts.detailLevel === "detailed") {
26
+ return formatDetailed(data, opts.responseFormat);
27
+ }
28
+ let formattedText = "";
29
+ let context;
30
+ switch (opts.detailLevel) {
31
+ case "minimal":
32
+ formattedText = formatMinimal(result);
33
+ context = buildScriptContext(scriptSnippet, result, output);
34
+ break;
35
+ case "summary": {
36
+ const summary = formatSummary(result, output, scriptSnippet);
37
+ formattedText = summary.text;
38
+ context = summary.context;
39
+ break;
40
+ }
41
+ }
42
+ const ctx = context;
43
+ return finalizeFormattedText(formattedText, opts, {
44
+ template: "scriptSummary",
45
+ context: ctx,
46
+ structured: ctx,
47
+ });
48
+ }
49
+ /**
50
+ * Format error result
51
+ */
52
+ function formatError(error, scriptSnippet) {
53
+ const errorMsg = typeof error === "string" ? error : JSON.stringify(error);
54
+ const snippet = scriptSnippet
55
+ ? `\nScript: ${truncateScript(scriptSnippet)}`
56
+ : "";
57
+ return `āŒ Script execution failed:${snippet}\n\nError: ${errorMsg}`;
58
+ }
59
+ /**
60
+ * Minimal mode: Just the result value
61
+ */
62
+ function formatMinimal(result) {
63
+ if (result === undefined || result === null) {
64
+ return "āœ“ Script executed successfully (no return value)";
65
+ }
66
+ if (typeof result === "string" ||
67
+ typeof result === "number" ||
68
+ typeof result === "boolean") {
69
+ return `āœ“ Result: ${result}`;
70
+ }
71
+ if (Array.isArray(result)) {
72
+ return `āœ“ Result: Array[${result.length}]`;
73
+ }
74
+ if (typeof result === "object") {
75
+ const keys = Object.keys(result);
76
+ return `āœ“ Result: Object{${keys.length} keys}`;
77
+ }
78
+ return `āœ“ Result: ${String(result)}`;
79
+ }
80
+ /**
81
+ * Summary mode: Result with context
82
+ */
83
+ function formatSummary(result, output, scriptSnippet) {
84
+ let formatted = "āœ“ Script executed successfully\n\n";
85
+ const snippet = scriptSnippet ? truncateScript(scriptSnippet) : "";
86
+ if (snippet) {
87
+ formatted += `Script: ${snippet}\n\n`;
88
+ }
89
+ let resultPreview = "(none)";
90
+ if (result !== undefined && result !== null) {
91
+ resultPreview = formatResultValue(result, 500);
92
+ formatted += `Result: ${resultPreview}\n`;
93
+ }
94
+ let outputPreview;
95
+ if (output?.trim()) {
96
+ outputPreview =
97
+ output.length > 200 ? `${output.substring(0, 200)}...` : output;
98
+ formatted += `\nOutput:\n${outputPreview}`;
99
+ }
100
+ return {
101
+ text: formatted,
102
+ context: {
103
+ snippet,
104
+ resultType: getValueType(result),
105
+ resultPreview,
106
+ hasOutput: Boolean(outputPreview),
107
+ outputType: getValueType(output),
108
+ outputPreview,
109
+ },
110
+ };
111
+ }
112
+ function buildScriptContext(scriptSnippet, result, output) {
113
+ return {
114
+ snippet: scriptSnippet ? truncateScript(scriptSnippet) : "",
115
+ resultType: getValueType(result),
116
+ resultPreview: formatResultValue(result ?? "", 200),
117
+ hasOutput: Boolean(output?.trim()),
118
+ outputType: getValueType(output),
119
+ outputPreview: output,
120
+ };
121
+ }
122
+ /**
123
+ * Detailed mode: Full JSON
124
+ */
125
+ function formatDetailed(data, format) {
126
+ const title = "Script Result";
127
+ const payloadFormat = format ?? DEFAULT_PRESENTER_FORMAT;
128
+ return presentStructuredData({
129
+ text: title,
130
+ detailLevel: "detailed",
131
+ structured: data,
132
+ context: {
133
+ title,
134
+ payloadFormat,
135
+ },
136
+ template: "detailedPayload",
137
+ }, payloadFormat);
138
+ }
139
+ function getValueType(value) {
140
+ if (value === undefined || value === null) {
141
+ return "none";
142
+ }
143
+ if (Array.isArray(value)) {
144
+ return `array(${value.length})`;
145
+ }
146
+ return typeof value;
147
+ }
148
+ /**
149
+ * Format result value with size limit
150
+ */
151
+ function formatResultValue(value, maxChars) {
152
+ const str = typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
153
+ if (str.length <= maxChars) {
154
+ return str;
155
+ }
156
+ return `${str.substring(0, maxChars)}...\nšŸ’” Result truncated. Use detailLevel='detailed' for full output.`;
157
+ }
158
+ /**
159
+ * Truncate script for display
160
+ */
161
+ function truncateScript(script, maxLength = 100) {
162
+ const trimmed = script.trim();
163
+ if (trimmed.length <= maxLength) {
164
+ return trimmed;
165
+ }
166
+ const firstLine = trimmed.split("\n")[0];
167
+ if (firstLine.length <= maxLength) {
168
+ return `${firstLine}...`;
169
+ }
170
+ return `${trimmed.substring(0, maxLength)}...`;
171
+ }
@@ -0,0 +1,13 @@
1
+ ## Class `{{name}}`
2
+ - Type: {{type}}
3
+ - Description: {{{description}}}
4
+
5
+ ### Methods ({{methodsShown}} / {{methodsTotal}})
6
+ {{#methods}}1. `{{signature}}` — {{summary}}
7
+ {{/methods}}
8
+
9
+ ### Properties ({{propertiesShown}} / {{propertiesTotal}})
10
+ {{#properties}}- `{{name}}`: {{type}}
11
+ {{/properties}}
12
+
13
+ {{#truncated}}_šŸ’” Additional members omitted._{{/truncated}}