proofscan 0.10.62 → 0.11.1

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 (199) hide show
  1. package/README.ja.md +1 -0
  2. package/README.md +2 -0
  3. package/dist/a2a/agent-card.d.ts +2 -0
  4. package/dist/a2a/agent-card.d.ts.map +1 -1
  5. package/dist/a2a/agent-card.js +2 -2
  6. package/dist/a2a/agent-card.js.map +1 -1
  7. package/dist/a2a/client.d.ts +74 -12
  8. package/dist/a2a/client.d.ts.map +1 -1
  9. package/dist/a2a/client.js +228 -29
  10. package/dist/a2a/client.js.map +1 -1
  11. package/dist/a2a/normalizer.d.ts +4 -0
  12. package/dist/a2a/normalizer.d.ts.map +1 -1
  13. package/dist/a2a/normalizer.js +7 -4
  14. package/dist/a2a/normalizer.js.map +1 -1
  15. package/dist/a2a/session-manager.d.ts +81 -0
  16. package/dist/a2a/session-manager.d.ts.map +1 -0
  17. package/dist/a2a/session-manager.js +176 -0
  18. package/dist/a2a/session-manager.js.map +1 -0
  19. package/dist/a2a/types.d.ts +60 -0
  20. package/dist/a2a/types.d.ts.map +1 -1
  21. package/dist/cli.d.ts +2 -1
  22. package/dist/cli.d.ts.map +1 -1
  23. package/dist/cli.js +6 -3
  24. package/dist/cli.js.map +1 -1
  25. package/dist/commands/agent.d.ts.map +1 -1
  26. package/dist/commands/agent.js +35 -10
  27. package/dist/commands/agent.js.map +1 -1
  28. package/dist/commands/analyze.d.ts.map +1 -1
  29. package/dist/commands/analyze.js +12 -10
  30. package/dist/commands/analyze.js.map +1 -1
  31. package/dist/commands/connectors.js +2 -2
  32. package/dist/commands/connectors.js.map +1 -1
  33. package/dist/commands/index.d.ts +1 -0
  34. package/dist/commands/index.d.ts.map +1 -1
  35. package/dist/commands/index.js +1 -0
  36. package/dist/commands/index.js.map +1 -1
  37. package/dist/commands/plans.js +1 -1
  38. package/dist/commands/plans.js.map +1 -1
  39. package/dist/commands/record.js +5 -4
  40. package/dist/commands/record.js.map +1 -1
  41. package/dist/commands/rpc.d.ts.map +1 -1
  42. package/dist/commands/rpc.js +90 -28
  43. package/dist/commands/rpc.js.map +1 -1
  44. package/dist/commands/scan.d.ts.map +1 -1
  45. package/dist/commands/scan.js +8 -10
  46. package/dist/commands/scan.js.map +1 -1
  47. package/dist/commands/secrets.d.ts.map +1 -1
  48. package/dist/commands/secrets.js +11 -10
  49. package/dist/commands/secrets.js.map +1 -1
  50. package/dist/commands/sessions.js +2 -2
  51. package/dist/commands/sessions.js.map +1 -1
  52. package/dist/commands/summary.d.ts.map +1 -1
  53. package/dist/commands/summary.js +4 -2
  54. package/dist/commands/summary.js.map +1 -1
  55. package/dist/commands/task.d.ts +14 -0
  56. package/dist/commands/task.d.ts.map +1 -0
  57. package/dist/commands/task.js +520 -0
  58. package/dist/commands/task.js.map +1 -0
  59. package/dist/db/connection.d.ts.map +1 -1
  60. package/dist/db/connection.js +68 -21
  61. package/dist/db/connection.js.map +1 -1
  62. package/dist/db/events-store.d.ts +307 -8
  63. package/dist/db/events-store.d.ts.map +1 -1
  64. package/dist/db/events-store.js +620 -26
  65. package/dist/db/events-store.js.map +1 -1
  66. package/dist/db/proofs-store.d.ts +8 -1
  67. package/dist/db/proofs-store.d.ts.map +1 -1
  68. package/dist/db/proofs-store.js +18 -8
  69. package/dist/db/proofs-store.js.map +1 -1
  70. package/dist/db/schema.d.ts +15 -3
  71. package/dist/db/schema.d.ts.map +1 -1
  72. package/dist/db/schema.js +150 -5
  73. package/dist/db/schema.js.map +1 -1
  74. package/dist/db/tool-analysis.d.ts +15 -3
  75. package/dist/db/tool-analysis.d.ts.map +1 -1
  76. package/dist/db/tool-analysis.js +35 -17
  77. package/dist/db/tool-analysis.js.map +1 -1
  78. package/dist/db/types.d.ts +64 -1
  79. package/dist/db/types.d.ts.map +1 -1
  80. package/dist/filter/fields.d.ts.map +1 -1
  81. package/dist/filter/fields.js +22 -0
  82. package/dist/filter/fields.js.map +1 -1
  83. package/dist/filter/parser.js +2 -2
  84. package/dist/filter/parser.js.map +1 -1
  85. package/dist/filter/types.d.ts +1 -1
  86. package/dist/filter/types.d.ts.map +1 -1
  87. package/dist/html/analytics.test.ts +682 -0
  88. package/dist/html/analytics.ts +499 -0
  89. package/dist/html/browser.ts +39 -0
  90. package/dist/html/index.ts +97 -0
  91. package/dist/html/rpc-inspector.test.ts +529 -0
  92. package/dist/html/rpc-inspector.ts +1700 -0
  93. package/dist/html/templates.js +4 -4
  94. package/dist/html/templates.js.map +1 -1
  95. package/dist/html/templates.test.ts +861 -0
  96. package/dist/html/templates.ts +3163 -0
  97. package/dist/html/trace-viewer.html +624 -0
  98. package/dist/html/types.d.ts +3 -3
  99. package/dist/html/types.d.ts.map +1 -1
  100. package/dist/html/types.ts +491 -0
  101. package/dist/html/utils.ts +107 -0
  102. package/dist/monitor/data/connectors.d.ts.map +1 -1
  103. package/dist/monitor/data/connectors.js +113 -8
  104. package/dist/monitor/data/connectors.js.map +1 -1
  105. package/dist/monitor/data/popl.js +2 -2
  106. package/dist/monitor/data/popl.js.map +1 -1
  107. package/dist/monitor/routes/api.js +2 -2
  108. package/dist/monitor/routes/api.js.map +1 -1
  109. package/dist/monitor/routes/connectors.js +15 -15
  110. package/dist/monitor/routes/connectors.js.map +1 -1
  111. package/dist/monitor/routes/popl.js +5 -5
  112. package/dist/monitor/routes/popl.js.map +1 -1
  113. package/dist/monitor/templates/components.js +2 -2
  114. package/dist/monitor/templates/components.js.map +1 -1
  115. package/dist/monitor/templates/popl.js +4 -4
  116. package/dist/monitor/templates/popl.js.map +1 -1
  117. package/dist/monitor/types.d.ts +2 -2
  118. package/dist/monitor/types.d.ts.map +1 -1
  119. package/dist/proxy/bridge-utils.d.ts +41 -0
  120. package/dist/proxy/bridge-utils.d.ts.map +1 -0
  121. package/dist/proxy/bridge-utils.js +60 -0
  122. package/dist/proxy/bridge-utils.js.map +1 -0
  123. package/dist/proxy/ipc-client.d.ts.map +1 -1
  124. package/dist/proxy/ipc-client.js +1 -2
  125. package/dist/proxy/ipc-client.js.map +1 -1
  126. package/dist/proxy/ipc-server.d.ts.map +1 -1
  127. package/dist/proxy/ipc-server.js +4 -2
  128. package/dist/proxy/ipc-server.js.map +1 -1
  129. package/dist/proxy/mcp-server.d.ts +31 -0
  130. package/dist/proxy/mcp-server.d.ts.map +1 -1
  131. package/dist/proxy/mcp-server.js +393 -4
  132. package/dist/proxy/mcp-server.js.map +1 -1
  133. package/dist/proxy/types.d.ts +95 -0
  134. package/dist/proxy/types.d.ts.map +1 -1
  135. package/dist/secrets/management.d.ts +2 -2
  136. package/dist/secrets/management.d.ts.map +1 -1
  137. package/dist/secrets/management.js +7 -7
  138. package/dist/secrets/management.js.map +1 -1
  139. package/dist/shell/completer.d.ts.map +1 -1
  140. package/dist/shell/completer.js +16 -0
  141. package/dist/shell/completer.js.map +1 -1
  142. package/dist/shell/context-applicator.d.ts.map +1 -1
  143. package/dist/shell/context-applicator.js +32 -0
  144. package/dist/shell/context-applicator.js.map +1 -1
  145. package/dist/shell/filter-mappers.d.ts +5 -1
  146. package/dist/shell/filter-mappers.d.ts.map +1 -1
  147. package/dist/shell/filter-mappers.js +12 -0
  148. package/dist/shell/filter-mappers.js.map +1 -1
  149. package/dist/shell/find-command.js +13 -13
  150. package/dist/shell/find-command.js.map +1 -1
  151. package/dist/shell/inscribe-commands.js +5 -5
  152. package/dist/shell/inscribe-commands.js.map +1 -1
  153. package/dist/shell/pager/less-pager.d.ts +1 -1
  154. package/dist/shell/pager/less-pager.d.ts.map +1 -1
  155. package/dist/shell/pager/less-pager.js +5 -2
  156. package/dist/shell/pager/less-pager.js.map +1 -1
  157. package/dist/shell/pager/more-pager.d.ts +1 -1
  158. package/dist/shell/pager/more-pager.d.ts.map +1 -1
  159. package/dist/shell/pager/more-pager.js +3 -2
  160. package/dist/shell/pager/more-pager.js.map +1 -1
  161. package/dist/shell/pager/renderer.d.ts.map +1 -1
  162. package/dist/shell/pager/renderer.js +66 -15
  163. package/dist/shell/pager/renderer.js.map +1 -1
  164. package/dist/shell/pager/types.d.ts +5 -2
  165. package/dist/shell/pager/types.d.ts.map +1 -1
  166. package/dist/shell/pager/utils.d.ts +5 -2
  167. package/dist/shell/pager/utils.d.ts.map +1 -1
  168. package/dist/shell/pager/utils.js +14 -17
  169. package/dist/shell/pager/utils.js.map +1 -1
  170. package/dist/shell/pipeline-types.d.ts +12 -4
  171. package/dist/shell/pipeline-types.d.ts.map +1 -1
  172. package/dist/shell/ref-commands.js +7 -7
  173. package/dist/shell/ref-commands.js.map +1 -1
  174. package/dist/shell/ref-resolver.d.ts +15 -15
  175. package/dist/shell/ref-resolver.d.ts.map +1 -1
  176. package/dist/shell/ref-resolver.js +34 -20
  177. package/dist/shell/ref-resolver.js.map +1 -1
  178. package/dist/shell/repl.d.ts +25 -0
  179. package/dist/shell/repl.d.ts.map +1 -1
  180. package/dist/shell/repl.js +285 -51
  181. package/dist/shell/repl.js.map +1 -1
  182. package/dist/shell/router-commands.d.ts +30 -0
  183. package/dist/shell/router-commands.d.ts.map +1 -1
  184. package/dist/shell/router-commands.js +1011 -62
  185. package/dist/shell/router-commands.js.map +1 -1
  186. package/dist/shell/selector.d.ts +1 -1
  187. package/dist/shell/selector.d.ts.map +1 -1
  188. package/dist/shell/selector.js +1 -1
  189. package/dist/shell/selector.js.map +1 -1
  190. package/dist/shell/types.d.ts.map +1 -1
  191. package/dist/shell/types.js +3 -1
  192. package/dist/shell/types.js.map +1 -1
  193. package/dist/shell/where-command.d.ts.map +1 -1
  194. package/dist/shell/where-command.js +19 -3
  195. package/dist/shell/where-command.js.map +1 -1
  196. package/dist/utils/output.d.ts.map +1 -1
  197. package/dist/utils/output.js +7 -1
  198. package/dist/utils/output.js.map +1 -1
  199. package/package.json +2 -2
@@ -0,0 +1,1700 @@
1
+ /**
2
+ * RPC Inspector - Wireshark-style JSON viewer with path tracking
3
+ *
4
+ * Phase 11.5: 2-column layout with Summary View (left) and Raw JSON View (right)
5
+ * - JSON rendered with data-path attributes for click-to-navigate
6
+ * - Method-aware summary generation (tools/list, etc.)
7
+ * - RFC 6901 JSON Pointer paths
8
+ */
9
+
10
+ import type { SummaryRow, MethodSummaryHandler } from './types.js';
11
+
12
+ // ============================================================================
13
+ // HTML Escaping Utilities
14
+ // ============================================================================
15
+
16
+ /**
17
+ * Escape HTML special characters and JavaScript string special chars
18
+ * This is critical because the rendered HTML is embedded in JavaScript string concatenation
19
+ */
20
+ function escapeHtml(text: string): string {
21
+ return text
22
+ .replace(/\\/g, '\') // Escape backslashes as HTML entity
23
+ .replace(/&/g, '&')
24
+ .replace(/</g, '&lt;')
25
+ .replace(/>/g, '&gt;')
26
+ .replace(/"/g, '&quot;')
27
+ .replace(/'/g, '&#x27;')
28
+ .replace(/\n/g, '&#10;') // Escape newlines for JS string safety
29
+ .replace(/\r/g, '&#13;') // Escape carriage returns
30
+ .replace(/\u2028/g, '&#8232;') // Line Separator (breaks JS strings)
31
+ .replace(/\u2029/g, '&#8233;'); // Paragraph Separator (breaks JS strings)
32
+ }
33
+
34
+ /**
35
+ * Escape attribute value for data-path
36
+ */
37
+ function escapeAttr(text: string): string {
38
+ return escapeHtml(text);
39
+ }
40
+
41
+ // ============================================================================
42
+ // JSON Pointer Utilities (RFC 6901)
43
+ // ============================================================================
44
+
45
+ /**
46
+ * Escape string for JSON Pointer (RFC 6901)
47
+ * ~ → ~0, / → ~1
48
+ */
49
+ export function escapeJsonPointer(str: string): string {
50
+ return str.replace(/~/g, '~0').replace(/\//g, '~1');
51
+ }
52
+
53
+ /**
54
+ * Check if value is a primitive (string, number, boolean, null)
55
+ */
56
+ function isPrimitive(value: unknown): boolean {
57
+ return value === null || typeof value !== 'object';
58
+ }
59
+
60
+ // ============================================================================
61
+ // JSON Renderer with Path Tracking
62
+ // ============================================================================
63
+
64
+ /**
65
+ * Render primitive value inline (for same-line key-value pairs)
66
+ */
67
+ function renderPrimitiveInline(value: unknown): string {
68
+ if (value === null) {
69
+ return '<span class="json-null">null</span>';
70
+ }
71
+ if (typeof value === 'boolean') {
72
+ return `<span class="json-bool">${value}</span>`;
73
+ }
74
+ if (typeof value === 'number') {
75
+ return `<span class="json-number">${value}</span>`;
76
+ }
77
+ if (typeof value === 'string') {
78
+ return `<span class="json-string">"${escapeHtml(value)}"</span>`;
79
+ }
80
+ return escapeHtml(String(value));
81
+ }
82
+
83
+ /**
84
+ * Render JSON value recursively with path tracking
85
+ */
86
+ function renderValue(
87
+ value: unknown,
88
+ path: string,
89
+ indent: number,
90
+ lines: string[]
91
+ ): void {
92
+ const indentStr = ' '.repeat(indent);
93
+
94
+ if (value === null) {
95
+ lines.push(
96
+ `<span class="json-line" data-path="${escapeAttr(path)}">${indentStr}<span class="json-null">null</span></span>`
97
+ );
98
+ } else if (typeof value === 'boolean') {
99
+ lines.push(
100
+ `<span class="json-line" data-path="${escapeAttr(path)}">${indentStr}<span class="json-bool">${value}</span></span>`
101
+ );
102
+ } else if (typeof value === 'number') {
103
+ lines.push(
104
+ `<span class="json-line" data-path="${escapeAttr(path)}">${indentStr}<span class="json-number">${value}</span></span>`
105
+ );
106
+ } else if (typeof value === 'string') {
107
+ lines.push(
108
+ `<span class="json-line" data-path="${escapeAttr(path)}">${indentStr}<span class="json-string">"${escapeHtml(value)}"</span></span>`
109
+ );
110
+ } else if (Array.isArray(value)) {
111
+ renderArray(value, path, indent, lines);
112
+ } else if (typeof value === 'object') {
113
+ renderObject(value as Record<string, unknown>, path, indent, lines);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Render object with path tracking
119
+ */
120
+ function renderObject(
121
+ obj: Record<string, unknown>,
122
+ path: string,
123
+ indent: number,
124
+ lines: string[]
125
+ ): void {
126
+ const indentStr = ' '.repeat(indent);
127
+ const keys = Object.keys(obj);
128
+
129
+ lines.push(
130
+ `<span class="json-line json-bracket" data-path="${escapeAttr(path)}">${indentStr}{</span>`
131
+ );
132
+
133
+ keys.forEach((key, idx) => {
134
+ const keyPath = `${path}/${escapeJsonPointer(key)}`;
135
+ const value = obj[key];
136
+ const comma = idx < keys.length - 1 ? ',' : '';
137
+ const keyIndent = ' '.repeat(indent + 1);
138
+
139
+ // For primitives, render key and value on same line
140
+ if (isPrimitive(value)) {
141
+ const valueHtml = renderPrimitiveInline(value);
142
+ lines.push(
143
+ `<span class="json-line" data-path="${escapeAttr(keyPath)}">${keyIndent}<span class="json-key">"${escapeHtml(key)}"</span>: ${valueHtml}${comma}</span>`
144
+ );
145
+ } else {
146
+ // For objects/arrays, key on its own line with nested content
147
+ lines.push(
148
+ `<span class="json-line json-key-line" data-path="${escapeAttr(keyPath)}">${keyIndent}<span class="json-key">"${escapeHtml(key)}"</span>:</span>`
149
+ );
150
+ renderValue(value, keyPath, indent + 1, lines);
151
+ // Add comma to last line if needed
152
+ if (comma && lines.length > 0) {
153
+ const lastIdx = lines.length - 1;
154
+ lines[lastIdx] = lines[lastIdx].replace(/<\/span>$/, `${comma}</span>`);
155
+ }
156
+ }
157
+ });
158
+
159
+ lines.push(
160
+ `<span class="json-line json-bracket" data-path="${escapeAttr(path)}">${indentStr}}</span>`
161
+ );
162
+ }
163
+
164
+ /**
165
+ * Render array with path tracking
166
+ */
167
+ function renderArray(
168
+ arr: unknown[],
169
+ path: string,
170
+ indent: number,
171
+ lines: string[]
172
+ ): void {
173
+ const indentStr = ' '.repeat(indent);
174
+
175
+ lines.push(
176
+ `<span class="json-line json-bracket" data-path="${escapeAttr(path)}">${indentStr}[</span>`
177
+ );
178
+
179
+ arr.forEach((item, idx) => {
180
+ const itemPath = `${path}/${idx}`;
181
+ const comma = idx < arr.length - 1 ? ',' : '';
182
+
183
+ if (isPrimitive(item)) {
184
+ const itemIndent = ' '.repeat(indent + 1);
185
+ const valueHtml = renderPrimitiveInline(item);
186
+ lines.push(
187
+ `<span class="json-line" data-path="${escapeAttr(itemPath)}">${itemIndent}${valueHtml}${comma}</span>`
188
+ );
189
+ } else {
190
+ renderValue(item, itemPath, indent + 1, lines);
191
+ if (comma && lines.length > 0) {
192
+ const lastIdx = lines.length - 1;
193
+ lines[lastIdx] = lines[lastIdx].replace(/<\/span>$/, `${comma}</span>`);
194
+ }
195
+ }
196
+ });
197
+
198
+ lines.push(
199
+ `<span class="json-line json-bracket" data-path="${escapeAttr(path)}">${indentStr}]</span>`
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Render JSON as HTML with line-level data-path attributes
205
+ *
206
+ * @param json - The JSON object to render
207
+ * @param pathPrefix - Base path (e.g., "#" for root)
208
+ * @returns HTML string with span elements containing data-path
209
+ */
210
+ export function renderJsonWithPaths(
211
+ json: unknown,
212
+ pathPrefix: string = '#'
213
+ ): string {
214
+ if (json === null || json === undefined) {
215
+ return '<span class="json-line json-null" data-path="#">(no data)</span>';
216
+ }
217
+
218
+ const lines: string[] = [];
219
+ renderValue(json, pathPrefix, 0, lines);
220
+ // Join without newlines - each json-line span has display:block in CSS
221
+ // This avoids JavaScript syntax errors when HTML is embedded in string concatenation
222
+ return lines.join('');
223
+ }
224
+
225
+ // ============================================================================
226
+ // Method Summary Registry
227
+ // ============================================================================
228
+
229
+ /**
230
+ * Registry of method-specific summary handlers
231
+ */
232
+ const methodHandlers: MethodSummaryHandler[] = [];
233
+
234
+ /**
235
+ * Register a method-specific summary handler
236
+ */
237
+ export function registerMethodHandler(handler: MethodSummaryHandler): void {
238
+ methodHandlers.push(handler);
239
+ }
240
+
241
+ /**
242
+ * Render method-specific summary (combined request + response)
243
+ */
244
+ export function renderMethodSummary(
245
+ method: string,
246
+ request: unknown,
247
+ response: unknown
248
+ ): SummaryRow[] {
249
+ for (const handler of methodHandlers) {
250
+ if (typeof handler.method === 'string' && handler.method === method) {
251
+ return handler.render(request, response);
252
+ }
253
+ if (handler.method instanceof RegExp && handler.method.test(method)) {
254
+ return handler.render(request, response);
255
+ }
256
+ }
257
+ // Default: show generic summary
258
+ return renderGenericSummary(method, request, response);
259
+ }
260
+
261
+ /**
262
+ * Render request-specific summary
263
+ */
264
+ /**
265
+ * Extended method summary handler with separate request/response renderers
266
+ */
267
+ interface MethodSummaryHandlerExtended extends MethodSummaryHandler {
268
+ renderRequest?: (request: unknown) => SummaryRow[];
269
+ renderResponse?: (response: unknown) => SummaryRow[];
270
+ }
271
+
272
+ export function renderRequestSummary(
273
+ method: string,
274
+ request: unknown
275
+ ): SummaryRow[] {
276
+ // Check for method-specific request handler
277
+ for (const handler of methodHandlers) {
278
+ const extended = handler as MethodSummaryHandlerExtended;
279
+ if (typeof handler.method === 'string' && handler.method === method) {
280
+ if (extended.renderRequest) {
281
+ return extended.renderRequest(request);
282
+ }
283
+ }
284
+ if (handler.method instanceof RegExp && handler.method.test(method)) {
285
+ if (extended.renderRequest) {
286
+ return extended.renderRequest(request);
287
+ }
288
+ }
289
+ }
290
+ // Default: show generic request summary
291
+ return renderGenericRequestSummary(method, request);
292
+ }
293
+
294
+ /**
295
+ * Render response-specific summary
296
+ */
297
+ export function renderResponseSummary(
298
+ method: string,
299
+ response: unknown
300
+ ): SummaryRow[] {
301
+ // Check for method-specific response handler
302
+ for (const handler of methodHandlers) {
303
+ const extended = handler as MethodSummaryHandlerExtended;
304
+ if (typeof handler.method === 'string' && handler.method === method) {
305
+ if (extended.renderResponse) {
306
+ return extended.renderResponse(response);
307
+ }
308
+ }
309
+ if (handler.method instanceof RegExp && handler.method.test(method)) {
310
+ if (extended.renderResponse) {
311
+ return extended.renderResponse(response);
312
+ }
313
+ }
314
+ }
315
+ // Default: show generic response summary
316
+ return renderGenericResponseSummary(method, response);
317
+ }
318
+
319
+ /**
320
+ * Generic summary for unknown methods - returns separate request and response summaries
321
+ */
322
+ function renderGenericSummary(
323
+ method: string,
324
+ request: unknown,
325
+ response: unknown
326
+ ): SummaryRow[] {
327
+ // This returns combined rows for backward compatibility
328
+ // New code should use renderGenericRequestSummary and renderGenericResponseSummary
329
+ const reqRows = renderGenericRequestSummary(method, request);
330
+ const resRows = renderGenericResponseSummary(method, response);
331
+ return [...reqRows, ...resRows];
332
+ }
333
+
334
+ /**
335
+ * Render request-only summary
336
+ */
337
+ export function renderGenericRequestSummary(
338
+ method: string,
339
+ request: unknown
340
+ ): SummaryRow[] {
341
+ const rows: SummaryRow[] = [];
342
+
343
+ rows.push({
344
+ type: 'header',
345
+ label: `Method: ${method}`,
346
+ cssClass: 'summary-method-header',
347
+ });
348
+
349
+ // Show request params if present
350
+ const req = request as { params?: Record<string, unknown> } | null;
351
+ if (req?.params && typeof req.params === 'object') {
352
+ const paramKeys = Object.keys(req.params);
353
+ if (paramKeys.length > 0) {
354
+ rows.push({
355
+ type: 'header',
356
+ label: 'Parameters',
357
+ cssClass: 'summary-section-header',
358
+ });
359
+ paramKeys.forEach((key) => {
360
+ rows.push({
361
+ type: 'property',
362
+ label: key,
363
+ value: summarizeValue(req.params![key]),
364
+ pointer: {
365
+ target: 'request',
366
+ path: `#/params/${escapeJsonPointer(key)}`,
367
+ },
368
+ });
369
+ });
370
+ }
371
+ } else {
372
+ rows.push({
373
+ type: 'property',
374
+ label: '(no parameters)',
375
+ cssClass: 'summary-empty',
376
+ });
377
+ }
378
+
379
+ return rows;
380
+ }
381
+
382
+ /**
383
+ * Render response-only summary
384
+ */
385
+ export function renderGenericResponseSummary(
386
+ method: string,
387
+ response: unknown
388
+ ): SummaryRow[] {
389
+ const rows: SummaryRow[] = [];
390
+
391
+ rows.push({
392
+ type: 'header',
393
+ label: `Method: ${method}`,
394
+ cssClass: 'summary-method-header',
395
+ });
396
+
397
+ // Show response result summary
398
+ const res = response as { result?: unknown; error?: unknown } | null;
399
+ if (res?.result !== undefined) {
400
+ rows.push({
401
+ type: 'header',
402
+ label: 'Result',
403
+ cssClass: 'summary-section-header',
404
+ });
405
+
406
+ // If result is an object, show its keys
407
+ if (res.result && typeof res.result === 'object' && !Array.isArray(res.result)) {
408
+ const resultObj = res.result as Record<string, unknown>;
409
+ Object.keys(resultObj).forEach((key) => {
410
+ rows.push({
411
+ type: 'property',
412
+ label: key,
413
+ value: summarizeValue(resultObj[key]),
414
+ pointer: {
415
+ target: 'response',
416
+ path: `#/result/${escapeJsonPointer(key)}`,
417
+ },
418
+ });
419
+ });
420
+ } else {
421
+ rows.push({
422
+ type: 'property',
423
+ label: 'result',
424
+ value: summarizeValue(res.result),
425
+ pointer: {
426
+ target: 'response',
427
+ path: '#/result',
428
+ },
429
+ });
430
+ }
431
+ }
432
+
433
+ if (res?.error !== undefined) {
434
+ rows.push({
435
+ type: 'header',
436
+ label: 'Error',
437
+ cssClass: 'summary-section-header summary-error',
438
+ });
439
+ const errorObj = res.error as Record<string, unknown> | null;
440
+ if (errorObj && typeof errorObj === 'object') {
441
+ Object.keys(errorObj).forEach((key) => {
442
+ rows.push({
443
+ type: 'property',
444
+ label: key,
445
+ value: summarizeValue(errorObj[key]),
446
+ pointer: {
447
+ target: 'response',
448
+ path: `#/error/${escapeJsonPointer(key)}`,
449
+ },
450
+ cssClass: 'summary-error-item',
451
+ });
452
+ });
453
+ } else {
454
+ rows.push({
455
+ type: 'property',
456
+ label: 'error',
457
+ value: summarizeValue(res.error),
458
+ pointer: {
459
+ target: 'response',
460
+ path: '#/error',
461
+ },
462
+ cssClass: 'summary-error-item',
463
+ });
464
+ }
465
+ }
466
+
467
+ if (res?.result === undefined && res?.error === undefined) {
468
+ rows.push({
469
+ type: 'property',
470
+ label: '(pending or no response)',
471
+ cssClass: 'summary-empty',
472
+ });
473
+ }
474
+
475
+ return rows;
476
+ }
477
+
478
+ /**
479
+ * Summarize a value for display (truncate if needed)
480
+ */
481
+ function summarizeValue(value: unknown): string {
482
+ if (value === null) return 'null';
483
+ if (value === undefined) return 'undefined';
484
+ if (typeof value === 'string') {
485
+ return value.length > 50 ? `"${value.slice(0, 47)}..."` : `"${value}"`;
486
+ }
487
+ if (typeof value === 'number' || typeof value === 'boolean') {
488
+ return String(value);
489
+ }
490
+ if (Array.isArray(value)) {
491
+ return `Array(${value.length})`;
492
+ }
493
+ if (typeof value === 'object') {
494
+ const keys = Object.keys(value);
495
+ return `Object(${keys.length} keys)`;
496
+ }
497
+ return String(value);
498
+ }
499
+
500
+ // ============================================================================
501
+ // tools/list Summary Handler
502
+ // ============================================================================
503
+
504
+ interface ToolInfo {
505
+ name: string;
506
+ description?: string;
507
+ inputSchema?: {
508
+ type?: string;
509
+ properties?: Record<
510
+ string,
511
+ {
512
+ type?: string;
513
+ description?: string;
514
+ default?: unknown;
515
+ deprecated?: boolean;
516
+ }
517
+ >;
518
+ required?: string[];
519
+ };
520
+ }
521
+
522
+ /**
523
+ * Register tools/list handler with separate request/response renderers
524
+ */
525
+ registerMethodHandler({
526
+ method: 'tools/list',
527
+ render: (_request: unknown, response: unknown): SummaryRow[] => {
528
+ // Combined view - just show response (tools list)
529
+ return renderToolsListResponse(response);
530
+ },
531
+ renderRequest: (_request: unknown): SummaryRow[] => {
532
+ const rows: SummaryRow[] = [];
533
+ rows.push({
534
+ type: 'header',
535
+ label: 'Method: tools/list',
536
+ cssClass: 'summary-method-header',
537
+ });
538
+ rows.push({
539
+ type: 'property',
540
+ label: '(no parameters required)',
541
+ cssClass: 'summary-empty',
542
+ });
543
+ return rows;
544
+ },
545
+ renderResponse: (response: unknown): SummaryRow[] => {
546
+ return renderToolsListResponse(response);
547
+ },
548
+ } as MethodSummaryHandlerExtended);
549
+
550
+ /**
551
+ * Render tools/list response summary
552
+ */
553
+ function renderToolsListResponse(response: unknown): SummaryRow[] {
554
+ const rows: SummaryRow[] = [];
555
+
556
+ rows.push({
557
+ type: 'header',
558
+ label: 'Method: tools/list',
559
+ cssClass: 'summary-method-header',
560
+ });
561
+
562
+ // Extract tools from response.result.tools
563
+ const res = response as { result?: { tools?: ToolInfo[] } } | null;
564
+ const tools = res?.result?.tools ?? [];
565
+
566
+ if (tools.length === 0) {
567
+ rows.push({
568
+ type: 'property',
569
+ label: '(no tools available)',
570
+ cssClass: 'summary-empty',
571
+ });
572
+ return rows;
573
+ }
574
+
575
+ // Table header with collapse controls (only if many tools)
576
+ const showCollapseControls = tools.length > 5;
577
+ rows.push({
578
+ type: 'header',
579
+ label: `Tools (${tools.length})`,
580
+ cssClass: `summary-table-header${showCollapseControls ? ' summary-collapsible-header' : ''}`,
581
+ });
582
+
583
+ // Tool rows
584
+ tools.forEach((tool, idx) => {
585
+ const toolRow: SummaryRow = {
586
+ type: 'item',
587
+ label: tool.name,
588
+ value: tool.description || '(no description)',
589
+ pointer: {
590
+ target: 'response',
591
+ path: `#/result/tools/${idx}`,
592
+ },
593
+ cssClass: 'summary-tool-row',
594
+ };
595
+
596
+ // Add inputSchema properties as children
597
+ if (tool.inputSchema?.properties) {
598
+ toolRow.children = renderInputSchemaRows(
599
+ tool.inputSchema,
600
+ `#/result/tools/${idx}/inputSchema`
601
+ );
602
+ }
603
+
604
+ rows.push(toolRow);
605
+ });
606
+
607
+ return rows;
608
+ }
609
+
610
+ /**
611
+ * Render inputSchema properties as summary rows
612
+ */
613
+ function renderInputSchemaRows(
614
+ schema: NonNullable<ToolInfo['inputSchema']>,
615
+ basePath: string
616
+ ): SummaryRow[] {
617
+ const rows: SummaryRow[] = [];
618
+ const props = schema.properties ?? {};
619
+ const required = new Set(schema.required ?? []);
620
+
621
+ Object.entries(props).forEach(([name, propSchema]) => {
622
+ const isRequired = required.has(name);
623
+ const isDeprecated = propSchema.deprecated === true;
624
+ const hasDefault = propSchema.default !== undefined;
625
+ const typeStr = propSchema.type ?? 'any';
626
+
627
+ // Build value string with badges
628
+ const badges: string[] = [];
629
+ if (isRequired) badges.push('required');
630
+ if (hasDefault) badges.push(`default: ${JSON.stringify(propSchema.default)}`);
631
+ if (isDeprecated) badges.push('deprecated');
632
+
633
+ const valueStr = badges.length > 0 ? `${typeStr} (${badges.join(', ')})` : typeStr;
634
+
635
+ // Build CSS class
636
+ const cssClasses: string[] = [];
637
+ if (isRequired) cssClasses.push('schema-required');
638
+ else cssClasses.push('schema-optional');
639
+ if (hasDefault) cssClasses.push('schema-has-default');
640
+ if (isDeprecated) cssClasses.push('schema-deprecated');
641
+
642
+ rows.push({
643
+ type: 'property',
644
+ label: name,
645
+ value: valueStr,
646
+ pointer: {
647
+ target: 'response',
648
+ path: `${basePath}/properties/${escapeJsonPointer(name)}`,
649
+ },
650
+ cssClass: cssClasses.join(' '),
651
+ });
652
+ });
653
+
654
+ return rows;
655
+ }
656
+
657
+ // ============================================================================
658
+ // initialize Summary Handler
659
+ // ============================================================================
660
+
661
+ interface InitializeResult {
662
+ protocolVersion?: string;
663
+ serverInfo?: {
664
+ name?: string;
665
+ version?: string;
666
+ };
667
+ capabilities?: Record<string, unknown>;
668
+ }
669
+
670
+ interface InitializeRequest {
671
+ params?: {
672
+ protocolVersion?: string;
673
+ capabilities?: Record<string, unknown>;
674
+ clientInfo?: {
675
+ name?: string;
676
+ version?: string;
677
+ };
678
+ };
679
+ }
680
+
681
+ /**
682
+ * Register initialize handler with separate request/response renderers
683
+ */
684
+ registerMethodHandler({
685
+ method: 'initialize',
686
+ render: (request: unknown, response: unknown): SummaryRow[] => {
687
+ // Combined view (for backward compatibility)
688
+ const reqRows = renderInitializeRequest(request);
689
+ const resRows = renderInitializeResponse(response);
690
+ return [...reqRows, ...resRows];
691
+ },
692
+ renderRequest: (request: unknown): SummaryRow[] => {
693
+ return renderInitializeRequest(request);
694
+ },
695
+ renderResponse: (response: unknown): SummaryRow[] => {
696
+ return renderInitializeResponse(response);
697
+ },
698
+ } as MethodSummaryHandlerExtended);
699
+
700
+ /**
701
+ * Render initialize request summary
702
+ */
703
+ function renderInitializeRequest(request: unknown): SummaryRow[] {
704
+ const rows: SummaryRow[] = [];
705
+ const req = request as InitializeRequest | null;
706
+
707
+ rows.push({
708
+ type: 'header',
709
+ label: 'Method: initialize',
710
+ cssClass: 'summary-method-header',
711
+ });
712
+
713
+ const params = req?.params;
714
+ if (!params) {
715
+ rows.push({
716
+ type: 'property',
717
+ label: '(no parameters)',
718
+ cssClass: 'summary-empty',
719
+ });
720
+ return rows;
721
+ }
722
+
723
+ // Protocol Version
724
+ if (params.protocolVersion) {
725
+ rows.push({
726
+ type: 'header',
727
+ label: 'Protocol',
728
+ cssClass: 'summary-section-header',
729
+ });
730
+ rows.push({
731
+ type: 'property',
732
+ label: 'protocolVersion',
733
+ value: params.protocolVersion,
734
+ pointer: {
735
+ target: 'request',
736
+ path: '#/params/protocolVersion',
737
+ },
738
+ });
739
+ }
740
+
741
+ // Client Info
742
+ if (params.clientInfo) {
743
+ rows.push({
744
+ type: 'header',
745
+ label: 'Client Info',
746
+ cssClass: 'summary-section-header',
747
+ });
748
+ if (params.clientInfo.name) {
749
+ rows.push({
750
+ type: 'property',
751
+ label: 'name',
752
+ value: params.clientInfo.name,
753
+ pointer: {
754
+ target: 'request',
755
+ path: '#/params/clientInfo/name',
756
+ },
757
+ });
758
+ }
759
+ if (params.clientInfo.version) {
760
+ rows.push({
761
+ type: 'property',
762
+ label: 'version',
763
+ value: params.clientInfo.version,
764
+ pointer: {
765
+ target: 'request',
766
+ path: '#/params/clientInfo/version',
767
+ },
768
+ });
769
+ }
770
+ }
771
+
772
+ // Client Capabilities
773
+ if (params.capabilities) {
774
+ rows.push({
775
+ type: 'header',
776
+ label: 'Client Capabilities',
777
+ cssClass: 'summary-section-header',
778
+ });
779
+ renderCapabilitiesRows(params.capabilities, '#/params/capabilities', 'request', rows);
780
+ }
781
+
782
+ return rows;
783
+ }
784
+
785
+ /**
786
+ * Render initialize response summary
787
+ */
788
+ function renderInitializeResponse(response: unknown): SummaryRow[] {
789
+ const rows: SummaryRow[] = [];
790
+ const res = response as { result?: InitializeResult; error?: unknown } | null;
791
+
792
+ rows.push({
793
+ type: 'header',
794
+ label: 'Method: initialize',
795
+ cssClass: 'summary-method-header',
796
+ });
797
+
798
+ if (res?.error) {
799
+ rows.push({
800
+ type: 'header',
801
+ label: 'Error',
802
+ cssClass: 'summary-section-header summary-error',
803
+ });
804
+ rows.push({
805
+ type: 'property',
806
+ label: 'error',
807
+ value: summarizeValue(res.error),
808
+ pointer: {
809
+ target: 'response',
810
+ path: '#/error',
811
+ },
812
+ cssClass: 'summary-error-item',
813
+ });
814
+ return rows;
815
+ }
816
+
817
+ const result = res?.result;
818
+ if (!result) {
819
+ rows.push({
820
+ type: 'property',
821
+ label: '(pending or no response)',
822
+ cssClass: 'summary-empty',
823
+ });
824
+ return rows;
825
+ }
826
+
827
+ // Protocol Version
828
+ if (result.protocolVersion) {
829
+ rows.push({
830
+ type: 'header',
831
+ label: 'Protocol',
832
+ cssClass: 'summary-section-header',
833
+ });
834
+ rows.push({
835
+ type: 'property',
836
+ label: 'protocolVersion',
837
+ value: result.protocolVersion,
838
+ pointer: {
839
+ target: 'response',
840
+ path: '#/result/protocolVersion',
841
+ },
842
+ });
843
+ }
844
+
845
+ // Server Info
846
+ if (result.serverInfo) {
847
+ rows.push({
848
+ type: 'header',
849
+ label: 'Server Info',
850
+ cssClass: 'summary-section-header',
851
+ });
852
+ if (result.serverInfo.name) {
853
+ rows.push({
854
+ type: 'property',
855
+ label: 'name',
856
+ value: result.serverInfo.name,
857
+ pointer: {
858
+ target: 'response',
859
+ path: '#/result/serverInfo/name',
860
+ },
861
+ });
862
+ }
863
+ if (result.serverInfo.version) {
864
+ rows.push({
865
+ type: 'property',
866
+ label: 'version',
867
+ value: result.serverInfo.version,
868
+ pointer: {
869
+ target: 'response',
870
+ path: '#/result/serverInfo/version',
871
+ },
872
+ });
873
+ }
874
+ }
875
+
876
+ // Server Capabilities
877
+ if (result.capabilities) {
878
+ rows.push({
879
+ type: 'header',
880
+ label: 'Server Capabilities',
881
+ cssClass: 'summary-section-header',
882
+ });
883
+ renderCapabilitiesRows(result.capabilities, '#/result/capabilities', 'response', rows);
884
+ }
885
+
886
+ return rows;
887
+ }
888
+
889
+ /**
890
+ * Render capabilities object as summary rows
891
+ */
892
+ function renderCapabilitiesRows(
893
+ capabilities: Record<string, unknown>,
894
+ basePath: string,
895
+ target: 'request' | 'response',
896
+ rows: SummaryRow[]
897
+ ): void {
898
+ Object.entries(capabilities).forEach(([key, value]) => {
899
+ const path = `${basePath}/${escapeJsonPointer(key)}`;
900
+
901
+ // Determine if capability is enabled
902
+ let displayValue: string;
903
+ let cssClass = '';
904
+
905
+ if (value === undefined || value === null) {
906
+ displayValue = 'disabled';
907
+ cssClass = 'capability-disabled';
908
+ } else if (typeof value === 'boolean') {
909
+ displayValue = value ? 'enabled' : 'disabled';
910
+ cssClass = value ? 'capability-enabled' : 'capability-disabled';
911
+ } else if (typeof value === 'object') {
912
+ // Has options - show as enabled with details
913
+ const optionCount = Object.keys(value as object).length;
914
+ displayValue = optionCount > 0 ? `enabled (${optionCount} options)` : 'enabled';
915
+ cssClass = 'capability-enabled';
916
+ } else {
917
+ displayValue = String(value);
918
+ }
919
+
920
+ rows.push({
921
+ type: 'property',
922
+ label: key,
923
+ value: displayValue,
924
+ pointer: {
925
+ target,
926
+ path,
927
+ },
928
+ cssClass,
929
+ });
930
+ });
931
+ }
932
+
933
+ // ============================================================================
934
+ // Summary Row HTML Renderer
935
+ // ============================================================================
936
+
937
+ /**
938
+ * Render summary rows to HTML
939
+ * Note: Join without newlines to avoid JS string syntax errors when embedded in templates
940
+ */
941
+ export function renderSummaryRowsHtml(rows: SummaryRow[]): string {
942
+ const html: string[] = [];
943
+
944
+ for (const row of rows) {
945
+ html.push(renderSummaryRow(row));
946
+ }
947
+
948
+ return html.join('');
949
+ }
950
+
951
+ /**
952
+ * Render a single summary row
953
+ */
954
+ function renderSummaryRow(row: SummaryRow): string {
955
+ const cssClass = row.cssClass || '';
956
+ const pointerAttrs = row.pointer
957
+ ? ` data-pointer-target="${row.pointer.target}" data-pointer-path="${escapeAttr(row.pointer.path)}"`
958
+ : '';
959
+ const clickable = row.pointer ? ' clickable' : '';
960
+ // Add title attribute for tooltip on truncated values
961
+ const valueTitle = row.value ? ` title="${escapeAttr(row.value)}"` : '';
962
+
963
+ if (row.type === 'header') {
964
+ // Add collapse controls if header has collapsible class
965
+ if (cssClass.includes('summary-collapsible-header')) {
966
+ // Single line to avoid JS string syntax issues when embedded in templates
967
+ return `<div class="summary-row summary-header ${cssClass}"><span>${escapeHtml(row.label)}</span><span class="collapse-controls"><button class="collapse-btn collapse-all" title="Collapse all">−</button><button class="collapse-btn expand-all" title="Expand all">+</button></span></div>`;
968
+ }
969
+ return `<div class="summary-row summary-header ${cssClass}">${escapeHtml(row.label)}</div>`;
970
+ }
971
+
972
+ if (row.type === 'item') {
973
+ const hasChildren = row.children && row.children.length > 0;
974
+ let childHtml = '';
975
+ let toggleHtml = '';
976
+
977
+ if (hasChildren) {
978
+ // Join without newlines to avoid JS string syntax errors
979
+ childHtml = `<div class="summary-children">${row.children!.map((c) => renderSummaryRow(c)).join('')}</div>`;
980
+ toggleHtml = '<span class="item-toggle" title="Toggle properties">▼</span>';
981
+ }
982
+
983
+ // Single line to avoid JS string syntax issues
984
+ return `<div class="summary-row summary-item ${cssClass}${clickable}${hasChildren ? ' has-children' : ''}"${pointerAttrs}>${toggleHtml}<span class="summary-label">${escapeHtml(row.label)}</span><span class="summary-value"${valueTitle}>${escapeHtml(row.value || '')}</span></div>${childHtml}`;
985
+ }
986
+
987
+ // property type - single line to avoid JS string syntax issues
988
+ return `<div class="summary-row summary-property ${cssClass}${clickable}"${pointerAttrs}><span class="summary-prop-name">${escapeHtml(row.label)}</span><span class="summary-prop-value"${valueTitle}>${escapeHtml(row.value || '')}</span></div>`;
989
+ }
990
+
991
+ // ============================================================================
992
+ // Sensitive Key Detection (Phase 12.x-c)
993
+ // ============================================================================
994
+
995
+ /**
996
+ * Patterns for detecting sensitive keys in JSON data
997
+ * These patterns match common names for authentication, secrets, and credentials
998
+ */
999
+ const SENSITIVE_PATTERNS: RegExp[] = [
1000
+ /authorization/i,
1001
+ /api[_-]?key/i,
1002
+ /token/i,
1003
+ /secret/i,
1004
+ /password/i,
1005
+ /private[_-]?key/i,
1006
+ /bearer/i,
1007
+ /credential/i,
1008
+ /signature/i,
1009
+ /access[_-]?token/i,
1010
+ /refresh[_-]?token/i,
1011
+ /session[_-]?id/i,
1012
+ /cookie/i,
1013
+ /^auth$/i, // Exact match for 'auth' key (avoids 'author', 'authorized_users')
1014
+ /^auth_/i, // Prefix match for auth_token, auth_header, etc.
1015
+ /client[_-]?secret/i,
1016
+ /jwt/i,
1017
+ /oauth/i,
1018
+ /x-api-key/i,
1019
+ /x-auth/i,
1020
+ ];
1021
+
1022
+ /**
1023
+ * Detect sensitive keys in JSON data
1024
+ * Walks the object tree and returns paths to keys matching sensitive patterns
1025
+ *
1026
+ * @param json - The JSON object to scan
1027
+ * @returns Array of paths to sensitive keys (e.g., "headers.authorization")
1028
+ */
1029
+ export function detectSensitiveKeys(json: unknown): string[] {
1030
+ const found: string[] = [];
1031
+
1032
+ function walk(obj: unknown, path: string = ''): void {
1033
+ if (!obj || typeof obj !== 'object') return;
1034
+
1035
+ if (Array.isArray(obj)) {
1036
+ obj.forEach((v, i) => walk(v, path ? `${path}[${i}]` : `[${i}]`));
1037
+ } else {
1038
+ Object.entries(obj as Record<string, unknown>).forEach(([k, v]) => {
1039
+ const currentPath = path ? `${path}.${k}` : k;
1040
+ if (SENSITIVE_PATTERNS.some((pat) => pat.test(k))) {
1041
+ found.push(currentPath);
1042
+ }
1043
+ walk(v, currentPath);
1044
+ });
1045
+ }
1046
+ }
1047
+
1048
+ walk(json);
1049
+ return found;
1050
+ }
1051
+
1052
+ /**
1053
+ * Check if JSON contains any sensitive keys
1054
+ *
1055
+ * @param json - The JSON object to check
1056
+ * @returns true if sensitive keys are detected
1057
+ */
1058
+ export function hasSensitiveContent(json: unknown): boolean {
1059
+ return detectSensitiveKeys(json).length > 0;
1060
+ }
1061
+
1062
+ // ============================================================================
1063
+ // CSS Styles for RPC Inspector
1064
+ // ============================================================================
1065
+
1066
+ /**
1067
+ * Get RPC Inspector CSS styles
1068
+ */
1069
+ export function getRpcInspectorStyles(): string {
1070
+ return `
1071
+ /* RPC Info horizontal layout */
1072
+ .rpc-info-grid {
1073
+ display: flex;
1074
+ flex-wrap: wrap;
1075
+ gap: 16px 32px;
1076
+ align-items: center;
1077
+ margin-bottom: 16px;
1078
+ }
1079
+
1080
+ .rpc-info-item {
1081
+ display: flex;
1082
+ align-items: center;
1083
+ gap: 8px;
1084
+ }
1085
+
1086
+ .rpc-info-item dt {
1087
+ font-size: 12px;
1088
+ color: var(--text-secondary);
1089
+ font-weight: 400;
1090
+ }
1091
+
1092
+ .rpc-info-item dd {
1093
+ margin: 0;
1094
+ }
1095
+
1096
+ /* Right pane layout - RPC Info header fixed, inspector scrollable */
1097
+ .right-pane {
1098
+ display: flex;
1099
+ flex-direction: column;
1100
+ overflow: hidden;
1101
+ }
1102
+
1103
+ /* Container for pre-rendered RPC details */
1104
+ .rpc-details-container {
1105
+ flex: 1;
1106
+ position: relative;
1107
+ min-height: 0;
1108
+ overflow: hidden;
1109
+ }
1110
+
1111
+ /* Individual RPC detail wrapper - absolute positioned */
1112
+ .rpc-detail-content {
1113
+ position: absolute;
1114
+ top: 0;
1115
+ left: 0;
1116
+ right: 0;
1117
+ bottom: 0;
1118
+ display: flex;
1119
+ flex-direction: column;
1120
+ overflow: hidden;
1121
+ }
1122
+
1123
+ /* RPC Info header section - fixed at top */
1124
+ .rpc-detail-content > .detail-section:first-child {
1125
+ flex-shrink: 0;
1126
+ overflow: visible;
1127
+ }
1128
+
1129
+ /* Inspector section - takes remaining space */
1130
+ .rpc-detail-content > .detail-section:last-child {
1131
+ flex: 1;
1132
+ display: flex;
1133
+ flex-direction: column;
1134
+ min-height: 0;
1135
+ overflow: hidden;
1136
+ margin-bottom: 0;
1137
+ }
1138
+
1139
+ /* 2-column RPC detail layout - each column scrolls independently */
1140
+ .rpc-inspector {
1141
+ display: flex;
1142
+ gap: 16px;
1143
+ flex: 1;
1144
+ min-height: 0;
1145
+ overflow: hidden;
1146
+ }
1147
+
1148
+ /* Summary pane - independent vertical scroll */
1149
+ .rpc-inspector-summary {
1150
+ flex: 0 0 320px;
1151
+ max-width: 400px;
1152
+ overflow-y: auto;
1153
+ overflow-x: hidden;
1154
+ border-right: 1px solid var(--border-color);
1155
+ padding-right: 16px;
1156
+ overscroll-behavior: contain;
1157
+ }
1158
+
1159
+ .rpc-inspector-summary h3 {
1160
+ position: sticky;
1161
+ top: 0;
1162
+ background: var(--bg-secondary);
1163
+ padding: 8px 0;
1164
+ margin: 0 0 8px 0;
1165
+ z-index: 1;
1166
+ }
1167
+
1168
+ /* RAW pane - independent vertical scroll */
1169
+ .rpc-inspector-raw {
1170
+ flex: 1;
1171
+ min-width: 0;
1172
+ display: flex;
1173
+ flex-direction: column;
1174
+ min-height: 0;
1175
+ }
1176
+
1177
+ /* Toggle buttons */
1178
+ .rpc-toggle-bar {
1179
+ display: flex;
1180
+ gap: 8px;
1181
+ margin-bottom: 12px;
1182
+ flex-shrink: 0;
1183
+ }
1184
+
1185
+ .rpc-toggle-btn {
1186
+ padding: 6px 16px;
1187
+ background: var(--bg-secondary);
1188
+ border: 1px solid var(--border-color);
1189
+ border-radius: 4px;
1190
+ color: var(--text-secondary);
1191
+ cursor: pointer;
1192
+ font-size: 12px;
1193
+ font-weight: 500;
1194
+ transition: all 0.15s;
1195
+ }
1196
+
1197
+ .rpc-toggle-btn:hover {
1198
+ border-color: var(--accent-blue);
1199
+ color: var(--text-primary);
1200
+ }
1201
+
1202
+ .rpc-toggle-btn.active {
1203
+ background: rgba(0, 212, 255, 0.15);
1204
+ border-color: var(--accent-blue);
1205
+ color: var(--accent-blue);
1206
+ }
1207
+
1208
+ /* Raw JSON container - independent scroll */
1209
+ .rpc-raw-json {
1210
+ flex: 1;
1211
+ min-height: 0;
1212
+ overflow-y: auto;
1213
+ overflow-x: auto;
1214
+ overscroll-behavior: contain;
1215
+ background: var(--bg-primary);
1216
+ border: 1px solid var(--border-color);
1217
+ border-radius: 6px;
1218
+ padding: 12px;
1219
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
1220
+ font-size: 12px;
1221
+ line-height: 1.6;
1222
+ }
1223
+
1224
+ /* JSON line styling */
1225
+ .json-line {
1226
+ display: block;
1227
+ white-space: pre;
1228
+ padding: 1px 4px;
1229
+ border-radius: 2px;
1230
+ transition: background-color 0.15s;
1231
+ }
1232
+
1233
+ .json-line:hover {
1234
+ background: rgba(0, 212, 255, 0.05);
1235
+ }
1236
+
1237
+ .json-line.highlighted {
1238
+ background: rgba(0, 212, 255, 0.2);
1239
+ outline: 1px solid var(--accent-blue);
1240
+ }
1241
+
1242
+ /* JSON syntax highlighting */
1243
+ .json-key { color: #79c0ff; }
1244
+ .json-string { color: #a5d6ff; }
1245
+ .json-number { color: #ffa657; }
1246
+ .json-bool { color: #ff7b72; }
1247
+ .json-null { color: var(--text-secondary); }
1248
+ .json-bracket { color: var(--text-secondary); }
1249
+
1250
+ /* Summary view styles */
1251
+ .summary-row {
1252
+ padding: 4px 0;
1253
+ }
1254
+
1255
+ .summary-header {
1256
+ font-weight: 600;
1257
+ font-size: 14px;
1258
+ color: var(--text-primary);
1259
+ padding: 8px 0 4px 0;
1260
+ border-bottom: 1px solid var(--border-color);
1261
+ margin-bottom: 8px;
1262
+ margin-top: 12px;
1263
+ }
1264
+
1265
+ .summary-header:first-child {
1266
+ margin-top: 0;
1267
+ }
1268
+
1269
+ .summary-table-header {
1270
+ font-size: 13px;
1271
+ }
1272
+
1273
+ .summary-item {
1274
+ display: flex;
1275
+ justify-content: space-between;
1276
+ align-items: flex-start;
1277
+ padding: 8px 12px;
1278
+ margin: 4px 0;
1279
+ background: var(--bg-secondary);
1280
+ border: 1px solid transparent;
1281
+ border-radius: 4px;
1282
+ transition: border-color 0.15s, background 0.15s;
1283
+ }
1284
+
1285
+ .summary-item.clickable {
1286
+ cursor: pointer;
1287
+ }
1288
+
1289
+ .summary-item.clickable:hover {
1290
+ border-color: var(--accent-blue);
1291
+ background: rgba(0, 212, 255, 0.05);
1292
+ }
1293
+
1294
+ .summary-item.selected {
1295
+ border-color: var(--accent-blue);
1296
+ background: rgba(0, 212, 255, 0.1);
1297
+ }
1298
+
1299
+ .summary-label {
1300
+ font-family: 'SFMono-Regular', Consolas, monospace;
1301
+ font-size: 13px;
1302
+ color: var(--accent-blue);
1303
+ font-weight: 500;
1304
+ }
1305
+
1306
+ .summary-value {
1307
+ font-size: 12px;
1308
+ color: var(--text-secondary);
1309
+ max-width: 55%;
1310
+ overflow: hidden;
1311
+ text-overflow: ellipsis;
1312
+ white-space: nowrap;
1313
+ text-align: right;
1314
+ }
1315
+
1316
+ /* Schema properties (children) */
1317
+ .summary-children {
1318
+ margin-left: 16px;
1319
+ padding-left: 12px;
1320
+ border-left: 2px solid var(--border-color);
1321
+ margin-top: 4px;
1322
+ }
1323
+
1324
+ .summary-property {
1325
+ display: flex;
1326
+ gap: 8px;
1327
+ padding: 4px 8px;
1328
+ margin: 2px 0;
1329
+ font-size: 12px;
1330
+ border-radius: 3px;
1331
+ transition: background 0.15s;
1332
+ }
1333
+
1334
+ .summary-property.clickable {
1335
+ cursor: pointer;
1336
+ }
1337
+
1338
+ .summary-property.clickable:hover {
1339
+ background: rgba(0, 212, 255, 0.05);
1340
+ }
1341
+
1342
+ .summary-prop-name {
1343
+ font-family: 'SFMono-Regular', Consolas, monospace;
1344
+ color: var(--text-primary);
1345
+ }
1346
+
1347
+ .schema-required .summary-prop-name::after {
1348
+ content: '*';
1349
+ color: #f85149;
1350
+ margin-left: 2px;
1351
+ }
1352
+
1353
+ .schema-required .summary-prop-name {
1354
+ font-weight: 600;
1355
+ }
1356
+
1357
+ .schema-has-default .summary-prop-value::before {
1358
+ content: '=';
1359
+ color: #7ee787;
1360
+ margin-right: 4px;
1361
+ font-weight: 500;
1362
+ }
1363
+
1364
+ .schema-deprecated {
1365
+ opacity: 0.6;
1366
+ }
1367
+
1368
+ .schema-deprecated .summary-prop-name {
1369
+ text-decoration: line-through;
1370
+ color: #f0883e;
1371
+ }
1372
+
1373
+ .schema-deprecated .summary-prop-value::after {
1374
+ content: 'deprecated';
1375
+ background: rgba(240, 136, 62, 0.2);
1376
+ color: #f0883e;
1377
+ font-size: 10px;
1378
+ padding: 1px 4px;
1379
+ border-radius: 3px;
1380
+ margin-left: 6px;
1381
+ }
1382
+
1383
+ .summary-prop-value {
1384
+ color: var(--text-secondary);
1385
+ }
1386
+
1387
+ /* Collapse controls */
1388
+ .summary-collapsible-header {
1389
+ display: flex;
1390
+ justify-content: space-between;
1391
+ align-items: center;
1392
+ }
1393
+
1394
+ .collapse-controls {
1395
+ display: flex;
1396
+ gap: 4px;
1397
+ }
1398
+
1399
+ .collapse-btn {
1400
+ width: 20px;
1401
+ height: 20px;
1402
+ border: 1px solid var(--border-color);
1403
+ border-radius: 3px;
1404
+ background: var(--bg-secondary);
1405
+ color: var(--text-secondary);
1406
+ cursor: pointer;
1407
+ font-size: 12px;
1408
+ line-height: 1;
1409
+ display: flex;
1410
+ align-items: center;
1411
+ justify-content: center;
1412
+ }
1413
+
1414
+ .collapse-btn:hover {
1415
+ border-color: var(--accent-blue);
1416
+ color: var(--accent-blue);
1417
+ }
1418
+
1419
+ /* Item toggle */
1420
+ .item-toggle {
1421
+ cursor: pointer;
1422
+ color: var(--text-secondary);
1423
+ font-size: 10px;
1424
+ margin-right: 8px;
1425
+ transition: transform 0.15s;
1426
+ user-select: none;
1427
+ }
1428
+
1429
+ .summary-item.collapsed .item-toggle {
1430
+ transform: rotate(-90deg);
1431
+ }
1432
+
1433
+ .summary-item.collapsed + .summary-children {
1434
+ display: none;
1435
+ }
1436
+
1437
+ /* Error styling */
1438
+ .summary-error {
1439
+ color: #f85149;
1440
+ }
1441
+
1442
+ .summary-error-item {
1443
+ border-left: 3px solid #f85149;
1444
+ }
1445
+
1446
+ /* Empty state styling */
1447
+ .summary-empty {
1448
+ color: var(--text-secondary);
1449
+ font-style: italic;
1450
+ }
1451
+
1452
+ /* Capability styling (for initialize method) */
1453
+ .capability-enabled {
1454
+ color: var(--accent-blue);
1455
+ }
1456
+
1457
+ .capability-enabled .summary-prop-value {
1458
+ color: #3fb950;
1459
+ }
1460
+
1461
+ .capability-disabled {
1462
+ color: var(--text-secondary);
1463
+ opacity: 0.6;
1464
+ }
1465
+
1466
+ /* No-JS fallback */
1467
+ noscript + .rpc-toggle-bar {
1468
+ display: none;
1469
+ }
1470
+ `;
1471
+ }
1472
+
1473
+ // ============================================================================
1474
+ // JavaScript for RPC Inspector Interaction
1475
+ // ============================================================================
1476
+
1477
+ /**
1478
+ * Get RPC Inspector JavaScript
1479
+ * Updated to work with both ID-based (single RPC) and class-based (pre-rendered) DOM structures
1480
+ */
1481
+ export function getRpcInspectorScript(): string {
1482
+ return `
1483
+ // RPC Inspector - Toggle and Navigation
1484
+ (function() {
1485
+ let currentTarget = 'request';
1486
+
1487
+ // Get the currently visible RPC detail container
1488
+ function getVisibleDetail() {
1489
+ // For pre-rendered multiple RPC details (Connector page)
1490
+ const visibleDetail = document.querySelector('.rpc-detail-content[style*="display: block"]');
1491
+ if (visibleDetail) return visibleDetail;
1492
+ // Fallback for single RPC view
1493
+ return document;
1494
+ }
1495
+
1496
+ // Initialize toggle buttons
1497
+ function initInspectorToggle() {
1498
+ // ID-based (legacy single-RPC view)
1499
+ const reqBtnById = document.getElementById('toggle-req');
1500
+ const resBtnById = document.getElementById('toggle-res');
1501
+ if (reqBtnById) {
1502
+ reqBtnById.addEventListener('click', function() { switchTarget('request'); });
1503
+ }
1504
+ if (resBtnById) {
1505
+ resBtnById.addEventListener('click', function() { switchTarget('response'); });
1506
+ }
1507
+
1508
+ // Class-based with data-target (pre-rendered multi-RPC view)
1509
+ document.querySelectorAll('.rpc-toggle-btn[data-target]').forEach(function(btn) {
1510
+ btn.addEventListener('click', function() {
1511
+ const target = btn.dataset.target;
1512
+ const container = btn.closest('.rpc-detail-content') || document;
1513
+ switchTargetInContainer(target, container);
1514
+ });
1515
+ });
1516
+ }
1517
+
1518
+ // Switch between request and response view (both Summary and Raw JSON) - global
1519
+ function switchTarget(target) {
1520
+ currentTarget = target;
1521
+
1522
+ // Update button states (ID-based)
1523
+ const reqBtn = document.getElementById('toggle-req');
1524
+ const resBtn = document.getElementById('toggle-res');
1525
+ if (reqBtn) reqBtn.classList.toggle('active', target === 'request');
1526
+ if (resBtn) resBtn.classList.toggle('active', target === 'response');
1527
+
1528
+ // Update Summary display (ID-based)
1529
+ const reqSummary = document.getElementById('summary-request');
1530
+ const resSummary = document.getElementById('summary-response');
1531
+ if (reqSummary) reqSummary.style.display = target === 'request' ? 'block' : 'none';
1532
+ if (resSummary) resSummary.style.display = target === 'response' ? 'block' : 'none';
1533
+
1534
+ // Update raw JSON display (ID-based)
1535
+ const reqJson = document.getElementById('raw-json-request');
1536
+ const resJson = document.getElementById('raw-json-response');
1537
+ if (reqJson) reqJson.style.display = target === 'request' ? 'block' : 'none';
1538
+ if (resJson) resJson.style.display = target === 'response' ? 'block' : 'none';
1539
+ }
1540
+
1541
+ // Switch target within a specific container (for pre-rendered multi-RPC)
1542
+ function switchTargetInContainer(target, container) {
1543
+ currentTarget = target;
1544
+
1545
+ // Update button states within container
1546
+ container.querySelectorAll('.rpc-toggle-btn[data-target]').forEach(function(btn) {
1547
+ btn.classList.toggle('active', btn.dataset.target === target);
1548
+ });
1549
+
1550
+ // Update Summary display (class-based)
1551
+ const reqSummary = container.querySelector('.summary-request');
1552
+ const resSummary = container.querySelector('.summary-response');
1553
+ if (reqSummary) reqSummary.style.display = target === 'request' ? 'block' : 'none';
1554
+ if (resSummary) resSummary.style.display = target === 'response' ? 'block' : 'none';
1555
+
1556
+ // Update raw JSON display (class-based)
1557
+ const reqJson = container.querySelector('.raw-json-request');
1558
+ const resJson = container.querySelector('.raw-json-response');
1559
+ if (reqJson) reqJson.style.display = target === 'request' ? 'block' : 'none';
1560
+ if (resJson) resJson.style.display = target === 'response' ? 'block' : 'none';
1561
+ }
1562
+
1563
+ // Navigate to JSON path and highlight
1564
+ function navigateToPath(target, path, container) {
1565
+ container = container || getVisibleDetail();
1566
+
1567
+ // Switch to correct target first
1568
+ if (container === document) {
1569
+ switchTarget(target);
1570
+ } else {
1571
+ switchTargetInContainer(target, container);
1572
+ }
1573
+
1574
+ // Find container for this target (class-based or ID-based)
1575
+ var jsonContainer = container.querySelector('.raw-json-' + target) || document.getElementById('raw-json-' + target);
1576
+ if (!jsonContainer) return;
1577
+
1578
+ // Clear previous highlights
1579
+ jsonContainer.querySelectorAll('.highlighted').forEach(function(el) {
1580
+ el.classList.remove('highlighted');
1581
+ });
1582
+
1583
+ // Find and highlight the target line
1584
+ var targetLine = jsonContainer.querySelector('[data-path="' + path + '"]');
1585
+ if (targetLine) {
1586
+ targetLine.classList.add('highlighted');
1587
+ targetLine.scrollIntoView({ behavior: 'smooth', block: 'center' });
1588
+ }
1589
+ }
1590
+
1591
+ // Summary row click handler
1592
+ function initSummaryClicks() {
1593
+ document.querySelectorAll('[data-pointer-target]').forEach(function(el) {
1594
+ el.addEventListener('click', function(e) {
1595
+ // Don't navigate if clicking on toggle
1596
+ if (e.target.classList.contains('item-toggle')) return;
1597
+
1598
+ var target = el.dataset.pointerTarget;
1599
+ var path = el.dataset.pointerPath;
1600
+ if (target && path) {
1601
+ var container = el.closest('.rpc-detail-content') || document;
1602
+ navigateToPath(target, path, container);
1603
+
1604
+ // Mark as selected within the same container
1605
+ container.querySelectorAll('.summary-item.selected, .summary-property.selected').forEach(function(s) {
1606
+ s.classList.remove('selected');
1607
+ });
1608
+ el.classList.add('selected');
1609
+ }
1610
+ });
1611
+ });
1612
+ }
1613
+
1614
+ // Item toggle (expand/collapse single tool)
1615
+ function initItemToggles() {
1616
+ document.querySelectorAll('.summary-item.has-children .item-toggle').forEach(function(toggle) {
1617
+ toggle.addEventListener('click', function(e) {
1618
+ e.stopPropagation();
1619
+ var item = toggle.closest('.summary-item');
1620
+ if (item) {
1621
+ item.classList.toggle('collapsed');
1622
+ }
1623
+ });
1624
+ });
1625
+ }
1626
+
1627
+ // Collapse/Expand all buttons
1628
+ function initCollapseControls() {
1629
+ document.querySelectorAll('.collapse-all').forEach(function(btn) {
1630
+ btn.addEventListener('click', function() {
1631
+ var container = btn.closest('.rpc-detail-content') || document;
1632
+ container.querySelectorAll('.summary-item.has-children').forEach(function(item) {
1633
+ item.classList.add('collapsed');
1634
+ });
1635
+ });
1636
+ });
1637
+
1638
+ document.querySelectorAll('.expand-all').forEach(function(btn) {
1639
+ btn.addEventListener('click', function() {
1640
+ var container = btn.closest('.rpc-detail-content') || document;
1641
+ container.querySelectorAll('.summary-item.has-children').forEach(function(item) {
1642
+ item.classList.remove('collapsed');
1643
+ });
1644
+ });
1645
+ });
1646
+ }
1647
+
1648
+ // Prevent scroll chaining between Summary and RAW panes
1649
+ function initScrollIsolation() {
1650
+ document.querySelectorAll('.rpc-inspector-summary, .rpc-raw-json').forEach(function(pane) {
1651
+ pane.addEventListener('wheel', function(e) {
1652
+ var atTop = pane.scrollTop === 0;
1653
+ var atBottom = pane.scrollTop + pane.clientHeight >= pane.scrollHeight;
1654
+
1655
+ // If scrolling up at top or scrolling down at bottom, prevent propagation
1656
+ if ((e.deltaY < 0 && atTop) || (e.deltaY > 0 && atBottom)) {
1657
+ e.preventDefault();
1658
+ }
1659
+ }, { passive: false });
1660
+ });
1661
+ }
1662
+
1663
+ // Set max-height dynamically based on available space
1664
+ function initPaneHeights() {
1665
+ function updateHeights() {
1666
+ document.querySelectorAll('.rpc-inspector').forEach(function(inspector) {
1667
+ var rect = inspector.getBoundingClientRect();
1668
+ var availableHeight = window.innerHeight - rect.top - 20;
1669
+ if (availableHeight > 200) {
1670
+ inspector.style.maxHeight = availableHeight + 'px';
1671
+ var summary = inspector.querySelector('.rpc-inspector-summary');
1672
+ var rawJson = inspector.querySelector('.rpc-raw-json');
1673
+ if (summary) summary.style.maxHeight = availableHeight + 'px';
1674
+ if (rawJson) rawJson.style.maxHeight = (availableHeight - 50) + 'px';
1675
+ }
1676
+ });
1677
+ }
1678
+ updateHeights();
1679
+ window.addEventListener('resize', updateHeights);
1680
+ }
1681
+
1682
+ // Expose for re-initialization after dynamic content update
1683
+ window.initRpcInspector = function() {
1684
+ initInspectorToggle();
1685
+ initSummaryClicks();
1686
+ initItemToggles();
1687
+ initCollapseControls();
1688
+ initScrollIsolation();
1689
+ initPaneHeights();
1690
+ };
1691
+
1692
+ // Initialize on DOM ready
1693
+ if (document.readyState === 'loading') {
1694
+ document.addEventListener('DOMContentLoaded', window.initRpcInspector);
1695
+ } else {
1696
+ window.initRpcInspector();
1697
+ }
1698
+ })();
1699
+ `;
1700
+ }