@noodleseed/one 0.8.0 → 0.10.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 (165) hide show
  1. package/dist/commands/author-loop.d.ts.map +1 -1
  2. package/dist/commands/author-loop.js +78 -3
  3. package/dist/commands/author-loop.js.map +1 -1
  4. package/dist/commands/deploy-ops.d.ts +1 -30
  5. package/dist/commands/deploy-ops.d.ts.map +1 -1
  6. package/dist/commands/deploy-ops.js +55 -87
  7. package/dist/commands/deploy-ops.js.map +1 -1
  8. package/dist/commands/deploy-output.d.ts +31 -0
  9. package/dist/commands/deploy-output.d.ts.map +1 -0
  10. package/dist/commands/deploy-output.js +86 -0
  11. package/dist/commands/deploy-output.js.map +1 -0
  12. package/dist/commands/deploy-version-resolution.d.ts +20 -0
  13. package/dist/commands/deploy-version-resolution.d.ts.map +1 -0
  14. package/dist/commands/deploy-version-resolution.js +77 -0
  15. package/dist/commands/deploy-version-resolution.js.map +1 -0
  16. package/dist/commands/docs-mcp.d.ts +32 -0
  17. package/dist/commands/docs-mcp.d.ts.map +1 -0
  18. package/dist/commands/docs-mcp.js +66 -0
  19. package/dist/commands/docs-mcp.js.map +1 -0
  20. package/dist/commands/mcp-apps.d.ts.map +1 -1
  21. package/dist/commands/mcp-apps.js +38 -37
  22. package/dist/commands/mcp-apps.js.map +1 -1
  23. package/dist/commands/project-setup.d.ts.map +1 -1
  24. package/dist/commands/project-setup.js +29 -0
  25. package/dist/commands/project-setup.js.map +1 -1
  26. package/dist/commands/shared.d.ts +1 -0
  27. package/dist/commands/shared.d.ts.map +1 -1
  28. package/dist/commands/shared.js +8 -4
  29. package/dist/commands/shared.js.map +1 -1
  30. package/dist/deploy-version.d.ts +3 -0
  31. package/dist/deploy-version.d.ts.map +1 -0
  32. package/dist/deploy-version.js +28 -0
  33. package/dist/deploy-version.js.map +1 -0
  34. package/dist/deploy.d.ts +5 -1
  35. package/dist/deploy.d.ts.map +1 -1
  36. package/dist/deploy.js +27 -3
  37. package/dist/deploy.js.map +1 -1
  38. package/dist/devtools-chat.d.ts +76 -0
  39. package/dist/devtools-chat.d.ts.map +1 -0
  40. package/dist/devtools-chat.js +114 -0
  41. package/dist/devtools-chat.js.map +1 -0
  42. package/dist/devtools-env.d.ts +16 -0
  43. package/dist/devtools-env.d.ts.map +1 -0
  44. package/dist/devtools-env.js +71 -0
  45. package/dist/devtools-env.js.map +1 -0
  46. package/dist/devtools-harness.d.ts +28 -0
  47. package/dist/devtools-harness.d.ts.map +1 -0
  48. package/dist/devtools-harness.js +387 -0
  49. package/dist/devtools-harness.js.map +1 -0
  50. package/dist/devtools-preview.d.ts +42 -0
  51. package/dist/devtools-preview.d.ts.map +1 -0
  52. package/dist/devtools-preview.js +394 -0
  53. package/dist/devtools-preview.js.map +1 -0
  54. package/dist/preview-session.d.ts +46 -0
  55. package/dist/preview-session.d.ts.map +1 -0
  56. package/dist/preview-session.js +132 -0
  57. package/dist/preview-session.js.map +1 -0
  58. package/dist/project.d.ts +2 -0
  59. package/dist/project.d.ts.map +1 -1
  60. package/dist/project.js +2 -0
  61. package/dist/project.js.map +1 -1
  62. package/dist/react-widget-build.js +3 -1
  63. package/dist/react-widget-build.js.map +1 -1
  64. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  65. package/node_modules/@noodle-borg/connector-defs/dist/compile-expr.d.ts +23 -0
  66. package/node_modules/@noodle-borg/connector-defs/dist/compile-expr.d.ts.map +1 -0
  67. package/node_modules/@noodle-borg/connector-defs/dist/compile-expr.js +41 -0
  68. package/node_modules/@noodle-borg/connector-defs/dist/compile-expr.js.map +1 -0
  69. package/node_modules/@noodle-borg/connector-defs/dist/compile.d.ts +2 -6
  70. package/node_modules/@noodle-borg/connector-defs/dist/compile.d.ts.map +1 -1
  71. package/node_modules/@noodle-borg/connector-defs/dist/compile.js +10 -32
  72. package/node_modules/@noodle-borg/connector-defs/dist/compile.js.map +1 -1
  73. package/node_modules/@noodle-borg/connector-defs/dist/schema.d.ts +16 -0
  74. package/node_modules/@noodle-borg/connector-defs/dist/schema.d.ts.map +1 -1
  75. package/node_modules/@noodle-borg/connector-defs/dist/schema.js +6 -0
  76. package/node_modules/@noodle-borg/connector-defs/dist/schema.js.map +1 -1
  77. package/node_modules/@noodle-borg/connector-http/dist/http-connector.d.ts +7 -0
  78. package/node_modules/@noodle-borg/connector-http/dist/http-connector.d.ts.map +1 -1
  79. package/node_modules/@noodle-borg/connector-http/dist/http-connector.js +14 -5
  80. package/node_modules/@noodle-borg/connector-http/dist/http-connector.js.map +1 -1
  81. package/node_modules/@noodle-borg/module/dist/contract.d.ts +12 -0
  82. package/node_modules/@noodle-borg/module/dist/contract.d.ts.map +1 -1
  83. package/node_modules/@noodle-borg/module/dist/contract.js +41 -0
  84. package/node_modules/@noodle-borg/module/dist/contract.js.map +1 -1
  85. package/node_modules/@noodle-borg/service/dist/auth/deploy-gate.d.ts +20 -2
  86. package/node_modules/@noodle-borg/service/dist/auth/deploy-gate.d.ts.map +1 -1
  87. package/node_modules/@noodle-borg/service/dist/auth/deploy-gate.js +37 -2
  88. package/node_modules/@noodle-borg/service/dist/auth/deploy-gate.js.map +1 -1
  89. package/node_modules/@noodle-borg/service/dist/deployment-versioning.d.ts +18 -0
  90. package/node_modules/@noodle-borg/service/dist/deployment-versioning.d.ts.map +1 -0
  91. package/node_modules/@noodle-borg/service/dist/deployment-versioning.js +29 -0
  92. package/node_modules/@noodle-borg/service/dist/deployment-versioning.js.map +1 -0
  93. package/node_modules/@noodle-borg/service/dist/main.js +7 -0
  94. package/node_modules/@noodle-borg/service/dist/main.js.map +1 -1
  95. package/node_modules/@noodle-borg/service/dist/oauth/app.d.ts.map +1 -1
  96. package/node_modules/@noodle-borg/service/dist/oauth/app.js +2 -1
  97. package/node_modules/@noodle-borg/service/dist/oauth/app.js.map +1 -1
  98. package/node_modules/@noodle-borg/service/dist/oauth/branding.d.ts +30 -0
  99. package/node_modules/@noodle-borg/service/dist/oauth/branding.d.ts.map +1 -0
  100. package/node_modules/@noodle-borg/service/dist/oauth/branding.js +224 -0
  101. package/node_modules/@noodle-borg/service/dist/oauth/branding.js.map +1 -0
  102. package/node_modules/@noodle-borg/service/dist/oauth/consent.d.ts +0 -6
  103. package/node_modules/@noodle-borg/service/dist/oauth/consent.d.ts.map +1 -1
  104. package/node_modules/@noodle-borg/service/dist/oauth/consent.js +23 -34
  105. package/node_modules/@noodle-borg/service/dist/oauth/consent.js.map +1 -1
  106. package/node_modules/@noodle-borg/service/dist/oauth/customer-bridge.d.ts.map +1 -1
  107. package/node_modules/@noodle-borg/service/dist/oauth/customer-bridge.js +13 -28
  108. package/node_modules/@noodle-borg/service/dist/oauth/customer-bridge.js.map +1 -1
  109. package/node_modules/@noodle-borg/service/dist/registry-helpers.d.ts +1 -1
  110. package/node_modules/@noodle-borg/service/dist/registry-helpers.d.ts.map +1 -1
  111. package/node_modules/@noodle-borg/service/dist/registry-helpers.js +5 -3
  112. package/node_modules/@noodle-borg/service/dist/registry-helpers.js.map +1 -1
  113. package/node_modules/@noodle-borg/service/dist/registry-targets.d.ts +1 -0
  114. package/node_modules/@noodle-borg/service/dist/registry-targets.d.ts.map +1 -1
  115. package/node_modules/@noodle-borg/service/dist/registry-targets.js +4 -0
  116. package/node_modules/@noodle-borg/service/dist/registry-targets.js.map +1 -1
  117. package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +59 -0
  118. package/node_modules/@noodle-borg/service/dist/registry-types.d.ts.map +1 -0
  119. package/node_modules/@noodle-borg/service/dist/registry-types.js +2 -0
  120. package/node_modules/@noodle-borg/service/dist/registry-types.js.map +1 -0
  121. package/node_modules/@noodle-borg/service/dist/registry.d.ts +8 -65
  122. package/node_modules/@noodle-borg/service/dist/registry.d.ts.map +1 -1
  123. package/node_modules/@noodle-borg/service/dist/registry.js +100 -82
  124. package/node_modules/@noodle-borg/service/dist/registry.js.map +1 -1
  125. package/node_modules/@noodle-borg/service/dist/routes/access-mode.d.ts +4 -0
  126. package/node_modules/@noodle-borg/service/dist/routes/access-mode.d.ts.map +1 -0
  127. package/node_modules/@noodle-borg/service/dist/routes/access-mode.js +15 -0
  128. package/node_modules/@noodle-borg/service/dist/routes/access-mode.js.map +1 -0
  129. package/node_modules/@noodle-borg/service/dist/routes/control-plane.d.ts +1 -1
  130. package/node_modules/@noodle-borg/service/dist/routes/control-plane.d.ts.map +1 -1
  131. package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +34 -21
  132. package/node_modules/@noodle-borg/service/dist/routes/control-plane.js.map +1 -1
  133. package/node_modules/@noodle-borg/service/dist/routes/rollback.d.ts.map +1 -1
  134. package/node_modules/@noodle-borg/service/dist/routes/rollback.js +4 -6
  135. package/node_modules/@noodle-borg/service/dist/routes/rollback.js.map +1 -1
  136. package/node_modules/@noodle-borg/service/dist/serve.d.ts +6 -0
  137. package/node_modules/@noodle-borg/service/dist/serve.d.ts.map +1 -1
  138. package/node_modules/@noodle-borg/service/dist/serve.js +72 -32
  139. package/node_modules/@noodle-borg/service/dist/serve.js.map +1 -1
  140. package/node_modules/@noodle-borg/service/dist/server-auth-bindings.d.ts +7 -0
  141. package/node_modules/@noodle-borg/service/dist/server-auth-bindings.d.ts.map +1 -0
  142. package/node_modules/@noodle-borg/service/dist/server-auth-bindings.js +14 -0
  143. package/node_modules/@noodle-borg/service/dist/server-auth-bindings.js.map +1 -0
  144. package/node_modules/@noodle-borg/service/dist/service.d.ts.map +1 -1
  145. package/node_modules/@noodle-borg/service/dist/service.js +32 -3
  146. package/node_modules/@noodle-borg/service/dist/service.js.map +1 -1
  147. package/node_modules/@noodle-borg/service/dist/store/postgres-schema.d.ts.map +1 -1
  148. package/node_modules/@noodle-borg/service/dist/store/postgres-schema.js +13 -2
  149. package/node_modules/@noodle-borg/service/dist/store/postgres-schema.js.map +1 -1
  150. package/node_modules/@noodle-borg/service/dist/store/postgres.d.ts +3 -2
  151. package/node_modules/@noodle-borg/service/dist/store/postgres.d.ts.map +1 -1
  152. package/node_modules/@noodle-borg/service/dist/store/postgres.js +41 -9
  153. package/node_modules/@noodle-borg/service/dist/store/postgres.js.map +1 -1
  154. package/node_modules/@noodle-borg/service/dist/store.d.ts +9 -3
  155. package/node_modules/@noodle-borg/service/dist/store.d.ts.map +1 -1
  156. package/node_modules/@noodle-borg/service/dist/store.js +29 -27
  157. package/node_modules/@noodle-borg/service/dist/store.js.map +1 -1
  158. package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts +1 -0
  159. package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts.map +1 -1
  160. package/node_modules/@noodle-borg/transport-http/dist/handler.js +37 -0
  161. package/node_modules/@noodle-borg/transport-http/dist/handler.js.map +1 -1
  162. package/node_modules/@noodle-borg/transport-http/dist/index.d.ts +1 -1
  163. package/node_modules/@noodle-borg/transport-http/dist/index.d.ts.map +1 -1
  164. package/node_modules/@noodle-borg/transport-http/dist/index.js.map +1 -1
  165. package/package.json +3 -2
@@ -0,0 +1,387 @@
1
+ /**
2
+ * The `noodle devtools` browser harness: the three-pane shell HTML/CSS, the client script, the
3
+ * `window.openai` shim injected into widget iframes, and small HTML helpers. Kept separate from the
4
+ * preview server (`devtools-preview.ts`) so each file owns one concern.
5
+ *
6
+ * The client script is plain (no template literals / no `${...}`) so it splices safely into the shell
7
+ * and so the shim's classic script runs before the widget's deferred bridge module.
8
+ */
9
+ /**
10
+ * Serialize a value for safe inlining inside a `<script>` block. Tool output/metadata can contain
11
+ * `</script>` (and can come from an untrusted upstream API), which would otherwise close the inline script
12
+ * early and inject markup. `<`/`>`/`&` and the JS line/paragraph separators are neutralized.
13
+ */
14
+ function jsonForScript(value) {
15
+ return JSON.stringify(value)
16
+ .replaceAll('<', '\\u003c')
17
+ .replaceAll('>', '\\u003e')
18
+ .replaceAll('&', '\\u0026')
19
+ .replaceAll('\u2028', '\\u2028')
20
+ .replaceAll('\u2029', '\\u2029');
21
+ }
22
+ /** Build the classic (non-module) `window.openai` shim injected ahead of the deferred widget bridge. */
23
+ export function openAiShimScript(init) {
24
+ const data = {
25
+ toolInput: init.toolInput ?? {},
26
+ toolOutput: init.toolOutput ?? null,
27
+ toolResponseMetadata: init.toolResponseMetadata ?? null,
28
+ theme: init.theme ?? 'light',
29
+ };
30
+ return ('<script>' +
31
+ 'window.openai = Object.assign(' +
32
+ jsonForScript(data) +
33
+ ', {' +
34
+ ' widgetState: null, displayMode: "inline", locale: "en",' +
35
+ ' callTool: function (name, args) {' +
36
+ ' return fetch("/rpc", { method: "POST", headers: { "content-type": "application/json" },' +
37
+ ' body: JSON.stringify({ jsonrpc: "2.0", id: Date.now(), method: "tools/call",' +
38
+ ' params: { name: name, arguments: args || {} } }) })' +
39
+ ' .then(function (r) { return r.json(); })' +
40
+ ' .then(function (j) { if (j && j.error) { throw new Error((j.error && j.error.message) || "tool error"); } return j.result; });' +
41
+ ' },' +
42
+ ' setWidgetState: function (s) { window.openai.widgetState = s; return Promise.resolve(); },' +
43
+ // Notify the harness so a widget calling requestDisplayMode("fullscreen") is actually presented fullscreen.
44
+ ' requestDisplayMode: function (p) { var m = (p && p.mode) || "inline"; window.openai.displayMode = m; try { window.parent.postMessage({ type: "noodle:display-mode", mode: m }, "*"); } catch (e) {} return Promise.resolve({ mode: m }); },' +
45
+ ' openExternal: function (p) { try { window.open((p && p.href) || "#", "_blank", "noopener"); } catch (e) {} },' +
46
+ ' sendFollowUpMessage: function () { return Promise.resolve(); }' +
47
+ '});' +
48
+ '</script>');
49
+ }
50
+ /**
51
+ * Paint the widget document canvas with the devtools stage colour ({@link STAGE_BG}) so it blends into the
52
+ * preview instead of showing a white default canvas. Iframes do not render reliably transparent across
53
+ * browser color-scheme handling, so an explicit opaque colour is used rather than `transparent`.
54
+ */
55
+ const STAGE_BG = '#0c0a09';
56
+ const PREVIEW_RESET_STYLE = `<style>html,body{background:${STAGE_BG}!important;margin:0;padding:0}</style>`;
57
+ /** Insert the reset + shim into the widget document so they run before the (deferred) bridge module script. */
58
+ export function wrapWidgetHtml(html, shimScript) {
59
+ const inject = PREVIEW_RESET_STYLE + shimScript;
60
+ const lower = html.toLowerCase();
61
+ const headIdx = lower.indexOf('<head>');
62
+ if (headIdx !== -1) {
63
+ const at = headIdx + '<head>'.length;
64
+ return html.slice(0, at) + inject + html.slice(at);
65
+ }
66
+ const bodyIdx = lower.indexOf('<body');
67
+ if (bodyIdx !== -1) {
68
+ const close = html.indexOf('>', bodyIdx);
69
+ if (close !== -1)
70
+ return html.slice(0, close + 1) + inject + html.slice(close + 1);
71
+ }
72
+ return inject + html;
73
+ }
74
+ function escapeHtml(value) {
75
+ return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
76
+ }
77
+ /** Official Noodle Seed brand mark (from apps/website/public/favicon.svg), sized via CSS. */
78
+ const NOODLE_MARK = '<svg class="brand__mark" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">' +
79
+ '<rect width="16" height="16" rx="8" fill="white"/>' +
80
+ '<path d="M8.58023 12.8485C6.27833 12.8463 4.41315 11.0141 4.42117 8.75681L4.43588 4.61714L8.66254 4.62118C10.9644 4.62339 12.8296 6.45561 12.8216 8.71287C12.8135 10.9975 10.9128 12.8507 8.58023 12.8485Z" fill="black"/>' +
81
+ '<path d="M8.82627 4.76869C11.1282 4.7709 12.9933 6.60312 12.9853 8.86038L12.9706 13L8.74395 12.996C6.44206 12.9938 4.57687 11.1616 4.5849 8.90432C4.59302 6.6197 6.49364 4.76646 8.82627 4.76869Z" fill="black"/>' +
82
+ '<ellipse cx="3.69536" cy="3.68106" rx="0.695363" ry="0.681056" fill="black"/></svg>';
83
+ /** The three-pane harness shell. */
84
+ export function harnessHtml(options) {
85
+ const client = HARNESS_CLIENT_JS.replace('__INITIAL_THEME__', options.theme === 'dark' ? 'dark' : 'light')
86
+ .replace('__INITIAL_DEVICE__', options.device === 'mobile' ? 'mobile' : 'desktop')
87
+ // replaceAll: the placeholder appears more than once in the client — a single replace would leave a
88
+ // literal `__CHAT_HAS_KEY__` that throws a ReferenceError and aborts the script (breaking the key gate).
89
+ .replaceAll('__CHAT_HAS_KEY__', options.hasServerKey ? 'true' : 'false');
90
+ return ('<!doctype html><html lang="en"><head><meta charset="utf-8">' +
91
+ '<meta name="viewport" content="width=device-width, initial-scale=1">' +
92
+ '<title>Noodle Seed Devtools</title><style>' +
93
+ HARNESS_CSS +
94
+ '</style></head><body>' +
95
+ '<header class="bar"><span class="brand">' +
96
+ NOODLE_MARK +
97
+ '<span class="brand__name">Noodle Seed</span><span class="brand__label">Devtools</span></span>' +
98
+ '<div class="modes" role="tablist">' +
99
+ '<button id="mode-preview" class="mode is-active" type="button" role="tab">Preview</button>' +
100
+ '<button id="mode-chat" class="mode" type="button" role="tab">Chat</button>' +
101
+ '</div>' +
102
+ `<span class="muted brand__url">${escapeHtml(options.mcpUrl)}</span></header>` +
103
+ '<div class="cols">' +
104
+ '<aside id="tools" class="pane pane--tools"><h2>Tools</h2><ul id="tool-list"></ul></aside>' +
105
+ '<main id="stage" class="pane pane--stage">' +
106
+ '<div id="preview-view">' +
107
+ '<div id="controls" class="controls">' +
108
+ '<span class="ctl"><span class="ctl__lbl">Theme</span><select id="theme"><option value="light">Light</option><option value="dark">Dark</option></select></span>' +
109
+ '<span class="ctl"><span class="ctl__lbl">Device</span><select id="device"><option value="desktop">Desktop</option><option value="mobile">Mobile</option></select></span>' +
110
+ '<span class="ctl ctl--width"><span class="ctl__lbl">Width</span><input id="width" type="range" min="320" max="1200" value="820"><span id="width-val" class="ctl__val">820px</span></span>' +
111
+ '<span class="ctl__spacer"></span>' +
112
+ '<button id="fullscreen" class="ctl-btn" type="button" title="Toggle fullscreen preview">' +
113
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m13-5h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3m13 5h3a2 2 0 0 0 2-2v-3"/></svg>Fullscreen</button>' +
114
+ '<button id="reload" class="ctl-btn" type="button" title="Reload widget">' +
115
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-2.64-6.36M21 4v5h-5"/></svg>Reload</button>' +
116
+ '</div>' +
117
+ '<div id="empty" class="empty-state">' +
118
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">' +
119
+ '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18"/><path d="M7.5 4v5"/></svg>' +
120
+ '<p>Nothing selected</p>' +
121
+ '<span>Pick a tool on the left — preview a widget, or run a tool to see its JSON response here.</span>' +
122
+ '</div>' +
123
+ '<iframe id="frame" class="hidden" title="widget preview"></iframe>' +
124
+ '<div id="result" class="hidden"><div class="result__label">response</div><pre id="result-pre"></pre></div>' +
125
+ '</div>' +
126
+ '<div id="chat-view" class="hidden">' +
127
+ // Key-entry gate — shown when the server has no key; the key is sent to the loopback /chat only.
128
+ '<div id="chat-gate" class="chat-gate"><div class="chat-gate__card">' +
129
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">' +
130
+ '<circle cx="7.5" cy="15.5" r="4.5"/><path d="M10.7 12.3 19 4"/><path d="m16 6 2 2"/><path d="m14 8 2 2"/></svg>' +
131
+ '<h3>Connect a model</h3>' +
132
+ '<p>Enter an OpenAI API key to start the chat playground. It is kept only on this local devtools server to call OpenAI — never stored in the browser.</p>' +
133
+ '<form id="chat-key-form"><input id="chat-key-input" type="password" placeholder="sk-…" autocomplete="off" spellcheck="false">' +
134
+ '<button class="btn btn--run" type="submit">Start chatting</button></form>' +
135
+ '<span id="chat-gate-hint" class="chat-gate__hint">Prefer a file? Set <code>OPENAI_API_KEY</code> in your shell or a <code>.env.local</code>.</span>' +
136
+ '</div></div>' +
137
+ '<div id="chat-body" class="hidden">' +
138
+ '<div id="chat-keybar"><button id="chat-reset" type="button">Change key</button></div>' +
139
+ '<div id="chat-log"><div id="chat-empty" class="empty-state">' +
140
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">' +
141
+ '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>' +
142
+ '<p>Chat playground</p>' +
143
+ '<span>Ask the agent to use this server&#39;s tools. Tool calls and any widgets render inline.</span>' +
144
+ '</div></div>' +
145
+ '<form id="chat-form" class="chat-form">' +
146
+ '<textarea id="chat-input" rows="1" placeholder="Ask the agent to run a tool…"></textarea>' +
147
+ '<button id="chat-send" class="btn btn--run" type="submit">Send</button>' +
148
+ '</form>' +
149
+ '</div>' +
150
+ '</div>' +
151
+ '</main>' +
152
+ '<aside id="log" class="pane pane--log"><h2>MCP calls</h2><ol id="log-list"></ol></aside>' +
153
+ '</div>' +
154
+ '<script>' +
155
+ client +
156
+ '</script></body></html>');
157
+ }
158
+ const HARNESS_CSS =
159
+ // Warm-on-stone brand palette ported from apps/website (stone-950 base + warm CTA ramp).
160
+ ':root{color-scheme:dark;' +
161
+ '--nd-bg:#0c0a09;--nd-panel:#1c1917;--nd-raised:#292524;' +
162
+ '--nd-border:rgba(250,250,249,.09);--nd-border-strong:rgba(250,250,249,.16);' +
163
+ '--nd-text:#fafaf9;--nd-muted:#a8a29e;--nd-faint:#78716c;' +
164
+ '--nd-accent:#f97316;--nd-amber:#f59e0b;--nd-rose:#f43f5e;--nd-ok:#4ade80;' +
165
+ '--nd-r:10px;--nd-r-sm:7px;' +
166
+ '--nd-font:"Geist",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;' +
167
+ '--nd-mono:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,monospace}' +
168
+ '*{box-sizing:border-box}' +
169
+ // Full-height flex column (header + cols) instead of a magic `100vh - 49px`, so a taller header (mode
170
+ // toggle) never pushes the grid past the viewport and makes the body scroll.
171
+ 'body{margin:0;height:100vh;display:flex;flex-direction:column;overflow:hidden;font-family:var(--nd-font);background:var(--nd-bg);color:var(--nd-text);-webkit-font-smoothing:antialiased}' +
172
+ '.bar{flex:none;display:flex;gap:12px;align-items:center;padding:12px 16px;border-bottom:1px solid var(--nd-border)}' +
173
+ '.brand{display:flex;align-items:center;gap:9px}' +
174
+ '.brand__mark{width:22px;height:22px;display:block;flex:none}' +
175
+ '.brand__name{font-weight:650;font-size:14px;color:var(--nd-text);letter-spacing:-.01em}' +
176
+ '.brand__label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--nd-accent);background:color-mix(in oklab,var(--nd-accent) 14%,transparent);padding:2px 7px;border-radius:999px}' +
177
+ '.brand__url{margin-left:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:38%}' +
178
+ '.muted{color:var(--nd-muted);font-size:12px}' +
179
+ // Segmented Preview|Chat toggle in the header.
180
+ '.modes{display:flex;gap:2px;background:var(--nd-panel);border:1px solid var(--nd-border-strong);border-radius:999px;padding:3px}' +
181
+ '.mode{background:transparent;color:var(--nd-muted);border:0;border-radius:999px;padding:5px 16px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}' +
182
+ '.mode.is-active{background:var(--nd-accent);color:#1c0a00}' +
183
+ '.cols{flex:1;min-height:0;display:grid;grid-template-columns:288px 1fr 372px;grid-template-rows:minmax(0,1fr)}' +
184
+ '.pane{padding:14px;overflow:auto}.pane--tools{border-right:1px solid var(--nd-border)}' +
185
+ '.pane--log{border-left:1px solid var(--nd-border)}' +
186
+ 'h2{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--nd-faint);margin:0 0 12px;font-weight:600}' +
187
+ '#tool-list{list-style:none;margin:0;padding:0}' +
188
+ '.tool{border:1px solid var(--nd-border);border-radius:var(--nd-r);margin-bottom:8px;overflow:hidden;background:var(--nd-panel)}' +
189
+ '.tool__head{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:pointer;font-size:13.5px}' +
190
+ '.tool__head:hover{background:var(--nd-raised)}.tool.open .tool__head{background:var(--nd-raised)}' +
191
+ '.tool__name{font-weight:600;font-family:var(--nd-mono);font-size:13px}' +
192
+ '.tag{font-size:10px;font-weight:600;color:var(--nd-accent);background:color-mix(in oklab,var(--nd-accent) 16%,transparent);padding:1px 7px;border-radius:999px}' +
193
+ '.chev{color:var(--nd-faint);font-size:11px}.tool__head .chev{margin-left:auto}' +
194
+ '.tool__detail{display:none;padding:2px 12px 12px}.tool.open .tool__detail{display:block}' +
195
+ '.tool__desc{color:var(--nd-muted);font-size:12.5px;margin:6px 0 12px;line-height:1.5}' +
196
+ '.field{margin-bottom:10px}.field label{display:block;font-size:12px;color:var(--nd-text);margin-bottom:4px}' +
197
+ '.field__hint{font-size:11px;color:var(--nd-muted);margin-top:4px;line-height:1.4}' +
198
+ '.field input[type=text],.field input[type=number],.field select{width:100%;background:var(--nd-bg);color:var(--nd-text);border:1px solid var(--nd-border-strong);border-radius:var(--nd-r-sm);padding:7px 9px;font-size:13px;font-family:var(--nd-mono)}' +
199
+ '.field input:focus-visible,.field select:focus-visible{outline:2px solid var(--nd-accent);outline-offset:1px}' +
200
+ '.tool__actions{display:flex;gap:8px;margin-top:12px;justify-content:flex-end}' +
201
+ '.btn{background:var(--nd-raised);color:var(--nd-text);border:1px solid var(--nd-border-strong);border-radius:var(--nd-r-sm);padding:7px 14px;font-size:13px;font-weight:500;cursor:pointer;transition:filter .15s ease}' +
202
+ '.btn:hover{filter:brightness(1.15)}' +
203
+ '.btn--run{background:var(--nd-accent);border-color:transparent;color:#1c0a00;font-weight:600;box-shadow:0 6px 18px color-mix(in oklab,var(--nd-accent) 35%,transparent)}' +
204
+ '.btn:disabled{opacity:.55;cursor:default;box-shadow:none;filter:none}' +
205
+ // Polished preview toolbar: one rounded bar, micro-labels, custom-caret selects, icon buttons.
206
+ '.controls{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:14px;padding:7px 9px;background:var(--nd-panel);border:1px solid var(--nd-border);border-radius:12px}' +
207
+ '.ctl{display:flex;align-items:center;gap:8px}' +
208
+ '.ctl__lbl{font-size:10px;text-transform:uppercase;letter-spacing:.07em;color:var(--nd-faint);font-weight:600}' +
209
+ '.ctl select{appearance:none;-webkit-appearance:none;background-color:var(--nd-bg);color:var(--nd-text);border:1px solid var(--nd-border-strong);' +
210
+ 'border-radius:8px;padding:6px 26px 6px 10px;font-size:12.5px;font-family:inherit;cursor:pointer;background-repeat:no-repeat;background-position:right 8px center;background-size:11px;' +
211
+ 'background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2012%2012%22%20fill%3D%22none%22%20stroke%3D%22%23a8a29e%22%20stroke-width%3D%221.6%22%20stroke-linecap%3D%22round%22%3E%3Cpath%20d%3D%22M3%204.5%206%207.5%209%204.5%22%2F%3E%3C%2Fsvg%3E")}' +
212
+ '.ctl select:hover{border-color:var(--nd-faint)}.ctl select:focus-visible{outline:2px solid var(--nd-accent);outline-offset:1px}' +
213
+ '.ctl--width{gap:10px}.ctl input[type=range]{accent-color:var(--nd-accent);width:118px;height:4px;cursor:pointer}' +
214
+ '.ctl__val{font-family:var(--nd-mono);font-size:11.5px;color:var(--nd-muted);min-width:46px}' +
215
+ '.ctl__spacer{margin-left:auto}' +
216
+ '.ctl-btn{display:inline-flex;align-items:center;gap:6px;background:var(--nd-bg);color:var(--nd-text);border:1px solid var(--nd-border-strong);' +
217
+ 'border-radius:8px;padding:6px 12px;font-size:12.5px;font-weight:500;cursor:pointer;font-family:inherit;transition:filter .15s ease,border-color .15s ease}' +
218
+ '.ctl-btn:hover{border-color:var(--nd-faint);filter:brightness(1.12)}.ctl-btn svg{width:14px;height:14px}' +
219
+ // min-height:0 + overflow:hidden so the stage stays bounded and its inner scroller (chat log / result) scrolls
220
+ // instead of the whole grid growing as tool cards accumulate.
221
+ '.pane--stage{display:flex;flex-direction:column;padding:0;min-height:0;overflow:hidden}.pane--stage>div{padding:14px}' +
222
+ '#preview-view{flex:1;min-height:0;flex-direction:column}' +
223
+ '.hidden{display:none}' +
224
+ '#frame{width:820px;max-width:100%;height:300px;border:0;background:var(--nd-bg);display:block;margin:0 auto;flex:none}' +
225
+ // Fullscreen: the widget iframe fills the viewport; a floating exit control + Esc leave it.
226
+ '.nd-frame-full{display:block!important;position:fixed!important;inset:0!important;width:100vw!important;height:100dvh!important;max-width:none!important;margin:0!important;z-index:60!important;background:var(--nd-bg)!important}' +
227
+ '#fs-exit{position:fixed;top:16px;right:16px;z-index:61;display:inline-flex;align-items:center;gap:6px;background:var(--nd-panel);color:var(--nd-text);' +
228
+ 'border:1px solid var(--nd-border-strong);border-radius:8px;padding:8px 13px;font-size:12.5px;font-weight:500;cursor:pointer;font-family:var(--nd-font);box-shadow:0 10px 30px rgba(0,0,0,.45)}' +
229
+ '#fs-exit:hover{filter:brightness(1.12)}' +
230
+ '.empty-state{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;color:var(--nd-muted);text-align:center;padding:24px}' +
231
+ '.empty-state svg{width:48px;height:48px;color:var(--nd-accent);opacity:.55}' +
232
+ '.empty-state p{margin:0;font-size:15px;font-weight:600;color:var(--nd-text)}' +
233
+ '.empty-state span{font-size:12.5px;max-width:300px;line-height:1.5}' +
234
+ '#result{flex:1;min-height:0;overflow:auto}' +
235
+ '.result__label{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--nd-faint);margin:0 0 6px}' +
236
+ '#result pre{margin:0;background:var(--nd-panel);border:1px solid var(--nd-border);border-radius:var(--nd-r);padding:12px;' +
237
+ 'font-family:var(--nd-mono);font-size:12.5px;white-space:pre-wrap;word-break:break-word;color:var(--nd-text)}' +
238
+ '#log-list{list-style:none;margin:0;padding:0;font-size:12px}' +
239
+ '.log{border-bottom:1px solid var(--nd-border)}' +
240
+ '.log__head{display:flex;gap:8px;align-items:baseline;padding:7px 4px;cursor:pointer}' +
241
+ '.log__head:hover{background:var(--nd-panel)}' +
242
+ '.log .m{font-weight:600;font-family:var(--nd-mono);font-size:11.5px}.log .ok{color:var(--nd-ok)}.log .error{color:var(--nd-rose)}' +
243
+ '.log__sum{color:var(--nd-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}' +
244
+ '.log .d{margin-left:auto;color:var(--nd-faint)}' +
245
+ '.log__detail{display:none;padding:4px 6px 10px}.log.open .log__detail{display:block}' +
246
+ '.log__label{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--nd-faint);margin:6px 0 3px}' +
247
+ '.log__detail pre{margin:0;background:var(--nd-bg);border:1px solid var(--nd-border);border-radius:var(--nd-r-sm);padding:8px;' +
248
+ 'font-family:var(--nd-mono);font-size:11.5px;white-space:pre-wrap;word-break:break-word;max-height:280px;overflow:auto;color:var(--nd-text)}' +
249
+ // Chat playground.
250
+ '#chat-view{flex:1;min-height:0;flex-direction:column}' +
251
+ '#chat-body{flex:1;min-height:0;flex-direction:column;gap:12px}' +
252
+ '.chat-gate{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;padding:24px}' +
253
+ '.chat-gate__card{max-width:390px;width:100%;text-align:center;display:flex;flex-direction:column;align-items:center;gap:10px}' +
254
+ '.chat-gate__card svg{width:38px;height:38px;color:var(--nd-accent);opacity:.75}' +
255
+ '.chat-gate__card h3{margin:0;font-size:16px;font-weight:650;letter-spacing:-.01em}' +
256
+ '.chat-gate__card p{margin:0;font-size:12.5px;color:var(--nd-muted);line-height:1.55}' +
257
+ '#chat-key-form{display:flex;gap:8px;width:100%;margin-top:6px}' +
258
+ '#chat-key-input{flex:1;background:var(--nd-bg);color:var(--nd-text);border:1px solid var(--nd-border-strong);' +
259
+ 'border-radius:var(--nd-r-sm);padding:9px 11px;font-size:13px;font-family:var(--nd-mono)}' +
260
+ '#chat-key-input:focus-visible{outline:2px solid var(--nd-accent);outline-offset:1px}' +
261
+ '.chat-gate__hint{font-size:11.5px;color:var(--nd-faint);line-height:1.5}' +
262
+ '.chat-gate__hint code{font-family:var(--nd-mono);color:var(--nd-muted)}' +
263
+ '.chat-gate__hint.err{color:var(--nd-rose)}' +
264
+ '#chat-keybar{display:none;justify-content:flex-end;flex:none}#chat-keybar.on{display:flex}' +
265
+ '#chat-reset{background:transparent;border:0;color:var(--nd-muted);font-size:11.5px;cursor:pointer;text-decoration:underline;font-family:inherit;padding:0}' +
266
+ '#chat-reset:hover{color:var(--nd-text)}' +
267
+ '#chat-log{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:14px}' +
268
+ // Critical: children must not flex-shrink, else they compress to fit (no scroll, squished/cut-off cards)
269
+ // instead of overflowing the log. flex:none = keep natural height; the log scrolls.
270
+ '#chat-log>*{flex:0 0 auto}' +
271
+ // User message: a rounded bubble. Assistant: plain text + inline widget, no bubble.
272
+ '.msg{max-width:84%;line-height:1.55;white-space:pre-wrap;word-break:break-word}' +
273
+ '.msg--user{align-self:flex-end;background:var(--nd-accent);color:#1c0a00;font-weight:500;padding:10px 15px;border-radius:18px;font-size:13.5px}' +
274
+ '.msg--assistant{align-self:stretch;max-width:100%;color:var(--nd-text);font-size:14px;padding:2px 1px}' +
275
+ '.msg--error{align-self:flex-start;background:color-mix(in oklab,var(--nd-rose) 14%,transparent);border:1px solid color-mix(in oklab,var(--nd-rose) 40%,transparent);color:var(--nd-text);padding:10px 14px;border-radius:14px;font-size:13.5px}' +
276
+ '.msg--pending{align-self:flex-start;color:var(--nd-muted);font-style:italic;font-size:13.5px;padding:2px 1px}' +
277
+ // Tool call: a minimal, collapsed-by-default card, separate from the widget.
278
+ '.toolcall{align-self:stretch;border:1px solid var(--nd-border);border-radius:12px;background:color-mix(in oklab,var(--nd-panel) 55%,transparent);overflow:hidden}' +
279
+ '.toolcall__head{display:flex;align-items:center;gap:8px;padding:9px 12px;cursor:pointer;font-size:12.5px}' +
280
+ '.toolcall__head:hover{background:color-mix(in oklab,var(--nd-panel) 80%,transparent)}' +
281
+ '.toolcall__ico{color:var(--nd-faint);display:flex;align-items:center}.toolcall__ico svg{width:14px;height:14px}' +
282
+ '.toolcall__name{font-family:var(--nd-mono);font-weight:600;font-size:12.5px;color:var(--nd-text)}' +
283
+ '.toolcall__name.error{color:var(--nd-rose)}' +
284
+ '.toolcall__chev{margin-left:auto;color:var(--nd-faint);font-size:11px}' +
285
+ '.toolcall__detail{display:none;padding:0 12px 12px}.toolcall.open .toolcall__detail{display:block}' +
286
+ '.toolcall__detail .log__label{margin:10px 0 4px}.toolcall__detail .log__label:first-child{margin-top:2px}' +
287
+ // No inner scroll: the JSON renders full-height and the chat log is the single scroll surface.
288
+ '.toolcall__detail pre{margin:0;background:var(--nd-bg);border:1px solid var(--nd-border);border-radius:8px;padding:8px;' +
289
+ 'font-family:var(--nd-mono);font-size:11.5px;white-space:pre-wrap;word-break:break-word;overflow-x:auto;color:var(--nd-text)}' +
290
+ // Widget: inline in the conversation, no wrapping container. A shimmer skeleton covers it while it mounts.
291
+ '.chat-widget{align-self:stretch;margin:2px 0;position:relative;min-height:160px}' +
292
+ '.chat-widget iframe{width:100%;border:0;background:var(--nd-bg);display:block;min-height:120px}' +
293
+ '.chat-skeleton{position:absolute;inset:0;border-radius:12px;background:var(--nd-panel);border:1px solid var(--nd-border);overflow:hidden}' +
294
+ '.chat-skeleton::after{content:"";position:absolute;inset:0;transform:translateX(-100%);animation:nd-shimmer 1.4s ease-in-out infinite;' +
295
+ 'background:linear-gradient(90deg,transparent,color-mix(in oklab,var(--nd-raised) 70%,transparent),transparent)}' +
296
+ '@keyframes nd-shimmer{100%{transform:translateX(100%)}}' +
297
+ '@media (prefers-reduced-motion: reduce){.chat-skeleton::after{animation:none}}' +
298
+ '.chat-form{display:flex;gap:8px;align-items:flex-end;border-top:1px solid var(--nd-border);padding-top:12px;flex:none}' +
299
+ '#chat-input{flex:1;resize:none;max-height:160px;background:var(--nd-bg);color:var(--nd-text);border:1px solid var(--nd-border-strong);' +
300
+ 'border-radius:var(--nd-r-sm);padding:9px 11px;font-size:13.5px;font-family:var(--nd-font);line-height:1.5}' +
301
+ '#chat-input:focus-visible{outline:2px solid var(--nd-accent);outline-offset:1px}' +
302
+ '#chat-send:disabled{opacity:.55;cursor:default;box-shadow:none;filter:none}';
303
+ // Plain ES5-ish client (no template literals / no ${...}) — spliced into the harness verbatim.
304
+ const HARNESS_CLIENT_JS = [
305
+ 'var frame=document.getElementById("frame");',
306
+ 'var empty=document.getElementById("empty");',
307
+ 'var themeSel=document.getElementById("theme");',
308
+ 'var deviceSel=document.getElementById("device");',
309
+ 'var widthInput=document.getElementById("width");',
310
+ 'var widthVal=document.getElementById("width-val");',
311
+ 'var current=null; var currentArgs={};',
312
+ 'var resultBox=document.getElementById("result"); var resultPre=document.getElementById("result-pre"); var controlsBar=document.getElementById("controls");',
313
+ 'themeSel.value="__INITIAL_THEME__"; deviceSel.value="__INITIAL_DEVICE__";',
314
+ 'function rpc(method,params){ return fetch("/rpc",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:Date.now(),method:method,params:params||{}})}).then(function(r){return r.json();}); }',
315
+ 'function applyTheme(){ try{ var w=frame.contentWindow; if(w&&w.openai){ w.openai.theme=themeSel.value; w.dispatchEvent(new Event("openai:set_globals")); } }catch(e){} }',
316
+ 'function fitFrame(){ if(frame.classList.contains("nd-frame-full")) return; try{ var d=frame.contentDocument; if(d&&d.documentElement){ frame.style.height=Math.max(120, d.documentElement.scrollHeight)+"px"; } }catch(e){} }',
317
+ 'function clearBg(){ try{ var d=frame.contentDocument; if(d){ if(d.documentElement) d.documentElement.style.setProperty("background","#0c0a09","important"); if(d.body) d.body.style.setProperty("background","#0c0a09","important"); } }catch(e){} }',
318
+ 'function applyDevice(){ widthInput.value = deviceSel.value==="mobile"?390:820; syncWidth(); }',
319
+ 'function syncWidth(){ frame.style.width=widthInput.value+"px"; widthVal.textContent=widthInput.value+"px"; }',
320
+ 'function setState(s){ empty.style.display=s==="empty"?"flex":"none"; frame.style.display=s==="widget"?"block":"none"; resultBox.style.display=s==="result"?"block":"none"; controlsBar.style.display=s==="widget"?"flex":"none"; }',
321
+ 'function loadWidget(name,args){ current=name; currentArgs=args||{}; setState("widget"); var q="/widget?name="+encodeURIComponent(name); if(Object.keys(currentArgs).length) q+="&args="+encodeURIComponent(JSON.stringify(currentArgs)); frame.src=q; }',
322
+ 'function showResult(v){ current=null; resultPre.textContent=(typeof v==="string")?v:JSON.stringify(v,null,2); setState("result"); }',
323
+ 'frame.addEventListener("load", function(){ applyTheme(); clearBg(); fitFrame(); try{ var b=frame.contentDocument&&frame.contentDocument.body; if(b&&window.ResizeObserver){ new ResizeObserver(function(){ clearBg(); fitFrame(); }).observe(b); } }catch(e){} setTimeout(function(){ clearBg(); fitFrame(); },150); setTimeout(function(){ clearBg(); fitFrame(); },600); });',
324
+ 'themeSel.addEventListener("change", applyTheme);',
325
+ 'deviceSel.addEventListener("change", applyDevice);',
326
+ 'widthInput.addEventListener("input", syncWidth);',
327
+ 'document.getElementById("reload").addEventListener("click", function(){ if(current) loadWidget(current, currentArgs); });',
328
+ // Fullscreen: works for the preview frame (toolbar button) and any chat widget (its requestDisplayMode).
329
+ 'var fsFrame=null; var fsExitBtn=null;',
330
+ 'function ndFit(f){ if(f===frame) fitFrame(); else fitChatFrame(f); }',
331
+ 'function notifyDisplayMode(f,mode){ try{ var w=f.contentWindow; if(w&&w.openai){ w.openai.displayMode=mode; w.dispatchEvent(new Event("openai:set_globals")); } }catch(e){} }',
332
+ 'function enterFullscreen(f){ if(!f) return; if(fsFrame) exitFullscreen(); fsFrame=f; f.classList.add("nd-frame-full"); if(!fsExitBtn){ fsExitBtn=document.createElement("button"); fsExitBtn.id="fs-exit"; fsExitBtn.type="button"; fsExitBtn.innerHTML="\\u2715 Exit fullscreen"; fsExitBtn.addEventListener("click", exitFullscreen); document.body.appendChild(fsExitBtn); } notifyDisplayMode(f,"fullscreen"); }',
333
+ 'function exitFullscreen(){ if(fsFrame){ var f=fsFrame; f.classList.remove("nd-frame-full"); notifyDisplayMode(f,"inline"); fsFrame=null; setTimeout(function(){ ndFit(f); },0); } if(fsExitBtn){ fsExitBtn.remove(); fsExitBtn=null; } }',
334
+ 'document.addEventListener("keydown", function(e){ if(e.key==="Escape"&&fsFrame) exitFullscreen(); });',
335
+ 'document.getElementById("fullscreen").addEventListener("click", function(){ if(fsFrame) exitFullscreen(); else if(current) enterFullscreen(frame); });',
336
+ 'function findFrameBySource(src){ if(frame&&frame.contentWindow===src) return frame; var list=document.querySelectorAll("#chat-log iframe"); for(var i=0;i<list.length;i++){ if(list[i].contentWindow===src) return list[i]; } return null; }',
337
+ 'window.addEventListener("message", function(ev){ var d=ev.data; if(!d||d.type!=="noodle:display-mode") return; var f=findFrameBySource(ev.source); if(!f) return; if(d.mode==="fullscreen") enterFullscreen(f); else if(fsFrame===f) exitFullscreen(); });',
338
+ // Tool cards (expandable: description + schema fields + a single Run; widget tools render in the preview).
339
+ 'function makeControl(p){ if(Array.isArray(p.enum)){ var s=document.createElement("select"); p.enum.forEach(function(v){ var o=document.createElement("option"); o.value=String(v); o.textContent=String(v); s.appendChild(o); }); return s; } if(p.type==="boolean"){ var c=document.createElement("input"); c.type="checkbox"; return c; } var i=document.createElement("input"); i.type=(p.type==="number"||p.type==="integer")?"number":"text"; if(p.default!==undefined&&p.type!=="object"&&p.type!=="array") i.value=String(p.default); return i; }',
340
+ 'function collect(props,controls){ var args={}; Object.keys(controls).forEach(function(k){ var p=props[k]||{}; var el=controls[k]; if(el.type==="checkbox"){ args[k]=el.checked; return; } var v=el.value; if(v==="") return; if(p.type==="number"||p.type==="integer"){ args[k]=Number(v); return; } if(p.type==="object"||p.type==="array"){ try{ args[k]=JSON.parse(v); }catch(e){ args[k]=v; } return; } args[k]=v; }); return args; }',
341
+ 'function buildDetail(detail,t,hasWidget){ if(t.description){ var d=document.createElement("p"); d.className="tool__desc"; d.textContent=t.description; detail.appendChild(d); } var schema=t.inputSchema||{}; var props=schema.properties||{}; var req=schema.required||[]; var controls={}; Object.keys(props).forEach(function(k){ var p=props[k]||{}; var f=document.createElement("div"); f.className="field"; var lab=document.createElement("label"); lab.textContent=k+(req.indexOf(k)>=0?" *":"")+(p.type?" ("+p.type+")":""); var ctrl=makeControl(p); f.appendChild(lab); f.appendChild(ctrl); if(p.description){ var h=document.createElement("div"); h.className="field__hint"; h.textContent=p.description; f.appendChild(h); } controls[k]=ctrl; detail.appendChild(f); }); var actions=document.createElement("div"); actions.className="tool__actions"; var run=document.createElement("button"); run.className="btn btn--run"; run.textContent="Run"; run.addEventListener("click", function(){ if(hasWidget){ loadWidget(t.name, collect(props,controls)); return; } var args=collect(props,controls); run.disabled=true; var o=run.textContent; run.textContent="Running…"; rpc("tools/call",{name:t.name,arguments:args}).then(function(j){ showResult(j&&j.result!==undefined?j.result:(j&&j.error!==undefined?j.error:j)); }).catch(function(){}).then(function(){ run.disabled=false; run.textContent=o; }); }); actions.appendChild(run); detail.appendChild(actions); }',
342
+ 'function renderTools(tools){ var ul=document.getElementById("tool-list"); ul.innerHTML=""; tools.forEach(function(t){ var li=document.createElement("li"); li.className="tool"; var head=document.createElement("div"); head.className="tool__head"; var nm=document.createElement("span"); nm.className="tool__name"; nm.textContent=t.name; head.appendChild(nm); var hasWidget=t._meta&&t._meta.ui&&t._meta.ui.resourceUri; if(hasWidget){ var b=document.createElement("span"); b.className="tag"; b.textContent="widget"; head.appendChild(b); } var chev=document.createElement("span"); chev.className="chev"; chev.textContent="▸"; head.appendChild(chev); var detail=document.createElement("div"); detail.className="tool__detail"; head.addEventListener("click", function(){ var open=li.classList.toggle("open"); chev.textContent=open?"▾":"▸"; if(open&&!detail.dataset.built){ buildDetail(detail,t,hasWidget); detail.dataset.built="1"; } }); li.appendChild(head); li.appendChild(detail); ul.appendChild(li); }); }',
343
+ 'function refreshTools(){ return rpc("tools/list",{}).then(function(j){ renderTools((j.result&&j.result.tools)||[]); }); }',
344
+ 'refreshTools();',
345
+ // MCP call log (expandable: input + output JSON).
346
+ 'function section(label,value){ var w=document.createElement("div"); var h=document.createElement("div"); h.className="log__label"; h.textContent=label; var pre=document.createElement("pre"); pre.textContent=(typeof value==="string")?value:JSON.stringify(value,null,2); w.appendChild(h); w.appendChild(pre); return w; }',
347
+ 'function addLog(e){ var ol=document.getElementById("log-list"); var li=document.createElement("li"); li.className="log"; var head=document.createElement("div"); head.className="log__head"; var has=(e.request!==undefined||e.response!==undefined); var chev=document.createElement("span"); chev.className="chev"; chev.textContent=has?"▸":""; var m=document.createElement("span"); m.className="m "+e.status; m.textContent=e.method; var n=document.createElement("span"); n.className="log__sum"; n.textContent=(e.name?e.name+" ":"")+e.summary; var d=document.createElement("span"); d.className="d"; d.textContent=e.durationMs+"ms"; head.appendChild(chev); head.appendChild(m); head.appendChild(n); head.appendChild(d); var detail=document.createElement("div"); detail.className="log__detail"; if(has){ head.addEventListener("click", function(){ var open=li.classList.toggle("open"); chev.textContent=open?"▾":"▸"; if(open&&!detail.dataset.built){ if(e.request!==undefined) detail.appendChild(section("input",e.request)); if(e.response!==undefined) detail.appendChild(section("output",e.response)); detail.dataset.built="1"; } }); } li.appendChild(head); li.appendChild(detail); ol.insertBefore(li, ol.firstChild); }',
348
+ 'try{ var es=new EventSource("/rpc/log"); es.onmessage=function(ev){ try{ addLog(JSON.parse(ev.data)); }catch(e){} }; }catch(e){}',
349
+ 'try{ var rs=new EventSource("/reload"); rs.onmessage=function(){ refreshTools(); if(current) loadWidget(current, currentArgs); }; }catch(e){}',
350
+ // Preview | Chat mode toggle.
351
+ 'var previewView=document.getElementById("preview-view"); var chatView=document.getElementById("chat-view");',
352
+ 'var modePreview=document.getElementById("mode-preview"); var modeChat=document.getElementById("mode-chat");',
353
+ 'function setMode(m){ var p=m!=="chat"; modePreview.classList.toggle("is-active",p); modeChat.classList.toggle("is-active",!p); previewView.style.display=p?"flex":"none"; chatView.style.display=p?"none":"flex"; if(!p) enterChat(); }',
354
+ 'modePreview.addEventListener("click", function(){ setMode("preview"); });',
355
+ 'modeChat.addEventListener("click", function(){ setMode("chat"); });',
356
+ // Chat playground: an agent turn posts the running message history to /chat and renders tool calls inline.
357
+ 'var chatMessages=[]; var chatBusy=false;',
358
+ 'var chatLog=document.getElementById("chat-log"); var chatForm=document.getElementById("chat-form"); var chatInput=document.getElementById("chat-input"); var chatSend=document.getElementById("chat-send"); var chatEmpty=document.getElementById("chat-empty");',
359
+ // Key gate: the key lives ONLY in the loopback server (POST /chat/key), never in the browser — same-origin
360
+ // widget iframes could otherwise read it from localStorage/globals and exfiltrate it. The gate state comes
361
+ // from GET /chat/status (a boolean + source), and the key is never sent back to the page.
362
+ 'var chatGate=document.getElementById("chat-gate"); var chatBody=document.getElementById("chat-body"); var chatKeyForm=document.getElementById("chat-key-form"); var chatKeyInput=document.getElementById("chat-key-input"); var chatGateHint=document.getElementById("chat-gate-hint"); var chatKeybar=document.getElementById("chat-keybar"); var chatReset=document.getElementById("chat-reset");',
363
+ 'var chatHasKey=__CHAT_HAS_KEY__; var chatKeySource=__CHAT_HAS_KEY__?"env":"none";',
364
+ 'function refreshChatReady(cb){ fetch("/chat/status").then(function(r){return r.json();}).then(function(j){ chatHasKey=!!(j&&j.hasKey); chatKeySource=(j&&j.source)||"none"; if(cb) cb(); }).catch(function(){ if(cb) cb(); }); }',
365
+ 'function renderGate(){ if(chatHasKey){ chatGate.style.display="none"; chatBody.style.display="flex"; chatKeybar.className=(chatKeySource==="session")?"on":""; chatInput.focus(); } else { chatBody.style.display="none"; chatGate.style.display="flex"; chatKeyInput.focus(); } }',
366
+ 'function enterChat(){ renderGate(); refreshChatReady(renderGate); }',
367
+ 'function showKeyError(msg){ chatGateHint.textContent=msg; chatGateHint.className="chat-gate__hint err"; chatBody.style.display="none"; chatGate.style.display="flex"; chatKeyInput.focus(); }',
368
+ 'chatKeyForm.addEventListener("submit", function(e){ e.preventDefault(); var v=chatKeyInput.value.trim(); if(!v) return; fetch("/chat/key",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({key:v})}).then(function(r){return r.json();}).then(function(j){ chatKeyInput.value=""; if(j&&j.ok){ chatHasKey=!!j.hasKey; chatKeySource=j.source||"session"; chatGateHint.className="chat-gate__hint"; chatGateHint.innerHTML="Prefer a file? Set <code>OPENAI_API_KEY</code> in your shell or a <code>.env.local</code>."; renderGate(); } else { showKeyError("Could not set the key. Try again."); } }).catch(function(){ showKeyError("Could not reach the devtools server."); }); });',
369
+ 'chatReset.addEventListener("click", function(){ fetch("/chat/key",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({key:""})}).then(function(r){return r.json();}).then(function(j){ chatHasKey=!!(j&&j.hasKey); chatKeySource=(j&&j.source)||"none"; renderGate(); }).catch(function(){ enterChat(); }); });',
370
+ 'function scrollChat(){ chatLog.scrollTop=chatLog.scrollHeight; }',
371
+ 'function clearChatEmpty(){ if(chatEmpty){ chatEmpty.remove(); chatEmpty=null; } }',
372
+ 'function chatBubble(cls,text){ clearChatEmpty(); var d=document.createElement("div"); d.className="msg "+cls; d.textContent=text; chatLog.appendChild(d); scrollChat(); return d; }',
373
+ 'function fitChatFrame(f){ if(f.classList.contains("nd-frame-full")) return; try{ var d=f.contentDocument; if(d&&d.documentElement){ var h=Math.max(d.documentElement.scrollHeight, d.body?d.body.scrollHeight:0); f.style.height=Math.max(120,h)+"px"; } }catch(e){} }',
374
+ // The widget mounts async, so fit on load, keep fitting via ResizeObserver, and retry a couple of times.
375
+ 'function wireChatFrame(f, onReady){ f.addEventListener("load", function(){ try{ var d=f.contentDocument; if(d){ if(d.documentElement) d.documentElement.style.setProperty("background","#0c0a09","important"); if(d.body) d.body.style.setProperty("background","#0c0a09","important"); } }catch(e){} fitChatFrame(f); try{ var b=f.contentDocument&&f.contentDocument.body; if(b&&window.ResizeObserver){ new ResizeObserver(function(){ fitChatFrame(f); scrollChat(); }).observe(b); } }catch(e){} setTimeout(function(){ fitChatFrame(f); },150); setTimeout(function(){ fitChatFrame(f); if(onReady) onReady(); scrollChat(); },500); }); }',
376
+ 'var TOOL_ICO=\'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="2.3"/><circle cx="18" cy="6" r="2.3"/><circle cx="6" cy="18" r="2.3"/><circle cx="18" cy="18" r="2.3"/><path d="M8.3 6h7.4M6 8.3v7.4M18 8.3v7.4M8.3 18h7.4"/></svg>\';',
377
+ // Tool call: a minimal card, collapsed by default; its I/O is separate from the widget below it.
378
+ 'function renderToolCall(tc){ clearChatEmpty(); var card=document.createElement("div"); card.className="toolcall"; var head=document.createElement("div"); head.className="toolcall__head"; var ico=document.createElement("span"); ico.className="toolcall__ico"; ico.innerHTML=TOOL_ICO; var nm=document.createElement("span"); nm.className="toolcall__name"+(tc.isError?" error":""); nm.textContent=tc.name; head.appendChild(ico); head.appendChild(nm); if(tc.resourceUri){ var tag=document.createElement("span"); tag.className="tag"; tag.textContent="widget"; head.appendChild(tag); } var chev=document.createElement("span"); chev.className="toolcall__chev"; chev.textContent="▸"; head.appendChild(chev); var detail=document.createElement("div"); detail.className="toolcall__detail"; detail.appendChild(section("input",tc.arguments)); detail.appendChild(section("output",tc.result)); head.addEventListener("click", function(){ var open=card.classList.toggle("open"); chev.textContent=open?"▾":"▸"; if(open) setTimeout(function(){ try{ card.scrollIntoView({block:"nearest"}); }catch(e){} },0); }); card.appendChild(head); card.appendChild(detail); chatLog.appendChild(card); scrollChat(); }',
379
+ // Widget: rendered inline in the conversation, unboxed.
380
+ 'function renderWidget(tc){ if(!tc.resourceUri) return; clearChatEmpty(); var wrap=document.createElement("div"); wrap.className="chat-widget"; var sk=document.createElement("div"); sk.className="chat-skeleton"; wrap.appendChild(sk); var f=document.createElement("iframe"); f.title="widget"; wireChatFrame(f, function(){ if(sk&&sk.parentNode) sk.remove(); }); var q="/widget?name="+encodeURIComponent(tc.name); if(tc.arguments&&Object.keys(tc.arguments).length) q+="&args="+encodeURIComponent(JSON.stringify(tc.arguments)); f.src=q; wrap.appendChild(f); chatLog.appendChild(wrap); scrollChat(); }',
381
+ 'function sendChat(){ var text=chatInput.value.trim(); if(!text||chatBusy) return; chatBubble("msg--user",text); chatInput.value=""; chatInput.style.height="auto"; chatBusy=true; chatSend.disabled=true; var pending=chatBubble("msg--pending","Thinking…"); var outgoing=chatMessages.concat([{role:"user",content:text}]); fetch("/chat",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({messages:outgoing})}).then(function(r){return r.json();}).then(function(j){ pending.remove(); if(j&&j.error){ if(j.error.code==="no_api_key"){ showKeyError("Add an OpenAI key to start chatting."); return; } chatBubble("msg--error","Chat failed: "+(j.error.message||"unknown error")); return; } chatMessages=(j&&j.messages)||outgoing; (j&&j.toolCalls||[]).forEach(function(tc){ renderToolCall(tc); renderWidget(tc); }); if(j&&j.text) chatBubble("msg--assistant",j.text); }).catch(function(e){ pending.remove(); chatBubble("msg--error","Chat failed: "+(e&&e.message||e)); }).then(function(){ chatBusy=false; chatSend.disabled=false; scrollChat(); }); }',
382
+ 'chatForm.addEventListener("submit", function(e){ e.preventDefault(); sendChat(); });',
383
+ 'chatInput.addEventListener("keydown", function(e){ if(e.key==="Enter"&&!e.shiftKey){ e.preventDefault(); sendChat(); } });',
384
+ 'chatInput.addEventListener("input", function(){ chatInput.style.height="auto"; chatInput.style.height=Math.min(160, chatInput.scrollHeight)+"px"; });',
385
+ 'setState("empty"); syncWidth(); setMode("preview");',
386
+ ].join('\n');
387
+ //# sourceMappingURL=devtools-harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools-harness.js","sourceRoot":"","sources":["../src/devtools-harness.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH;;;;GAIG;AACH,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;SAC1B,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;SAC1B,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;SAC1B,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC;SAC/B,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AACrC,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,gBAAgB,CAAC,IAKhC;IACC,MAAM,IAAI,GAAG;QACX,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE;QAC/B,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;QACnC,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,IAAI,IAAI;QACvD,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,OAAO;KAC7B,CAAC;IACF,OAAO,CACL,UAAU;QACV,gCAAgC;QAChC,aAAa,CAAC,IAAI,CAAC;QACnB,KAAK;QACL,0DAA0D;QAC1D,oCAAoC;QACpC,2FAA2F;QAC3F,iFAAiF;QACjF,yDAAyD;QACzD,6CAA6C;QAC7C,mIAAmI;QACnI,KAAK;QACL,6FAA6F;QAC7F,4GAA4G;QAC5G,8OAA8O;QAC9O,gHAAgH;QAChH,iEAAiE;QACjE,KAAK;QACL,WAAW,CACZ,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,QAAQ,GAAG,SAAS,CAAC;AAC3B,MAAM,mBAAmB,GAAG,+BAA+B,QAAQ,wCAAwC,CAAC;AAE5G,+GAA+G;AAC/G,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,UAAkB;IAC7D,MAAM,MAAM,GAAG,mBAAmB,GAAG,UAAU,CAAC;IAChD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxC,IAAI,OAAO,KAAK,CAAC,CAAC,EAAE,CAAC;QACnB,MAAM,EAAE,GAAG,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC;QACrC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACrD,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,OAAO,KAAK,CAAC,CAAC,EAAE,CAAC;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACzC,IAAI,KAAK,KAAK,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,MAAM,GAAG,IAAI,CAAC;AACvB,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AACxF,CAAC;AAED,6FAA6F;AAC7F,MAAM,WAAW,GACf,8FAA8F;IAC9F,oDAAoD;IACpD,4NAA4N;IAC5N,mNAAmN;IACnN,qFAAqF,CAAC;AAExF,oCAAoC;AACpC,MAAM,UAAU,WAAW,CAAC,OAM3B;IACC,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,CACtC,mBAAmB,EACnB,OAAO,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAC5C;SACE,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;QAClF,oGAAoG;QACpG,yGAAyG;SACxG,UAAU,CAAC,kBAAkB,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC3E,OAAO,CACL,6DAA6D;QAC7D,sEAAsE;QACtE,4CAA4C;QAC5C,WAAW;QACX,uBAAuB;QACvB,0CAA0C;QAC1C,WAAW;QACX,+FAA+F;QAC/F,oCAAoC;QACpC,4FAA4F;QAC5F,4EAA4E;QAC5E,QAAQ;QACR,kCAAkC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,kBAAkB;QAC9E,oBAAoB;QACpB,2FAA2F;QAC3F,4CAA4C;QAC5C,yBAAyB;QACzB,sCAAsC;QACtC,gKAAgK;QAChK,0KAA0K;QAC1K,2LAA2L;QAC3L,mCAAmC;QACnC,0FAA0F;QAC1F,iQAAiQ;QACjQ,0EAA0E;QAC1E,oMAAoM;QACpM,QAAQ;QACR,sCAAsC;QACtC,+HAA+H;QAC/H,gGAAgG;QAChG,yBAAyB;QACzB,uGAAuG;QACvG,QAAQ;QACR,oEAAoE;QACpE,4GAA4G;QAC5G,QAAQ;QACR,qCAAqC;QACrC,iGAAiG;QACjG,qEAAqE;QACrE,+HAA+H;QAC/H,iHAAiH;QACjH,0BAA0B;QAC1B,0JAA0J;QAC1J,+HAA+H;QAC/H,2EAA2E;QAC3E,qJAAqJ;QACrJ,cAAc;QACd,qCAAqC;QACrC,uFAAuF;QACvF,8DAA8D;QAC9D,+HAA+H;QAC/H,iFAAiF;QACjF,wBAAwB;QACxB,sGAAsG;QACtG,cAAc;QACd,yCAAyC;QACzC,2FAA2F;QAC3F,yEAAyE;QACzE,SAAS;QACT,QAAQ;QACR,QAAQ;QACR,SAAS;QACT,0FAA0F;QAC1F,QAAQ;QACR,UAAU;QACV,MAAM;QACN,yBAAyB,CAC1B,CAAC;AACJ,CAAC;AAED,MAAM,WAAW;AACf,yFAAyF;AACzF,0BAA0B;IAC1B,yDAAyD;IACzD,6EAA6E;IAC7E,0DAA0D;IAC1D,2EAA2E;IAC3E,4BAA4B;IAC5B,uFAAuF;IACvF,qEAAqE;IACrE,0BAA0B;IAC1B,sGAAsG;IACtG,6EAA6E;IAC7E,2LAA2L;IAC3L,qHAAqH;IACrH,iDAAiD;IACjD,8DAA8D;IAC9D,yFAAyF;IACzF,wNAAwN;IACxN,uGAAuG;IACvG,8CAA8C;IAC9C,+CAA+C;IAC/C,kIAAkI;IAClI,uKAAuK;IACvK,4DAA4D;IAC5D,gHAAgH;IAChH,wFAAwF;IACxF,oDAAoD;IACpD,wHAAwH;IACxH,gDAAgD;IAChD,iIAAiI;IACjI,wGAAwG;IACxG,mGAAmG;IACnG,wEAAwE;IACxE,iKAAiK;IACjK,gFAAgF;IAChF,0FAA0F;IAC1F,uFAAuF;IACvF,6GAA6G;IAC7G,mFAAmF;IACnF,0PAA0P;IAC1P,+GAA+G;IAC/G,+EAA+E;IAC/E,yNAAyN;IACzN,qCAAqC;IACrC,0KAA0K;IAC1K,uEAAuE;IACvE,+FAA+F;IAC/F,uLAAuL;IACvL,+CAA+C;IAC/C,+GAA+G;IAC/G,kJAAkJ;IAClJ,wLAAwL;IACxL,yTAAyT;IACzT,iIAAiI;IACjI,kHAAkH;IAClH,6FAA6F;IAC7F,gCAAgC;IAChC,gJAAgJ;IAChJ,4JAA4J;IAC5J,0GAA0G;IAC1G,+GAA+G;IAC/G,8DAA8D;IAC9D,uHAAuH;IACvH,0DAA0D;IAC1D,uBAAuB;IACvB,wHAAwH;IACxH,4FAA4F;IAC5F,qOAAqO;IACrO,wJAAwJ;IACxJ,gMAAgM;IAChM,yCAAyC;IACzC,iKAAiK;IACjK,6EAA6E;IAC7E,8EAA8E;IAC9E,qEAAqE;IACrE,4CAA4C;IAC5C,mHAAmH;IACnH,2HAA2H;IAC3H,8GAA8G;IAC9G,8DAA8D;IAC9D,gDAAgD;IAChD,sFAAsF;IACtF,8CAA8C;IAC9C,mIAAmI;IACnI,4FAA4F;IAC5F,iDAAiD;IACjD,sFAAsF;IACtF,kHAAkH;IAClH,+HAA+H;IAC/H,6IAA6I;IAC7I,mBAAmB;IACnB,uDAAuD;IACvD,gEAAgE;IAChE,qGAAqG;IACrG,+HAA+H;IAC/H,iFAAiF;IACjF,oFAAoF;IACpF,sFAAsF;IACtF,gEAAgE;IAChE,+GAA+G;IAC/G,0FAA0F;IAC1F,sFAAsF;IACtF,0EAA0E;IAC1E,yEAAyE;IACzE,4CAA4C;IAC5C,4FAA4F;IAC5F,4JAA4J;IAC5J,yCAAyC;IACzC,4FAA4F;IAC5F,yGAAyG;IACzG,oFAAoF;IACpF,4BAA4B;IAC5B,oFAAoF;IACpF,iFAAiF;IACjF,iJAAiJ;IACjJ,wGAAwG;IACxG,iPAAiP;IACjP,+GAA+G;IAC/G,6EAA6E;IAC7E,mKAAmK;IACnK,2GAA2G;IAC3G,uFAAuF;IACvF,iHAAiH;IACjH,mGAAmG;IACnG,6CAA6C;IAC7C,wEAAwE;IACxE,oGAAoG;IACpG,2GAA2G;IAC3G,+FAA+F;IAC/F,yHAAyH;IACzH,8HAA8H;IAC9H,2GAA2G;IAC3G,kFAAkF;IAClF,iGAAiG;IACjG,2IAA2I;IAC3I,wIAAwI;IACxI,iHAAiH;IACjH,yDAAyD;IACzD,gFAAgF;IAChF,wHAAwH;IACxH,wIAAwI;IACxI,4GAA4G;IAC5G,kFAAkF;IAClF,6EAA6E,CAAC;AAEhF,+FAA+F;AAC/F,MAAM,iBAAiB,GAAG;IACxB,6CAA6C;IAC7C,6CAA6C;IAC7C,gDAAgD;IAChD,kDAAkD;IAClD,kDAAkD;IAClD,oDAAoD;IACpD,uCAAuC;IACvC,4JAA4J;IAC5J,2EAA2E;IAC3E,yOAAyO;IACzO,0KAA0K;IAC1K,+NAA+N;IAC/N,sPAAsP;IACtP,+FAA+F;IAC/F,8GAA8G;IAC9G,oOAAoO;IACpO,yPAAyP;IACzP,qIAAqI;IACrI,gXAAgX;IAChX,kDAAkD;IAClD,oDAAoD;IACpD,kDAAkD;IAClD,2HAA2H;IAC3H,yGAAyG;IACzG,uCAAuC;IACvC,sEAAsE;IACtE,+KAA+K;IAC/K,sZAAsZ;IACtZ,0OAA0O;IAC1O,uGAAuG;IACvG,wJAAwJ;IACxJ,8OAA8O;IAC9O,4PAA4P;IAC5P,2GAA2G;IAC3G,0hBAA0hB;IAC1hB,2aAA2a;IAC3a,m6CAAm6C;IACn6C,0+BAA0+B;IAC1+B,2HAA2H;IAC3H,iBAAiB;IACjB,kDAAkD;IAClD,gUAAgU;IAChU,2rCAA2rC;IAC3rC,kIAAkI;IAClI,+IAA+I;IAC/I,8BAA8B;IAC9B,6GAA6G;IAC7G,6GAA6G;IAC7G,yOAAyO;IACzO,2EAA2E;IAC3E,qEAAqE;IACrE,2GAA2G;IAC3G,0CAA0C;IAC1C,kQAAkQ;IAClQ,2GAA2G;IAC3G,2GAA2G;IAC3G,0FAA0F;IAC1F,qYAAqY;IACrY,mFAAmF;IACnF,kOAAkO;IAClO,oRAAoR;IACpR,qEAAqE;IACrE,+LAA+L;IAC/L,urBAAurB;IACvrB,6UAA6U;IAC7U,kEAAkE;IAClE,mFAAmF;IACnF,qLAAqL;IACrL,wQAAwQ;IACxQ,yGAAyG;IACzG,knBAAknB;IAClnB,6UAA6U;IAC7U,iGAAiG;IACjG,gqCAAgqC;IAChqC,wDAAwD;IACxD,qlBAAqlB;IACrlB,wiCAAwiC;IACxiC,sFAAsF;IACtF,4HAA4H;IAC5H,uJAAuJ;IACvJ,qDAAqD;CACtD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { harnessHtml, openAiShimScript, type PreviewDevice, type PreviewTheme, wrapWidgetHtml } from './devtools-harness.js';
2
+ /**
3
+ * `noodle devtools` preview server. It boots alongside a `dev` MCP endpoint and:
4
+ * - serves the three-pane harness shell (HTML/CSS/JS in `devtools-harness.ts`),
5
+ * - proxies + logs every JSON-RPC call through `/rpc`, capturing request/response for the inspector,
6
+ * - serves each widget's `ui://` HTML with a same-origin `window.openai` shim injected,
7
+ * - broadcasts a browser reload over `/reload` when devtools recompiles.
8
+ * The MCP server already inlines the ext-apps bridge; the shim lets a widget's `callServerTool` become a
9
+ * `fetch('/rpc')` the harness proxies and records, without reimplementing the ext-apps host protocol.
10
+ */
11
+ export type { PreviewDevice, PreviewTheme };
12
+ export { harnessHtml, openAiShimScript, wrapWidgetHtml };
13
+ export interface PreviewOptions {
14
+ readonly mcpUrl: string;
15
+ readonly theme: PreviewTheme;
16
+ readonly device: PreviewDevice;
17
+ readonly port?: number;
18
+ readonly protocolVersion?: string;
19
+ /** OpenAI model for the chat playground; falls back to `OPENAI_MODEL` then {@link DEFAULT_CHAT_MODEL}. */
20
+ readonly openaiModel?: string;
21
+ }
22
+ export interface RpcLogEntry {
23
+ readonly time: number;
24
+ readonly method: string;
25
+ readonly name?: string;
26
+ readonly status: 'ok' | 'error';
27
+ readonly durationMs: number;
28
+ readonly summary: string;
29
+ /** Request params (input) captured for the inspector; size-capped. */
30
+ readonly request?: unknown;
31
+ /** Response result or error (output) captured for the inspector; size-capped. */
32
+ readonly response?: unknown;
33
+ }
34
+ export interface PreviewHandle {
35
+ readonly url: string;
36
+ readonly log: readonly RpcLogEntry[];
37
+ /** Tell connected browsers to reload (refresh the tool list + re-fetch the open widget). */
38
+ signalReload(): void;
39
+ close(): Promise<void>;
40
+ }
41
+ export declare function startPreview(options: PreviewOptions): Promise<PreviewHandle>;
42
+ //# sourceMappingURL=devtools-preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools-preview.d.ts","sourceRoot":"","sources":["../src/devtools-preview.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,cAAc,EACf,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;GAQG;AAEH,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,0GAA0G;IAC1G,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAOD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,SAAS,WAAW,EAAE,CAAC;IACrC,4FAA4F;IAC5F,YAAY,IAAI,IAAI,CAAC;IACrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA8ED,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA0UlF"}