mojulo 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +2 -2
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/api/mcp/route.js +1 -1
  14. package/.next/standalone/.next/server/app-paths-manifest.json +2 -2
  15. package/.next/standalone/.next/server/chunks/2550.js +1 -1
  16. package/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
  17. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  18. package/.next/standalone/.next/server/pages/500.html +1 -1
  19. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  20. package/.next/standalone/lib/mcp/tools/context.js +18 -0
  21. package/.next/standalone/package.json +1 -1
  22. package/README.md +26 -4
  23. package/lib/mcp/server.js +2 -0
  24. package/lib/mcp/tools/context.js +18 -0
  25. package/package.json +1 -1
  26. /package/.next/standalone/.next/static/{M4n2s70bp3uicUyR8DQUW → PfPW5h-7TCjPjzndQKTL8}/_buildManifest.js +0 -0
  27. /package/.next/standalone/.next/static/{M4n2s70bp3uicUyR8DQUW → PfPW5h-7TCjPjzndQKTL8}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- M4n2s70bp3uicUyR8DQUW
1
+ PfPW5h-7TCjPjzndQKTL8
@@ -25,17 +25,17 @@
25
25
  "/api/vectorize-rag/route": "/api/vectorize-rag",
26
26
  "/icon.svg/route": "/icon.svg",
27
27
  "/api/deploy/route": "/api/deploy",
28
+ "/api/builder/stream/route": "/api/builder/stream",
28
29
  "/api/deployments/[id]/connection/route": "/api/deployments/[id]/connection",
29
30
  "/api/deployments/[id]/conversations/[conversationId]/route": "/api/deployments/[id]/conversations/[conversationId]",
30
- "/api/deployments/[id]/conversations/route": "/api/deployments/[id]/conversations",
31
31
  "/api/deployments/[id]/conversations/export/route": "/api/deployments/[id]/conversations/export",
32
+ "/api/deployments/[id]/conversations/route": "/api/deployments/[id]/conversations",
32
33
  "/api/deployments/[id]/storage/route": "/api/deployments/[id]/storage",
33
34
  "/api/deployments/[id]/submissions/export/route": "/api/deployments/[id]/submissions/export",
34
35
  "/api/deployments/[id]/submissions/route": "/api/deployments/[id]/submissions",
35
36
  "/api/deployments/route": "/api/deployments",
36
37
  "/api/generate-form/route": "/api/generate-form",
37
38
  "/api/preview/chat/route": "/api/preview/chat",
38
- "/api/builder/stream/route": "/api/builder/stream",
39
39
  "/dashboard/deployments/[id]/cloud-deploy/page": "/dashboard/deployments/[id]/cloud-deploy",
40
40
  "/dashboard/deployments/[id]/conversations/page": "/dashboard/deployments/[id]/conversations",
41
41
  "/dashboard/deployments/[id]/submissions/page": "/dashboard/deployments/[id]/submissions",
@@ -4,8 +4,8 @@
4
4
  ],
5
5
  "devFiles": [],
6
6
  "lowPriorityFiles": [
7
- "static/M4n2s70bp3uicUyR8DQUW/_buildManifest.js",
8
- "static/M4n2s70bp3uicUyR8DQUW/_ssgManifest.js"
7
+ "static/PfPW5h-7TCjPjzndQKTL8/_buildManifest.js",
8
+ "static/PfPW5h-7TCjPjzndQKTL8/_ssgManifest.js"
9
9
  ],
10
10
  "rootMainFiles": [
11
11
  "static/chunks/webpack-67cbd0917f84c95c.js",
@@ -58,8 +58,8 @@
58
58
  "dynamicRoutes": {},
59
59
  "notFoundRoutes": [],
60
60
  "preview": {
61
- "previewModeId": "1067d2418886a1420bef010118c34392",
62
- "previewModeSigningKey": "532766d07af06addd442bdce5d39f3d7766784e01383b39c8c408a9a71b1e94b",
63
- "previewModeEncryptionKey": "07dcf3653b866ad85ab3ce0c48a5a2c7b894dade60740ef9958af9f72f17fb22"
61
+ "previewModeId": "322f901a3c13d4c80b66605f354ff723",
62
+ "previewModeSigningKey": "73ce710a1a223302e21855e911ca2eb1e307158cae4b8cdcb276bb552d3049cd",
63
+ "previewModeEncryptionKey": "bde05ebd8ef17b0346d68ba9eb1b3c3577dd55a363348c4448f71b200c2759d1"
64
64
  }
65
65
  }
@@ -1 +1 @@
1
- <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-67cbd0917f84c95c.js"/><script src="/_next/static/chunks/4bd1b696-d3a0b478714afd8c.js" async=""></script><script src="/_next/static/chunks/3794-eb6127acb14cbca0.js" async=""></script><script src="/_next/static/chunks/main-app-9004260ca53d2edf.js" async=""></script><meta name="next-size-adjust" content=""/><title>500: This page couldn’t load</title><style>:root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }</style><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;display:flex;align-items:center;justify-content:center"><div style="margin-top:-32px;max-width:325px;padding:32px 28px;text-align:left"><svg width="32" height="32" viewBox="-0.2 -1.5 32 32" fill="none" style="margin-bottom:24px"><path d="M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z" fill="var(--next-error-title)"></path></svg><h1 style="font-size:24px;font-weight:500;letter-spacing:-0.02em;line-height:32px;margin:0 0 12px 0;color:var(--next-error-title)">This page couldn’t load</h1><p style="font-size:14px;font-weight:400;line-height:21px;margin:0 0 20px 0;color:var(--next-error-message)">A server error occurred. Reload to try again.</p><form style="margin:0"><button type="submit" style="display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;font-size:14px;font-weight:500;line-height:20px;border-radius:6px;cursor:pointer;color:var(--next-error-btn-text);background:var(--next-error-btn-bg);border:var(--next-error-btn-border)">Reload</button></form></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-67cbd0917f84c95c.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[7121,[],\"\"]\n3:I[4581,[],\"\"]\n4:I[484,[],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[484,[],\"ViewportBoundary\"]\na:I[484,[],\"MetadataBoundary\"]\nc:I[7123,[],\"default\",1]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"_global-error\",{\"children\":[\"__PAGE__\",{}]}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"title\",null,{\"children\":\"500: This page couldn’t load\"}],[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\"}}]]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"-32px\",\"maxWidth\":\"325px\",\"padding\":\"32px 28px\",\"textAlign\":\"left\"},\"children\":[[\"$\",\"svg\",null,{\"width\":\"32\",\"height\":\"32\",\"viewBox\":\"-0.2 -1.5 32 32\",\"fill\":\"none\",\"style\":{\"marginBottom\":\"24px\"},\"children\":[\"$\",\"path\",null,{\"d\":\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\",\"fill\":\"var(--next-error-title)\"}]}],[\"$\",\"h1\",null,{\"style\":{\"fontSize\":\"24px\",\"fontWeight\":500,\"letterSpacing\":\"-0.02em\",\"lineHeight\":\"32px\",\"margin\":\"0 0 12px 0\",\"color\":\"var(--next-error-title)\"},\"children\":\"This page couldn’t load\"}],[\"$\",\"p\",null,{\"style\":{\"fontSize\":\"14px\",\"fontWeight\":400,\"lineHeight\":\"21px\",\"margin\":\"0 0 20px 0\",\"color\":\"var(--next-error-message)\"},\"children\":\"A server error occurred. Reload to try again.\"}],[\"$\",\"form\",null,{\"style\":{\"margin\":0},\"children\":[\"$\",\"button\",null,{\"type\":\"submit\",\"style\":{\"display\":\"inline-flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"height\":\"32px\",\"padding\":\"0 12px\",\"fontSize\":\"14px\",\"fontWeight\":500,\"lineHeight\":\"20px\",\"borderRadius\":\"6px\",\"cursor\":\"pointer\",\"color\":\"var(--next-error-btn-text)\",\"background\":\"var(--next-error-btn-bg)\",\"border\":\"var(--next-error-btn-border)\"},\"children\":\"Reload\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$@7\"]},null,false,\"$@7\"],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"M4n2s70bp3uicUyR8DQUW\"}\n"])</script><script>self.__next_f.push([1,"d:[]\n7:\"$Wd\"\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\nb:[]\n"])</script></body></html>
1
+ <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-67cbd0917f84c95c.js"/><script src="/_next/static/chunks/4bd1b696-d3a0b478714afd8c.js" async=""></script><script src="/_next/static/chunks/3794-eb6127acb14cbca0.js" async=""></script><script src="/_next/static/chunks/main-app-9004260ca53d2edf.js" async=""></script><meta name="next-size-adjust" content=""/><title>500: This page couldn’t load</title><style>:root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }</style><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;display:flex;align-items:center;justify-content:center"><div style="margin-top:-32px;max-width:325px;padding:32px 28px;text-align:left"><svg width="32" height="32" viewBox="-0.2 -1.5 32 32" fill="none" style="margin-bottom:24px"><path d="M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z" fill="var(--next-error-title)"></path></svg><h1 style="font-size:24px;font-weight:500;letter-spacing:-0.02em;line-height:32px;margin:0 0 12px 0;color:var(--next-error-title)">This page couldn’t load</h1><p style="font-size:14px;font-weight:400;line-height:21px;margin:0 0 20px 0;color:var(--next-error-message)">A server error occurred. Reload to try again.</p><form style="margin:0"><button type="submit" style="display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;font-size:14px;font-weight:500;line-height:20px;border-radius:6px;cursor:pointer;color:var(--next-error-btn-text);background:var(--next-error-btn-bg);border:var(--next-error-btn-border)">Reload</button></form></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-67cbd0917f84c95c.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[7121,[],\"\"]\n3:I[4581,[],\"\"]\n4:I[484,[],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[484,[],\"ViewportBoundary\"]\na:I[484,[],\"MetadataBoundary\"]\nc:I[7123,[],\"default\",1]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"_global-error\",{\"children\":[\"__PAGE__\",{}]}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"title\",null,{\"children\":\"500: This page couldn’t load\"}],[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\"}}]]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"-32px\",\"maxWidth\":\"325px\",\"padding\":\"32px 28px\",\"textAlign\":\"left\"},\"children\":[[\"$\",\"svg\",null,{\"width\":\"32\",\"height\":\"32\",\"viewBox\":\"-0.2 -1.5 32 32\",\"fill\":\"none\",\"style\":{\"marginBottom\":\"24px\"},\"children\":[\"$\",\"path\",null,{\"d\":\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\",\"fill\":\"var(--next-error-title)\"}]}],[\"$\",\"h1\",null,{\"style\":{\"fontSize\":\"24px\",\"fontWeight\":500,\"letterSpacing\":\"-0.02em\",\"lineHeight\":\"32px\",\"margin\":\"0 0 12px 0\",\"color\":\"var(--next-error-title)\"},\"children\":\"This page couldn’t load\"}],[\"$\",\"p\",null,{\"style\":{\"fontSize\":\"14px\",\"fontWeight\":400,\"lineHeight\":\"21px\",\"margin\":\"0 0 20px 0\",\"color\":\"var(--next-error-message)\"},\"children\":\"A server error occurred. Reload to try again.\"}],[\"$\",\"form\",null,{\"style\":{\"margin\":0},\"children\":[\"$\",\"button\",null,{\"type\":\"submit\",\"style\":{\"display\":\"inline-flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"height\":\"32px\",\"padding\":\"0 12px\",\"fontSize\":\"14px\",\"fontWeight\":500,\"lineHeight\":\"20px\",\"borderRadius\":\"6px\",\"cursor\":\"pointer\",\"color\":\"var(--next-error-btn-text)\",\"background\":\"var(--next-error-btn-bg)\",\"border\":\"var(--next-error-btn-border)\"},\"children\":\"Reload\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$@7\"]},null,false,\"$@7\"],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"PfPW5h-7TCjPjzndQKTL8\"}\n"])</script><script>self.__next_f.push([1,"d:[]\n7:\"$Wd\"\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\nb:[]\n"])</script></body></html>
@@ -6,7 +6,7 @@
6
6
  8:I[484,[],"ViewportBoundary"]
7
7
  a:I[484,[],"MetadataBoundary"]
8
8
  c:I[7123,[],"default",1]
9
- 0:{"P":null,"c":["","_global-error"],"q":"","i":false,"f":[[["",{"children":["_global-error",{"children":["__PAGE__",{}]}]}],[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","html",null,{"id":"__next_error__","children":[["$","head",null,{"children":[["$","title",null,{"children":"500: This page couldn’t load"}],["$","style",null,{"dangerouslySetInnerHTML":{"__html":":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }"}}]]}],["$","body",null,{"children":["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","display":"flex","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"style":{"marginTop":"-32px","maxWidth":"325px","padding":"32px 28px","textAlign":"left"},"children":[["$","svg",null,{"width":"32","height":"32","viewBox":"-0.2 -1.5 32 32","fill":"none","style":{"marginBottom":"24px"},"children":["$","path",null,{"d":"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z","fill":"var(--next-error-title)"}]}],["$","h1",null,{"style":{"fontSize":"24px","fontWeight":500,"letterSpacing":"-0.02em","lineHeight":"32px","margin":"0 0 12px 0","color":"var(--next-error-title)"},"children":"This page couldn’t load"}],["$","p",null,{"style":{"fontSize":"14px","fontWeight":400,"lineHeight":"21px","margin":"0 0 20px 0","color":"var(--next-error-message)"},"children":"A server error occurred. Reload to try again."}],["$","form",null,{"style":{"margin":0},"children":["$","button",null,{"type":"submit","style":{"display":"inline-flex","alignItems":"center","justifyContent":"center","height":"32px","padding":"0 12px","fontSize":"14px","fontWeight":500,"lineHeight":"20px","borderRadius":"6px","cursor":"pointer","color":"var(--next-error-btn-text)","background":"var(--next-error-btn-bg)","border":"var(--next-error-btn-border)"},"children":"Reload"}]}]]}]}]}]]}],null,["$","$L4",null,{"children":["$","$5",null,{"name":"Next.MetadataOutlet","children":"$@6"}]}]]}],{},null,false,null]},null,false,"$@7"]},null,false,"$@7"],["$","$1","h",{"children":[null,["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$5",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$c",[]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"M4n2s70bp3uicUyR8DQUW"}
9
+ 0:{"P":null,"c":["","_global-error"],"q":"","i":false,"f":[[["",{"children":["_global-error",{"children":["__PAGE__",{}]}]}],[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","html",null,{"id":"__next_error__","children":[["$","head",null,{"children":[["$","title",null,{"children":"500: This page couldn’t load"}],["$","style",null,{"dangerouslySetInnerHTML":{"__html":":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }"}}]]}],["$","body",null,{"children":["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","display":"flex","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"style":{"marginTop":"-32px","maxWidth":"325px","padding":"32px 28px","textAlign":"left"},"children":[["$","svg",null,{"width":"32","height":"32","viewBox":"-0.2 -1.5 32 32","fill":"none","style":{"marginBottom":"24px"},"children":["$","path",null,{"d":"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z","fill":"var(--next-error-title)"}]}],["$","h1",null,{"style":{"fontSize":"24px","fontWeight":500,"letterSpacing":"-0.02em","lineHeight":"32px","margin":"0 0 12px 0","color":"var(--next-error-title)"},"children":"This page couldn’t load"}],["$","p",null,{"style":{"fontSize":"14px","fontWeight":400,"lineHeight":"21px","margin":"0 0 20px 0","color":"var(--next-error-message)"},"children":"A server error occurred. Reload to try again."}],["$","form",null,{"style":{"margin":0},"children":["$","button",null,{"type":"submit","style":{"display":"inline-flex","alignItems":"center","justifyContent":"center","height":"32px","padding":"0 12px","fontSize":"14px","fontWeight":500,"lineHeight":"20px","borderRadius":"6px","cursor":"pointer","color":"var(--next-error-btn-text)","background":"var(--next-error-btn-bg)","border":"var(--next-error-btn-border)"},"children":"Reload"}]}]]}]}]}]]}],null,["$","$L4",null,{"children":["$","$5",null,{"name":"Next.MetadataOutlet","children":"$@6"}]}]]}],{},null,false,null]},null,false,"$@7"]},null,false,"$@7"],["$","$1","h",{"children":[null,["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$5",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$c",[]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"PfPW5h-7TCjPjzndQKTL8"}
10
10
  d:[]
11
11
  7:"$Wd"
12
12
  9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
@@ -6,7 +6,7 @@
6
6
  8:I[484,[],"ViewportBoundary"]
7
7
  a:I[484,[],"MetadataBoundary"]
8
8
  c:I[7123,[],"default",1]
9
- 0:{"P":null,"c":["","_global-error"],"q":"","i":false,"f":[[["",{"children":["_global-error",{"children":["__PAGE__",{}]}]}],[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","html",null,{"id":"__next_error__","children":[["$","head",null,{"children":[["$","title",null,{"children":"500: This page couldn’t load"}],["$","style",null,{"dangerouslySetInnerHTML":{"__html":":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }"}}]]}],["$","body",null,{"children":["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","display":"flex","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"style":{"marginTop":"-32px","maxWidth":"325px","padding":"32px 28px","textAlign":"left"},"children":[["$","svg",null,{"width":"32","height":"32","viewBox":"-0.2 -1.5 32 32","fill":"none","style":{"marginBottom":"24px"},"children":["$","path",null,{"d":"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z","fill":"var(--next-error-title)"}]}],["$","h1",null,{"style":{"fontSize":"24px","fontWeight":500,"letterSpacing":"-0.02em","lineHeight":"32px","margin":"0 0 12px 0","color":"var(--next-error-title)"},"children":"This page couldn’t load"}],["$","p",null,{"style":{"fontSize":"14px","fontWeight":400,"lineHeight":"21px","margin":"0 0 20px 0","color":"var(--next-error-message)"},"children":"A server error occurred. Reload to try again."}],["$","form",null,{"style":{"margin":0},"children":["$","button",null,{"type":"submit","style":{"display":"inline-flex","alignItems":"center","justifyContent":"center","height":"32px","padding":"0 12px","fontSize":"14px","fontWeight":500,"lineHeight":"20px","borderRadius":"6px","cursor":"pointer","color":"var(--next-error-btn-text)","background":"var(--next-error-btn-bg)","border":"var(--next-error-btn-border)"},"children":"Reload"}]}]]}]}]}]]}],null,["$","$L4",null,{"children":["$","$5",null,{"name":"Next.MetadataOutlet","children":"$@6"}]}]]}],{},null,false,null]},null,false,"$@7"]},null,false,"$@7"],["$","$1","h",{"children":[null,["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$5",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$c",[]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"M4n2s70bp3uicUyR8DQUW"}
9
+ 0:{"P":null,"c":["","_global-error"],"q":"","i":false,"f":[[["",{"children":["_global-error",{"children":["__PAGE__",{}]}]}],[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","html",null,{"id":"__next_error__","children":[["$","head",null,{"children":[["$","title",null,{"children":"500: This page couldn’t load"}],["$","style",null,{"dangerouslySetInnerHTML":{"__html":":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }"}}]]}],["$","body",null,{"children":["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","display":"flex","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"style":{"marginTop":"-32px","maxWidth":"325px","padding":"32px 28px","textAlign":"left"},"children":[["$","svg",null,{"width":"32","height":"32","viewBox":"-0.2 -1.5 32 32","fill":"none","style":{"marginBottom":"24px"},"children":["$","path",null,{"d":"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z","fill":"var(--next-error-title)"}]}],["$","h1",null,{"style":{"fontSize":"24px","fontWeight":500,"letterSpacing":"-0.02em","lineHeight":"32px","margin":"0 0 12px 0","color":"var(--next-error-title)"},"children":"This page couldn’t load"}],["$","p",null,{"style":{"fontSize":"14px","fontWeight":400,"lineHeight":"21px","margin":"0 0 20px 0","color":"var(--next-error-message)"},"children":"A server error occurred. Reload to try again."}],["$","form",null,{"style":{"margin":0},"children":["$","button",null,{"type":"submit","style":{"display":"inline-flex","alignItems":"center","justifyContent":"center","height":"32px","padding":"0 12px","fontSize":"14px","fontWeight":500,"lineHeight":"20px","borderRadius":"6px","cursor":"pointer","color":"var(--next-error-btn-text)","background":"var(--next-error-btn-bg)","border":"var(--next-error-btn-border)"},"children":"Reload"}]}]]}]}]}]]}],null,["$","$L4",null,{"children":["$","$5",null,{"name":"Next.MetadataOutlet","children":"$@6"}]}]]}],{},null,false,null]},null,false,"$@7"]},null,false,"$@7"],["$","$1","h",{"children":[null,["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$5",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$c",[]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"PfPW5h-7TCjPjzndQKTL8"}
10
10
  d:[]
11
11
  7:"$Wd"
12
12
  9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
@@ -1,5 +1,5 @@
1
1
  1:"$Sreact.fragment"
2
2
  2:I[484,[],"OutletBoundary"]
3
3
  3:"$Sreact.suspense"
4
- 0:{"rsc":["$","$1","c",{"children":[["$","html",null,{"id":"__next_error__","children":[["$","head",null,{"children":[["$","title",null,{"children":"500: This page couldn’t load"}],["$","style",null,{"dangerouslySetInnerHTML":{"__html":":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }"}}]]}],["$","body",null,{"children":["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","display":"flex","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"style":{"marginTop":"-32px","maxWidth":"325px","padding":"32px 28px","textAlign":"left"},"children":[["$","svg",null,{"width":"32","height":"32","viewBox":"-0.2 -1.5 32 32","fill":"none","style":{"marginBottom":"24px"},"children":["$","path",null,{"d":"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z","fill":"var(--next-error-title)"}]}],["$","h1",null,{"style":{"fontSize":"24px","fontWeight":500,"letterSpacing":"-0.02em","lineHeight":"32px","margin":"0 0 12px 0","color":"var(--next-error-title)"},"children":"This page couldn’t load"}],["$","p",null,{"style":{"fontSize":"14px","fontWeight":400,"lineHeight":"21px","margin":"0 0 20px 0","color":"var(--next-error-message)"},"children":"A server error occurred. Reload to try again."}],["$","form",null,{"style":{"margin":0},"children":["$","button",null,{"type":"submit","style":{"display":"inline-flex","alignItems":"center","justifyContent":"center","height":"32px","padding":"0 12px","fontSize":"14px","fontWeight":500,"lineHeight":"20px","borderRadius":"6px","cursor":"pointer","color":"var(--next-error-btn-text)","background":"var(--next-error-btn-bg)","border":"var(--next-error-btn-border)"},"children":"Reload"}]}]]}]}]}]]}],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"M4n2s70bp3uicUyR8DQUW"}
4
+ 0:{"rsc":["$","$1","c",{"children":[["$","html",null,{"id":"__next_error__","children":[["$","head",null,{"children":[["$","title",null,{"children":"500: This page couldn’t load"}],["$","style",null,{"dangerouslySetInnerHTML":{"__html":":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }"}}]]}],["$","body",null,{"children":["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","display":"flex","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"style":{"marginTop":"-32px","maxWidth":"325px","padding":"32px 28px","textAlign":"left"},"children":[["$","svg",null,{"width":"32","height":"32","viewBox":"-0.2 -1.5 32 32","fill":"none","style":{"marginBottom":"24px"},"children":["$","path",null,{"d":"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z","fill":"var(--next-error-title)"}]}],["$","h1",null,{"style":{"fontSize":"24px","fontWeight":500,"letterSpacing":"-0.02em","lineHeight":"32px","margin":"0 0 12px 0","color":"var(--next-error-title)"},"children":"This page couldn’t load"}],["$","p",null,{"style":{"fontSize":"14px","fontWeight":400,"lineHeight":"21px","margin":"0 0 20px 0","color":"var(--next-error-message)"},"children":"A server error occurred. Reload to try again."}],["$","form",null,{"style":{"margin":0},"children":["$","button",null,{"type":"submit","style":{"display":"inline-flex","alignItems":"center","justifyContent":"center","height":"32px","padding":"0 12px","fontSize":"14px","fontWeight":500,"lineHeight":"20px","borderRadius":"6px","cursor":"pointer","color":"var(--next-error-btn-text)","background":"var(--next-error-btn-bg)","border":"var(--next-error-btn-border)"},"children":"Reload"}]}]]}]}]}]]}],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"PfPW5h-7TCjPjzndQKTL8"}
5
5
  4:null
@@ -2,4 +2,4 @@
2
2
  2:I[7121,[],""]
3
3
  3:I[4581,[],""]
4
4
  4:[]
5
- 0:{"rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"isPartial":false,"staleTime":300,"varyParams":"$W4","buildId":"M4n2s70bp3uicUyR8DQUW"}
5
+ 0:{"rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"isPartial":false,"staleTime":300,"varyParams":"$W4","buildId":"PfPW5h-7TCjPjzndQKTL8"}
@@ -2,4 +2,4 @@
2
2
  2:I[484,[],"ViewportBoundary"]
3
3
  3:I[484,[],"MetadataBoundary"]
4
4
  4:"$Sreact.suspense"
5
- 0:{"rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"M4n2s70bp3uicUyR8DQUW"}
5
+ 0:{"rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"PfPW5h-7TCjPjzndQKTL8"}
@@ -2,4 +2,4 @@
2
2
  2:I[7121,[],""]
3
3
  3:I[4581,[],""]
4
4
  4:[]
5
- 0:{"rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"isPartial":false,"staleTime":300,"varyParams":"$W4","buildId":"M4n2s70bp3uicUyR8DQUW"}
5
+ 0:{"rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"isPartial":false,"staleTime":300,"varyParams":"$W4","buildId":"PfPW5h-7TCjPjzndQKTL8"}
@@ -1 +1 @@
1
- 0:{"tree":{"name":"","param":null,"prefetchHints":0,"slots":{"children":{"name":"_global-error","param":null,"prefetchHints":0,"slots":{"children":{"name":"__PAGE__","param":null,"prefetchHints":0,"slots":null}}}}},"staleTime":300,"buildId":"M4n2s70bp3uicUyR8DQUW"}
1
+ 0:{"tree":{"name":"","param":null,"prefetchHints":0,"slots":{"children":{"name":"_global-error","param":null,"prefetchHints":0,"slots":{"children":{"name":"__PAGE__","param":null,"prefetchHints":0,"slots":null}}}}},"staleTime":300,"buildId":"PfPW5h-7TCjPjzndQKTL8"}
@@ -1 +1 @@
1
- (()=>{var a={};a.id=90,a.ids=[90],a.modules={261:a=>{"use strict";a.exports=require("next/dist/shared/lib/router/utils/app-paths")},1708:a=>{"use strict";a.exports=require("node:process")},1932:a=>{"use strict";a.exports=require("url")},7172:a=>{"use strict";a.exports=require("archiver")},10846:a=>{"use strict";a.exports=require("next/dist/compiled/next-server/app-page.runtime.prod.js")},16698:a=>{"use strict";a.exports=require("node:async_hooks")},19170:(a,b,c)=>{"use strict";c.r(b),c.d(b,{handler:()=>G,patchFetch:()=>F,routeModule:()=>B,serverHooks:()=>E,workAsyncStorage:()=>C,workUnitAsyncStorage:()=>D});var d={};c.r(d),c.d(d,{GET:()=>A,POST:()=>z});var e=c(19225),f=c(84006),g=c(8317),h=c(99373),i=c(34775),j=c(24235),k=c(261),l=c(54365),m=c(90771),n=c(73461),o=c(67798),p=c(92280),q=c(62018),r=c(45696),s=c(47929),t=c(86439),u=c(37527),v=c(23786);function w(){return new Response("Not found",{status:404})}function x(){return new Response("Unauthorized",{status:401,headers:{"WWW-Authenticate":"Bearer"}})}function y(a){let b=process.env.CONTROL_PLANE_MCP_KEY;if(!b)return{configured:!1};let c=(a.headers.get("authorization")||"").match(/^Bearer\s+(.+)$/i);if(!c)return{configured:!0,authorized:!1};let d=c[1].trim();if(d.length!==b.length)return{configured:!0,authorized:!1};let e=0;for(let a=0;a<d.length;a++)e|=d.charCodeAt(a)^b.charCodeAt(a);return{configured:!0,authorized:0===e}}async function z(a){let b,c=y(a);if(!c.configured)return w();if(!c.authorized)return x();await (0,v.QW)();try{b=await a.json()}catch{return new Response(JSON.stringify({jsonrpc:"2.0",id:null,error:{code:-32700,message:"Parse error"}}),{status:400,headers:{"Content-Type":"application/json"}})}let d={mcpSessionId:a.headers.get("mcp-session-id")||a.headers.get("x-mcp-session-id")||"default",userId:"local"};if(Array.isArray(b)){let a=[];for(let c of b){let b=await (0,v.qp)(c,d);null!==b&&a.push(b)}return 0===a.length?new Response(null,{status:204}):new Response(JSON.stringify(a),{status:200,headers:{"Content-Type":"application/json"}})}let e=await (0,v.qp)(b,d);return null===e?new Response(null,{status:204}):new Response(JSON.stringify(e),{status:200,headers:{"Content-Type":"application/json"}})}async function A(a){let b=y(a);return b.configured?b.authorized?new Response("Method not allowed (server-initiated SSE not supported)",{status:405,headers:{Allow:"POST"}}):x():w()}let B=new e.AppRouteRouteModule({definition:{kind:f.RouteKind.APP_ROUTE,page:"/api/mcp/route",pathname:"/api/mcp",filename:"route",bundlePath:"app/api/mcp/route"},distDir:".next",relativeProjectDir:"",resolvedPagePath:"/Users/fombico/Documents/mojulo/control/app/api/mcp/route.js",nextConfigOutput:"standalone",userland:d,...{}}),{workAsyncStorage:C,workUnitAsyncStorage:D,serverHooks:E}=B;function F(){return(0,g.patchFetch)({workAsyncStorage:C,workUnitAsyncStorage:D})}async function G(a,b,c){c.requestMeta&&(0,h.setRequestMeta)(a,c.requestMeta),B.isDev&&(0,h.addRequestMeta)(a,"devRequestTimingInternalsEnd",process.hrtime.bigint());let d="/api/mcp/route";"/index"===d&&(d="/");let e=await B.prepare(a,b,{srcPage:d,multiZoneDraftMode:!1});if(!e)return b.statusCode=400,b.end("Bad Request"),null==c.waitUntil||c.waitUntil.call(c,Promise.resolve()),null;let{buildId:g,params:v,nextConfig:w,parsedUrl:x,isDraftMode:y,prerenderManifest:z,routerServerContext:A,isOnDemandRevalidate:C,revalidateOnlyGenerated:D,resolvedPathname:E,clientReferenceManifest:F,serverActionsManifest:G}=e,H=(0,k.normalizeAppPath)(d),I=!!(z.dynamicRoutes[H]||z.routes[E]),J=async()=>((null==A?void 0:A.render404)?await A.render404(a,b,x,!1):b.end("This page could not be found"),null);if(I&&!y){let a=!!z.routes[E],b=z.dynamicRoutes[H];if(b&&!1===b.fallback&&!a){if(w.adapterPath)return await J();throw new t.NoFallbackError}}let K=null;!I||B.isDev||y||(K="/index"===(K=E)?"/":K);let L=!0===B.isDev||!I,M=I&&!L;G&&F&&(0,j.setManifestsSingleton)({page:d,clientReferenceManifest:F,serverActionsManifest:G});let N=a.method||"GET",O=(0,i.getTracer)(),P=O.getActiveScopeSpan(),Q=!!(null==A?void 0:A.isWrappedByNextServer),R=!!(0,h.getRequestMeta)(a,"minimalMode"),S=(0,h.getRequestMeta)(a,"incrementalCache")||await B.getIncrementalCache(a,w,z,R);null==S||S.resetRequestCache(),globalThis.__incrementalCache=S;let T={params:v,previewProps:z.preview,renderOpts:{experimental:{authInterrupts:!!w.experimental.authInterrupts},cacheComponents:!!w.cacheComponents,supportsDynamicResponse:L,incrementalCache:S,cacheLifeProfiles:w.cacheLife,waitUntil:c.waitUntil,onClose:a=>{b.on("close",a)},onAfterTaskError:void 0,onInstrumentationRequestError:(b,c,d,e)=>B.onRequestError(a,b,d,e,A)},sharedContext:{buildId:g}},U=new l.NodeNextRequest(a),V=new l.NodeNextResponse(b),W=m.NextRequestAdapter.fromNodeNextRequest(U,(0,m.signalFromNodeResponse)(b));try{let e,g=async a=>B.handle(W,T).finally(()=>{if(!a)return;a.setAttributes({"http.status_code":b.statusCode,"next.rsc":!1});let c=O.getRootSpanAttributes();if(!c)return;if(c.get("next.span_type")!==n.BaseServerSpan.handleRequest)return void console.warn(`Unexpected root span type '${c.get("next.span_type")}'. Please report this Next.js issue https://github.com/vercel/next.js`);let f=c.get("next.route");if(f){let b=`${N} ${f}`;a.setAttributes({"next.route":f,"http.route":f,"next.span_name":b}),a.updateName(b),e&&e!==a&&(e.setAttribute("http.route",f),e.updateName(b))}else a.updateName(`${N} ${d}`)}),h=async e=>{var h,i;let j=async({previousCacheEntry:f})=>{try{if(!R&&C&&D&&!f)return b.statusCode=404,b.setHeader("x-nextjs-cache","REVALIDATED"),b.end("This page could not be found"),null;let d=await g(e);a.fetchMetrics=T.renderOpts.fetchMetrics;let h=T.renderOpts.pendingWaitUntil;h&&c.waitUntil&&(c.waitUntil(h),h=void 0);let i=T.renderOpts.collectedTags;if(!I)return await (0,p.I)(U,V,d,T.renderOpts.pendingWaitUntil),null;{let a=await d.blob(),b=(0,q.toNodeOutgoingHttpHeaders)(d.headers);i&&(b[s.NEXT_CACHE_TAGS_HEADER]=i),!b["content-type"]&&a.type&&(b["content-type"]=a.type);let c=void 0!==T.renderOpts.collectedRevalidate&&!(T.renderOpts.collectedRevalidate>=s.INFINITE_CACHE)&&T.renderOpts.collectedRevalidate,e=void 0===T.renderOpts.collectedExpire||T.renderOpts.collectedExpire>=s.INFINITE_CACHE?void 0:T.renderOpts.collectedExpire;return{value:{kind:u.CachedRouteKind.APP_ROUTE,status:d.status,body:Buffer.from(await a.arrayBuffer()),headers:b},cacheControl:{revalidate:c,expire:e}}}}catch(b){throw(null==f?void 0:f.isStale)&&await B.onRequestError(a,b,{routerKind:"App Router",routePath:d,routeType:"route",revalidateReason:(0,o.c)({isStaticGeneration:M,isOnDemandRevalidate:C})},!1,A),b}},k=await B.handleResponse({req:a,nextConfig:w,cacheKey:K,routeKind:f.RouteKind.APP_ROUTE,isFallback:!1,prerenderManifest:z,isRoutePPREnabled:!1,isOnDemandRevalidate:C,revalidateOnlyGenerated:D,responseGenerator:j,waitUntil:c.waitUntil,isMinimalMode:R});if(!I)return null;if((null==k||null==(h=k.value)?void 0:h.kind)!==u.CachedRouteKind.APP_ROUTE)throw Object.defineProperty(Error(`Invariant: app-route received invalid cache entry ${null==k||null==(i=k.value)?void 0:i.kind}`),"__NEXT_ERROR_CODE",{value:"E701",enumerable:!1,configurable:!0});R||b.setHeader("x-nextjs-cache",C?"REVALIDATED":k.isMiss?"MISS":k.isStale?"STALE":"HIT"),y&&b.setHeader("Cache-Control","private, no-cache, no-store, max-age=0, must-revalidate");let l=(0,q.fromNodeOutgoingHttpHeaders)(k.value.headers);return R&&I||l.delete(s.NEXT_CACHE_TAGS_HEADER),!k.cacheControl||b.getHeader("Cache-Control")||l.get("Cache-Control")||l.set("Cache-Control",(0,r.getCacheControlHeader)(k.cacheControl)),await (0,p.I)(U,V,new Response(k.value.body,{headers:l,status:k.value.status||200})),null};Q&&P?await h(P):(e=O.getActiveScopeSpan(),await O.withPropagatedContext(a.headers,()=>O.trace(n.BaseServerSpan.handleRequest,{spanName:`${N} ${d}`,kind:i.SpanKind.SERVER,attributes:{"http.method":N,"http.target":a.url}},h),void 0,!Q))}catch(b){if(b instanceof t.NoFallbackError||await B.onRequestError(a,b,{routerKind:"App Router",routePath:H,routeType:"route",revalidateReason:(0,o.c)({isStaticGeneration:M,isOnDemandRevalidate:C})},!1,A),I)throw b;return await (0,p.I)(U,V,new Response(null,{status:500})),null}}},19225:(a,b,c)=>{"use strict";a.exports=c(44870)},21820:a=>{"use strict";a.exports=require("os")},23786:(a,b,c)=>{"use strict";c.d(b,{QW:()=>r,SR:()=>l,Sw:()=>j,WP:()=>g,gz:()=>h,qp:()=>o});var d=c(73024),e=c(76760),f=c.n(e);let g="2024-11-05",h="mojulo-control-plane",i=null;function j(){if(null!==i)return i;try{i=JSON.parse((0,d.readFileSync)(f().join(process.cwd(),"package.json"),"utf8")).version||"0.0.0"}catch{i="0.0.0"}return i}let k=new Map;function l(a){if(!a||!a.name||"function"!=typeof a.handler)throw Error("registerTool requires { name, handler }");k.set(a.name,a)}function m(a,b){return{jsonrpc:"2.0",id:a,result:b}}function n(a,b,c,d){let e={code:b,message:c};return void 0!==d&&(e.data=d),{jsonrpc:"2.0",id:a,error:e}}async function o(a,b){if(!a||"2.0"!==a.jsonrpc)return n(a?.id??null,-32600,"Invalid JSON-RPC request");let c=void 0===a.id||null===a.id;try{switch(a.method){case"initialize":return c?null:m(a.id,{protocolVersion:g,capabilities:{tools:{listChanged:!1}},serverInfo:{name:h,version:j()},instructions:"Mojulo is a control plane for **chatbot-based solutions** — chatbots that talk to your users, capture what they say, and turn those conversations into real outcomes in the tools the user already runs (CRM, calendar, ticketing, drive, warehouse).\n\n**When the user asks what mojulo is, how it works, or which tools to pick — call `forward_context` first.** It returns the concept glossary, the bot capability model, the deploy/connect lifecycle, and a one-line description of every tool, so you can orient before acting.\n\n**Standing secrets rule:** treat `.env` files under `$MOJULO_HOME` and inside any unzipped mojulo bot as containing user secrets that must not enter your conversation context. To inspect a bot's environment, call `inspect_bot_env` — it returns masked values. Never `cat`, `Read`, or otherwise echo raw .env contents."});case"notifications/initialized":case"initialized":return null;case"ping":return c?null:m(a.id,{});case"tools/list":return m(a.id,{tools:Array.from(k.values()).map(a=>({name:a.name,description:a.description||"",inputSchema:a.inputSchema||{type:"object",properties:{}}}))});case"tools/call":return await p(a,b);default:if(c)return null;return n(a.id,-32601,`Method not found: ${a.method}`)}}catch(b){if(console.error("[mcp] dispatch error:",b),c)return null;return n(a.id,-32603,b.message||"Internal error")}}async function p(a,b){let c=a.params||{},d=c.name,e=c.arguments||{},f=k.get(d);if(!f)return n(a.id,-32601,`Unknown tool: ${d}`);try{var g;let c=await f.handler(e,b);return m(a.id,(g=c)&&"object"==typeof g&&Array.isArray(g.content)?g:{content:[{type:"text",text:"string"==typeof g?g:JSON.stringify(g??{},null,2)}]})}catch(b){return m(a.id,{content:[{type:"text",text:b.message||"Tool execution failed"}],isError:!0})}}let q=!1;async function r(){if(q)return;q=!0;let{registerContextTools:a}=await c.e(2550).then(c.bind(c,2550)),{registerBuildTools:b}=await Promise.all([c.e(1556),c.e(634),c.e(2156),c.e(1841),c.e(6233)]).then(c.bind(c,36233)),{registerJobsTools:d}=await Promise.all([c.e(1556),c.e(634),c.e(2156),c.e(1841),c.e(661)]).then(c.bind(c,661)),{registerOperateTools:e}=await Promise.all([c.e(2719),c.e(7121)]).then(c.bind(c,67121)),{registerFleetTools:f}=await Promise.all([c.e(2719),c.e(397)]).then(c.bind(c,50397)),{registerCatalystTools:g}=await c.e(4165).then(c.bind(c,84165));a(),b(),d(),e(),f(),g()}},27910:a=>{"use strict";a.exports=require("stream")},29021:a=>{"use strict";a.exports=require("fs")},29294:a=>{"use strict";a.exports=require("next/dist/server/app-render/work-async-storage.external.js")},31421:a=>{"use strict";a.exports=require("node:child_process")},32467:a=>{"use strict";a.exports=require("node:http2")},33873:a=>{"use strict";a.exports=require("path")},36138:a=>{"use strict";a.exports=require("pdf2json")},37067:a=>{"use strict";a.exports=require("node:http")},44708:a=>{"use strict";a.exports=require("node:https")},44870:a=>{"use strict";a.exports=require("next/dist/compiled/next-server/app-route.runtime.prod.js")},48161:a=>{"use strict";a.exports=require("node:os")},55511:a=>{"use strict";a.exports=require("crypto")},57075:a=>{"use strict";a.exports=require("node:stream")},57975:a=>{"use strict";a.exports=require("node:util")},63033:a=>{"use strict";a.exports=require("next/dist/server/app-render/work-unit-async-storage.external.js")},68023:a=>{"use strict";a.exports=require("officeparser")},73024:a=>{"use strict";a.exports=require("node:fs")},73136:a=>{"use strict";a.exports=require("node:url")},73836:a=>{"use strict";a.exports=require("node:fs/promises")},76760:a=>{"use strict";a.exports=require("node:path")},77598:a=>{"use strict";a.exports=require("node:crypto")},78335:()=>{},79428:a=>{"use strict";a.exports=require("buffer")},79748:a=>{"use strict";a.exports=require("fs/promises")},81630:a=>{"use strict";a.exports=require("http")},86439:a=>{"use strict";a.exports=require("next/dist/shared/lib/no-fallback-error.external")},87550:a=>{"use strict";a.exports=require("better-sqlite3")},92280:(a,b,c)=>{"use strict";Object.defineProperty(b,"I",{enumerable:!0,get:function(){return g}});let d=c(28208),e=c(47617),f=c(62018);async function g(a,b,c,g){if((0,d.isNodeNextResponse)(b)){var h;b.statusCode=c.status,b.statusMessage=c.statusText;let d=["set-cookie","www-authenticate","proxy-authenticate","vary"];null==(h=c.headers)||h.forEach((a,c)=>{if("x-middleware-set-cookie"!==c.toLowerCase())if("set-cookie"===c.toLowerCase())for(let d of(0,f.splitCookiesString)(a))b.appendHeader(c,d);else{let e=void 0!==b.getHeader(c);(d.includes(c.toLowerCase())||!e)&&b.appendHeader(c,a)}});let{originalResponse:i}=b;c.body&&"HEAD"!==a.method?await (0,e.pipeToNodeResponse)(c.body,i,g):i.end()}}},96487:()=>{},99253:a=>{"use strict";a.exports=import("@huggingface/transformers")}};var b=require("../../../webpack-runtime.js");b.C(a);var c=b.X(0,[4741],()=>b(b.s=19170));module.exports=c})();
1
+ (()=>{var a={};a.id=90,a.ids=[90],a.modules={261:a=>{"use strict";a.exports=require("next/dist/shared/lib/router/utils/app-paths")},1708:a=>{"use strict";a.exports=require("node:process")},1932:a=>{"use strict";a.exports=require("url")},7172:a=>{"use strict";a.exports=require("archiver")},10846:a=>{"use strict";a.exports=require("next/dist/compiled/next-server/app-page.runtime.prod.js")},16698:a=>{"use strict";a.exports=require("node:async_hooks")},19170:(a,b,c)=>{"use strict";c.r(b),c.d(b,{handler:()=>G,patchFetch:()=>F,routeModule:()=>B,serverHooks:()=>E,workAsyncStorage:()=>C,workUnitAsyncStorage:()=>D});var d={};c.r(d),c.d(d,{GET:()=>A,POST:()=>z});var e=c(19225),f=c(84006),g=c(8317),h=c(99373),i=c(34775),j=c(24235),k=c(261),l=c(54365),m=c(90771),n=c(73461),o=c(67798),p=c(92280),q=c(62018),r=c(45696),s=c(47929),t=c(86439),u=c(37527),v=c(23786);function w(){return new Response("Not found",{status:404})}function x(){return new Response("Unauthorized",{status:401,headers:{"WWW-Authenticate":"Bearer"}})}function y(a){let b=process.env.CONTROL_PLANE_MCP_KEY;if(!b)return{configured:!1};let c=(a.headers.get("authorization")||"").match(/^Bearer\s+(.+)$/i);if(!c)return{configured:!0,authorized:!1};let d=c[1].trim();if(d.length!==b.length)return{configured:!0,authorized:!1};let e=0;for(let a=0;a<d.length;a++)e|=d.charCodeAt(a)^b.charCodeAt(a);return{configured:!0,authorized:0===e}}async function z(a){let b,c=y(a);if(!c.configured)return w();if(!c.authorized)return x();await (0,v.QW)();try{b=await a.json()}catch{return new Response(JSON.stringify({jsonrpc:"2.0",id:null,error:{code:-32700,message:"Parse error"}}),{status:400,headers:{"Content-Type":"application/json"}})}let d={mcpSessionId:a.headers.get("mcp-session-id")||a.headers.get("x-mcp-session-id")||"default",userId:"local"};if(Array.isArray(b)){let a=[];for(let c of b){let b=await (0,v.qp)(c,d);null!==b&&a.push(b)}return 0===a.length?new Response(null,{status:204}):new Response(JSON.stringify(a),{status:200,headers:{"Content-Type":"application/json"}})}let e=await (0,v.qp)(b,d);return null===e?new Response(null,{status:204}):new Response(JSON.stringify(e),{status:200,headers:{"Content-Type":"application/json"}})}async function A(a){let b=y(a);return b.configured?b.authorized?new Response("Method not allowed (server-initiated SSE not supported)",{status:405,headers:{Allow:"POST"}}):x():w()}let B=new e.AppRouteRouteModule({definition:{kind:f.RouteKind.APP_ROUTE,page:"/api/mcp/route",pathname:"/api/mcp",filename:"route",bundlePath:"app/api/mcp/route"},distDir:".next",relativeProjectDir:"",resolvedPagePath:"/Users/fombico/Documents/mojulo/control/app/api/mcp/route.js",nextConfigOutput:"standalone",userland:d,...{}}),{workAsyncStorage:C,workUnitAsyncStorage:D,serverHooks:E}=B;function F(){return(0,g.patchFetch)({workAsyncStorage:C,workUnitAsyncStorage:D})}async function G(a,b,c){c.requestMeta&&(0,h.setRequestMeta)(a,c.requestMeta),B.isDev&&(0,h.addRequestMeta)(a,"devRequestTimingInternalsEnd",process.hrtime.bigint());let d="/api/mcp/route";"/index"===d&&(d="/");let e=await B.prepare(a,b,{srcPage:d,multiZoneDraftMode:!1});if(!e)return b.statusCode=400,b.end("Bad Request"),null==c.waitUntil||c.waitUntil.call(c,Promise.resolve()),null;let{buildId:g,params:v,nextConfig:w,parsedUrl:x,isDraftMode:y,prerenderManifest:z,routerServerContext:A,isOnDemandRevalidate:C,revalidateOnlyGenerated:D,resolvedPathname:E,clientReferenceManifest:F,serverActionsManifest:G}=e,H=(0,k.normalizeAppPath)(d),I=!!(z.dynamicRoutes[H]||z.routes[E]),J=async()=>((null==A?void 0:A.render404)?await A.render404(a,b,x,!1):b.end("This page could not be found"),null);if(I&&!y){let a=!!z.routes[E],b=z.dynamicRoutes[H];if(b&&!1===b.fallback&&!a){if(w.adapterPath)return await J();throw new t.NoFallbackError}}let K=null;!I||B.isDev||y||(K="/index"===(K=E)?"/":K);let L=!0===B.isDev||!I,M=I&&!L;G&&F&&(0,j.setManifestsSingleton)({page:d,clientReferenceManifest:F,serverActionsManifest:G});let N=a.method||"GET",O=(0,i.getTracer)(),P=O.getActiveScopeSpan(),Q=!!(null==A?void 0:A.isWrappedByNextServer),R=!!(0,h.getRequestMeta)(a,"minimalMode"),S=(0,h.getRequestMeta)(a,"incrementalCache")||await B.getIncrementalCache(a,w,z,R);null==S||S.resetRequestCache(),globalThis.__incrementalCache=S;let T={params:v,previewProps:z.preview,renderOpts:{experimental:{authInterrupts:!!w.experimental.authInterrupts},cacheComponents:!!w.cacheComponents,supportsDynamicResponse:L,incrementalCache:S,cacheLifeProfiles:w.cacheLife,waitUntil:c.waitUntil,onClose:a=>{b.on("close",a)},onAfterTaskError:void 0,onInstrumentationRequestError:(b,c,d,e)=>B.onRequestError(a,b,d,e,A)},sharedContext:{buildId:g}},U=new l.NodeNextRequest(a),V=new l.NodeNextResponse(b),W=m.NextRequestAdapter.fromNodeNextRequest(U,(0,m.signalFromNodeResponse)(b));try{let e,g=async a=>B.handle(W,T).finally(()=>{if(!a)return;a.setAttributes({"http.status_code":b.statusCode,"next.rsc":!1});let c=O.getRootSpanAttributes();if(!c)return;if(c.get("next.span_type")!==n.BaseServerSpan.handleRequest)return void console.warn(`Unexpected root span type '${c.get("next.span_type")}'. Please report this Next.js issue https://github.com/vercel/next.js`);let f=c.get("next.route");if(f){let b=`${N} ${f}`;a.setAttributes({"next.route":f,"http.route":f,"next.span_name":b}),a.updateName(b),e&&e!==a&&(e.setAttribute("http.route",f),e.updateName(b))}else a.updateName(`${N} ${d}`)}),h=async e=>{var h,i;let j=async({previousCacheEntry:f})=>{try{if(!R&&C&&D&&!f)return b.statusCode=404,b.setHeader("x-nextjs-cache","REVALIDATED"),b.end("This page could not be found"),null;let d=await g(e);a.fetchMetrics=T.renderOpts.fetchMetrics;let h=T.renderOpts.pendingWaitUntil;h&&c.waitUntil&&(c.waitUntil(h),h=void 0);let i=T.renderOpts.collectedTags;if(!I)return await (0,p.I)(U,V,d,T.renderOpts.pendingWaitUntil),null;{let a=await d.blob(),b=(0,q.toNodeOutgoingHttpHeaders)(d.headers);i&&(b[s.NEXT_CACHE_TAGS_HEADER]=i),!b["content-type"]&&a.type&&(b["content-type"]=a.type);let c=void 0!==T.renderOpts.collectedRevalidate&&!(T.renderOpts.collectedRevalidate>=s.INFINITE_CACHE)&&T.renderOpts.collectedRevalidate,e=void 0===T.renderOpts.collectedExpire||T.renderOpts.collectedExpire>=s.INFINITE_CACHE?void 0:T.renderOpts.collectedExpire;return{value:{kind:u.CachedRouteKind.APP_ROUTE,status:d.status,body:Buffer.from(await a.arrayBuffer()),headers:b},cacheControl:{revalidate:c,expire:e}}}}catch(b){throw(null==f?void 0:f.isStale)&&await B.onRequestError(a,b,{routerKind:"App Router",routePath:d,routeType:"route",revalidateReason:(0,o.c)({isStaticGeneration:M,isOnDemandRevalidate:C})},!1,A),b}},k=await B.handleResponse({req:a,nextConfig:w,cacheKey:K,routeKind:f.RouteKind.APP_ROUTE,isFallback:!1,prerenderManifest:z,isRoutePPREnabled:!1,isOnDemandRevalidate:C,revalidateOnlyGenerated:D,responseGenerator:j,waitUntil:c.waitUntil,isMinimalMode:R});if(!I)return null;if((null==k||null==(h=k.value)?void 0:h.kind)!==u.CachedRouteKind.APP_ROUTE)throw Object.defineProperty(Error(`Invariant: app-route received invalid cache entry ${null==k||null==(i=k.value)?void 0:i.kind}`),"__NEXT_ERROR_CODE",{value:"E701",enumerable:!1,configurable:!0});R||b.setHeader("x-nextjs-cache",C?"REVALIDATED":k.isMiss?"MISS":k.isStale?"STALE":"HIT"),y&&b.setHeader("Cache-Control","private, no-cache, no-store, max-age=0, must-revalidate");let l=(0,q.fromNodeOutgoingHttpHeaders)(k.value.headers);return R&&I||l.delete(s.NEXT_CACHE_TAGS_HEADER),!k.cacheControl||b.getHeader("Cache-Control")||l.get("Cache-Control")||l.set("Cache-Control",(0,r.getCacheControlHeader)(k.cacheControl)),await (0,p.I)(U,V,new Response(k.value.body,{headers:l,status:k.value.status||200})),null};Q&&P?await h(P):(e=O.getActiveScopeSpan(),await O.withPropagatedContext(a.headers,()=>O.trace(n.BaseServerSpan.handleRequest,{spanName:`${N} ${d}`,kind:i.SpanKind.SERVER,attributes:{"http.method":N,"http.target":a.url}},h),void 0,!Q))}catch(b){if(b instanceof t.NoFallbackError||await B.onRequestError(a,b,{routerKind:"App Router",routePath:H,routeType:"route",revalidateReason:(0,o.c)({isStaticGeneration:M,isOnDemandRevalidate:C})},!1,A),I)throw b;return await (0,p.I)(U,V,new Response(null,{status:500})),null}}},19225:(a,b,c)=>{"use strict";a.exports=c(44870)},21820:a=>{"use strict";a.exports=require("os")},23786:(a,b,c)=>{"use strict";c.d(b,{QW:()=>r,SR:()=>l,Sw:()=>j,WP:()=>g,gz:()=>h,qp:()=>o});var d=c(73024),e=c(76760),f=c.n(e);let g="2024-11-05",h="mojulo-control-plane",i=null;function j(){if(null!==i)return i;try{i=JSON.parse((0,d.readFileSync)(f().join(process.cwd(),"package.json"),"utf8")).version||"0.0.0"}catch{i="0.0.0"}return i}let k=new Map;function l(a){if(!a||!a.name||"function"!=typeof a.handler)throw Error("registerTool requires { name, handler }");k.set(a.name,a)}function m(a,b){return{jsonrpc:"2.0",id:a,result:b}}function n(a,b,c,d){let e={code:b,message:c};return void 0!==d&&(e.data=d),{jsonrpc:"2.0",id:a,error:e}}async function o(a,b){if(!a||"2.0"!==a.jsonrpc)return n(a?.id??null,-32600,"Invalid JSON-RPC request");let c=void 0===a.id||null===a.id;try{switch(a.method){case"initialize":return c?null:m(a.id,{protocolVersion:g,capabilities:{tools:{listChanged:!1}},serverInfo:{name:h,version:j()},instructions:"Mojulo is a control plane for **chatbot-based solutions** — chatbots that talk to your users, capture what they say, and turn those conversations into real outcomes in the tools the user already runs (CRM, calendar, ticketing, drive, warehouse).\n\n**When the user asks what mojulo is, how it works, or which tools to pick — call `forward_context` first.** It returns the concept glossary, the bot capability model, the deploy/connect lifecycle, and a one-line description of every tool, so you can orient before acting.\n\n**There's also a dashboard.** Mojulo ships a second bin, `mojulo-ui`, which boots a local Next.js dashboard at 127.0.0.1 sharing the same `~/.mojulo/` state as this MCP. Launch with `npx -y -p mojulo mojulo-ui`. Suggest it when the user wants to browse conversations / submissions interactively, mint a bot via the wizard form, see fleet analytics as charts, or click around between bots — anywhere the visual surface beats reading tool output.\n\n**Standing secrets rule:** treat `.env` files under `$MOJULO_HOME` and inside any unzipped mojulo bot as containing user secrets that must not enter your conversation context. To inspect a bot's environment, call `inspect_bot_env` — it returns masked values. Never `cat`, `Read`, or otherwise echo raw .env contents."});case"notifications/initialized":case"initialized":return null;case"ping":return c?null:m(a.id,{});case"tools/list":return m(a.id,{tools:Array.from(k.values()).map(a=>({name:a.name,description:a.description||"",inputSchema:a.inputSchema||{type:"object",properties:{}}}))});case"tools/call":return await p(a,b);default:if(c)return null;return n(a.id,-32601,`Method not found: ${a.method}`)}}catch(b){if(console.error("[mcp] dispatch error:",b),c)return null;return n(a.id,-32603,b.message||"Internal error")}}async function p(a,b){let c=a.params||{},d=c.name,e=c.arguments||{},f=k.get(d);if(!f)return n(a.id,-32601,`Unknown tool: ${d}`);try{var g;let c=await f.handler(e,b);return m(a.id,(g=c)&&"object"==typeof g&&Array.isArray(g.content)?g:{content:[{type:"text",text:"string"==typeof g?g:JSON.stringify(g??{},null,2)}]})}catch(b){return m(a.id,{content:[{type:"text",text:b.message||"Tool execution failed"}],isError:!0})}}let q=!1;async function r(){if(q)return;q=!0;let{registerContextTools:a}=await c.e(2550).then(c.bind(c,2550)),{registerBuildTools:b}=await Promise.all([c.e(1556),c.e(634),c.e(2156),c.e(1841),c.e(6233)]).then(c.bind(c,36233)),{registerJobsTools:d}=await Promise.all([c.e(1556),c.e(634),c.e(2156),c.e(1841),c.e(661)]).then(c.bind(c,661)),{registerOperateTools:e}=await Promise.all([c.e(2719),c.e(7121)]).then(c.bind(c,67121)),{registerFleetTools:f}=await Promise.all([c.e(2719),c.e(397)]).then(c.bind(c,50397)),{registerCatalystTools:g}=await c.e(4165).then(c.bind(c,84165));a(),b(),d(),e(),f(),g()}},27910:a=>{"use strict";a.exports=require("stream")},29021:a=>{"use strict";a.exports=require("fs")},29294:a=>{"use strict";a.exports=require("next/dist/server/app-render/work-async-storage.external.js")},31421:a=>{"use strict";a.exports=require("node:child_process")},32467:a=>{"use strict";a.exports=require("node:http2")},33873:a=>{"use strict";a.exports=require("path")},36138:a=>{"use strict";a.exports=require("pdf2json")},37067:a=>{"use strict";a.exports=require("node:http")},44708:a=>{"use strict";a.exports=require("node:https")},44870:a=>{"use strict";a.exports=require("next/dist/compiled/next-server/app-route.runtime.prod.js")},48161:a=>{"use strict";a.exports=require("node:os")},55511:a=>{"use strict";a.exports=require("crypto")},57075:a=>{"use strict";a.exports=require("node:stream")},57975:a=>{"use strict";a.exports=require("node:util")},63033:a=>{"use strict";a.exports=require("next/dist/server/app-render/work-unit-async-storage.external.js")},68023:a=>{"use strict";a.exports=require("officeparser")},73024:a=>{"use strict";a.exports=require("node:fs")},73136:a=>{"use strict";a.exports=require("node:url")},73836:a=>{"use strict";a.exports=require("node:fs/promises")},76760:a=>{"use strict";a.exports=require("node:path")},77598:a=>{"use strict";a.exports=require("node:crypto")},78335:()=>{},79428:a=>{"use strict";a.exports=require("buffer")},79748:a=>{"use strict";a.exports=require("fs/promises")},81630:a=>{"use strict";a.exports=require("http")},86439:a=>{"use strict";a.exports=require("next/dist/shared/lib/no-fallback-error.external")},87550:a=>{"use strict";a.exports=require("better-sqlite3")},92280:(a,b,c)=>{"use strict";Object.defineProperty(b,"I",{enumerable:!0,get:function(){return g}});let d=c(28208),e=c(47617),f=c(62018);async function g(a,b,c,g){if((0,d.isNodeNextResponse)(b)){var h;b.statusCode=c.status,b.statusMessage=c.statusText;let d=["set-cookie","www-authenticate","proxy-authenticate","vary"];null==(h=c.headers)||h.forEach((a,c)=>{if("x-middleware-set-cookie"!==c.toLowerCase())if("set-cookie"===c.toLowerCase())for(let d of(0,f.splitCookiesString)(a))b.appendHeader(c,d);else{let e=void 0!==b.getHeader(c);(d.includes(c.toLowerCase())||!e)&&b.appendHeader(c,a)}});let{originalResponse:i}=b;c.body&&"HEAD"!==a.method?await (0,e.pipeToNodeResponse)(c.body,i,g):i.end()}}},96487:()=>{},99253:a=>{"use strict";a.exports=import("@huggingface/transformers")}};var b=require("../../../webpack-runtime.js");b.C(a);var c=b.X(0,[4741],()=>b(b.s=19170));module.exports=c})();
@@ -25,17 +25,17 @@
25
25
  "/api/vectorize-rag/route": "app/api/vectorize-rag/route.js",
26
26
  "/icon.svg/route": "app/icon.svg/route.js",
27
27
  "/api/deploy/route": "app/api/deploy/route.js",
28
+ "/api/builder/stream/route": "app/api/builder/stream/route.js",
28
29
  "/api/deployments/[id]/connection/route": "app/api/deployments/[id]/connection/route.js",
29
30
  "/api/deployments/[id]/conversations/[conversationId]/route": "app/api/deployments/[id]/conversations/[conversationId]/route.js",
30
- "/api/deployments/[id]/conversations/route": "app/api/deployments/[id]/conversations/route.js",
31
31
  "/api/deployments/[id]/conversations/export/route": "app/api/deployments/[id]/conversations/export/route.js",
32
+ "/api/deployments/[id]/conversations/route": "app/api/deployments/[id]/conversations/route.js",
32
33
  "/api/deployments/[id]/storage/route": "app/api/deployments/[id]/storage/route.js",
33
34
  "/api/deployments/[id]/submissions/export/route": "app/api/deployments/[id]/submissions/export/route.js",
34
35
  "/api/deployments/[id]/submissions/route": "app/api/deployments/[id]/submissions/route.js",
35
36
  "/api/deployments/route": "app/api/deployments/route.js",
36
37
  "/api/generate-form/route": "app/api/generate-form/route.js",
37
38
  "/api/preview/chat/route": "app/api/preview/chat/route.js",
38
- "/api/builder/stream/route": "app/api/builder/stream/route.js",
39
39
  "/dashboard/deployments/[id]/cloud-deploy/page": "app/dashboard/deployments/[id]/cloud-deploy/page.js",
40
40
  "/dashboard/deployments/[id]/conversations/page": "app/dashboard/deployments/[id]/conversations/page.js",
41
41
  "/dashboard/deployments/[id]/submissions/page": "app/dashboard/deployments/[id]/submissions/page.js",
@@ -1 +1 @@
1
- "use strict";exports.id=2550,exports.ids=[2550],exports.modules={2550:(a,b,c)=>{c.d(b,{registerContextTools:()=>h});var d=c(23786);async function e(a,b){return{content:[{type:"text",text:'# Mojulo, oriented\n\nMojulo is a control plane for **chatbot-based solutions**. You build a chatbot, deploy it where users can reach it, let it collect conversations and form submissions, then turn what it captured into action in the tools the user already runs — typically via the other MCP servers they already have installed (Gmail, Google Drive, Google Calendar, plus whichever CRM / ticketing / warehouse MCPs they use).\n\n---\n\n## Secrets handling (standing rule)\n\nA compiled mojulo bot ships a `.env` containing the bot\'s auto-generated `MOJULO_API_KEY` (gates the bot\'s `/api/conversations` admin endpoints). After unzip, the user is expected to paste their LLM provider key (Anthropic / OpenAI / AWS / etc.) into the same `.env` before `docker compose up`. From that point on, the file holds two account-grade secrets.\n\n**Never `cat` or `Read` those `.env` files.** A routine "let me check your .env to debug" reads the raw secret into your conversation context, where it gets persisted, forwarded to the next prompt, and out of the user\'s control.\n\nUse `inspect_bot_env` instead. It returns `{ key, value, masked }` entries — sensitive values come through masked (first 4 + last 4), non-sensitive values (`LLM_PROVIDER=anthropic`, ports, webhook URLs) come through clear. You can still see which keys are present and whether the user has actually pasted a value, without the raw secret crossing into context.\n\nRecommended defense-in-depth: the user can add a deny rule to `.claude/settings.json` so the harness blocks the routine `cat`/`Read` path even if an agent forgets the rule. The MCP doesn\'t enforce this — it\'s the user\'s choice — but suggesting it on first connect is a reasonable nudge:\n\n```json\n{\n "permissions": {\n "deny": [\n "Read(~/.mojulo/**/.env)",\n "Read(~/.mojulo/**/.env.*)",\n "Bash(cat ~/.mojulo/**/.env*)"\n ]\n }\n}\n```\n\nThis applies the same rule **control-plane API keys are already protected by**: `mojulo-config` writes provider keys into the encrypted `api_keys` table via the same AES-GCM path the Settings UI uses, never to plaintext `.env`. The container-side `.env` is the remaining surface, and `inspect_bot_env` is the safe affordance for it.\n\n---\n\n## Verification posture (standing rule)\n\nMojulo **synthesizes; it does not certify.** Every artifact this MCP emits — bot configs from the build tools, catalyst recommendations, synthesized skills written to `.claude/skills/` — is an LLM output and inherits LLM failure modes: hallucinated field names, optimistic destination mappings, assumptions about which MCPs are installed that don\'t match reality.\n\nBefore any artifact graduates from one-shot to recurring execution or fleet-wide fan-out:\n\n1. **Dry-run on one real input.** Use an actual submission / conversation / bot, not a synthetic example.\n2. **Inspect the result.** Validate field shapes, destination payloads, idempotency keys — by reading, not by trusting.\n3. **Only then promote.** Schedule, loop, or fan out across the fleet.\n\nThis applies even when the user has run the same workflow before — schema drift, MCP version bumps, and bot config changes invalidate prior validation silently.\n\n---\n\n## Concepts\n\n- **Bot** — a deployed chatbot service. Runs as its own process (local Docker container or Fly.io app). Owns its own SQLite database; **every conversation and submission lives there and never leaves**.\n- **Deployment** — the control plane\'s row for a bot: id, name, status, URL, enabled capabilities, last_seen_at. The deployment ≠ the bot itself — it\'s the metadata that lets the control plane locate and describe the bot.\n- **Protocol** — a capability a bot can have turned on. Five of them ship today:\n - `knowledge` — answers questions from documents the user uploads (in-process RAG; no external embedding API calls at runtime).\n - `formGathering` — collects structured fields conversationally and writes a submission row.\n - `appointments` — books slots against a configured schedule.\n - `triage` — routes a conversation to a specialist bot via a federated handoff (the audit chain extends across bots).\n - `opticalRead` — extracts data from photos / screenshots (vision-capable models only).\n- **Chain** — every bot turn is hash-linked to the previous one, so the transcript is tamper-evident. `verify_chain` walks the chain for any conversation.\n- **Catalyst** — a workflow recipe shipped with mojulo. You read one with `get_catalyst`, then combine it with what a bot has captured + an MCP the user already has installed to write a local Claude Code skill (`.claude/skills/<name>/SKILL.md`) that turns the captured signal into action. The catalyst itself is a starting point — adapt freely, or skip it and synthesize from scratch. *See the texture preview below.*\n\n---\n\n## Catalyst texture preview\n\nTo set expectations, here is the opening of the canonical `qualify-lead-to-crm` catalyst body — every catalyst is shaped like this:\n\n> **How to synthesize the skill**\n>\n> 1. Call `get_deployment(deploymentId)` to read the bot\'s form schema. The synthesized skill\'s mapping is derived from this schema — never guess field names.\n> 2. Ask the user the three `parameters` questions in one round.\n> 3. Inspect the bound destination MCP to learn its contact-create surface (field names, required props, search-by-property tool). Field mapping is the catalyst\'s value-add — don\'t assume it\'s `name`/`email`/`phone` everywhere; HubSpot uses `firstname`/`lastname`, Salesforce uses `FirstName`/`LastName`, Attio uses object/attribute pairs.\n> 4. Write `.claude/skills/<bot-slug>-crm-sync/SKILL.md` with the synthesized workflow.\n\nThat density runs through the whole body — numbered synthesis steps, mapping rules per field type, pitfalls (PII through the LLM, idempotency, irreversible writes), and calibration tips. Plan to read the entire catalyst before writing the skill; don\'t skim.\n\n---\n\n## Lifecycle: build → deploy → connect → operate\n\n1. **Build.** Pick which protocols (capabilities) the bot needs, generate their configs, upload any documents the bot should know, compose the bot\'s identity. Either drive this step-by-step through the build tools, or just describe the user\'s goal and let the build tools sequence themselves starting from `infer_intent`.\n\n *Builder-session scope.* Build tools share state via a **builder session** keyed on the `mcp-session-id` header your client sends. The session row persists in the control plane\'s SQLite, but the header→session binding is held in process memory. So: the same client reconnecting during a single control-plane process lifetime resumes its in-progress config, while a **control-plane restart drops the binding** and the user\'s next build tool call starts a fresh bot (the orphaned row stays in SQLite). Inside the same connection, `start_new_bot` deliberately discards in-progress config and starts over.\n2. **Deploy.** `save_modular_bot` compiles the configured bot into a zip artifact on disk and returns its absolute path in `artifactPath`. The user runs it locally (`unzip` + `docker compose up`) or in the cloud (Fly.io). Over stdio MCP the zip lives under `$MOJULO_HOME/data/artifacts/` (default `~/.mojulo/data/artifacts/`) — hand the user the `artifactPath` value verbatim. The legacy `downloadUrl` field in the response is a Next.js-route path; ignore it over stdio. The container image is bot-agnostic — per-bot config is injected at start time, so the same image runs every bot the user has.\n3. **Connect.** Once the bot starts, it phones home to the control plane with its URL. From then on the control plane can reach it through a bearer-authenticated proxy. **Conversation data stays in the bot\'s SQLite forever** — the control plane only stores `url` and `last_seen_at`. Any tool that needs transcript data proxies through to the bot in real time.\n4. **Operate.** Use the operate tools to read what bots have captured. Use the catalyst tools to turn that captured signal into action via the user\'s other installed MCPs.\n5. **Operate the fleet.** Once multiple bots are connected, fleet-level questions ("how is the whole fleet doing?", "which bots saw the most activity?", "find any conversation across every bot that mentioned X") have their own surface — the `fleet_*` tools. They fan out across every connected bot and aggregate in process memory; conversation content still stays on each bot. The natural two-step pattern is **fleet-locate** with `fleet_query_conversations` → **per-bot-read** with `get_conversation`. Same posture as single-bot operate, just batched. Cross-bot catalysts (the new category fleet aggregation enables) come from `recommend_catalysts` with `scope: \'fleet\'`.\n\n---\n\n## Tool index (one line each)\n\n### Orientation\n- `forward_context` — (you are reading its output) glossary, lifecycle, tool index.\n- `version` — runtime versions: server, MCP protocol, Node, platform, pinned bot image tag, offline-build flag, MOJULO_HOME. Use to diagnose version mismatches.\n\n### Build, synchronous\n- `infer_intent` — read a free-text description of what the user wants and produce a structured intent the rest of the build tools can act on.\n- `recommend_protocols` — given the intent, suggest which protocols to enable (clamped to what the selected model can reliably support).\n- `compose_identity` — generate the bot\'s name, persona, and starter prompts.\n- `generate_form_schema` — produce the form-field schema for `formGathering`.\n- `generate_appointment_config` — produce booking config for `appointments`.\n- `generate_triage_config` — produce routing config for `triage`.\n- `generate_optical_read_config` — produce extraction config for `opticalRead`.\n- `set_suggested_prompts` — overwrite the starter prompts shown in the bot UI.\n- `generate_bot_summary` — produce the one-line summary stored on the deployment.\n- `get_builder_session` — read the in-progress bot config for this MCP connection.\n- `start_new_bot` — discard the in-progress config and start fresh in this MCP connection.\n\n### Build, documents and artifact compilation\n- `upload_document_from_url` — **sync**, ~1–5s. Upload a PDF / DOCX / TXT / MD / HTML the bot should learn from. Accepts a URL, base64, or pre-extracted text. → returns `{ documentId, originalName, mimeType, sizeBytes, message }`. Pass `documentId` into `process_documents`.\n- `process_documents` — **async**, returns `{ jobId }`. ~10–30s **per document** (parse + chunk + embed + per-doc LLM summary). Many or large docs can run minutes. Makes documents available to the `knowledge` protocol.\n- `save_modular_bot` — **async**, returns `{ jobId }`. ~10–60s in prebuilt-image mode (compose cartridges + write config + zip); longer when the control plane is in offline-build mode (`MOJULO_OFFLINE_BUILD=1` bundles full bot source). Compiles the configured bot into a zip on disk. Polled result: `{ deploymentId, status, botName, artifactPath, buildError, ... }`. `artifactPath` is the absolute path to the zip — that\'s the value to surface to the user.\n- `poll_job` — **sync**. Check the status of any async job. → returns `{ jobId, tool, status: "pending" | "running" | "done" | "error", progress, result, error }`. Reasonable polling cadence is every 2–5s.\n\n### Operate (fleet)\n\nAggregates and metadata only. For conversation content, use `get_conversation` against a specific bot — `fleet_query_conversations` exists to *locate which bot* a conversation lives on; it does not return turn content. All fleet tools return a consistent `unreachable: [{ botId, botName, reason }]` field so you can tell at a glance whether the answer reflects the whole fleet.\n\n- `fleet_analytics_summary` — fleet-wide totals + daily breakdown + top bots + protocol mix + per-bot breakdown. → returns `{ totals, daily, heatmap, topBots, protocolMix, perBot, unreachable, cache }`. Hits a 60s in-process cache; check `cache.fromCache` before answering "is this current?". Warm ~1–3s, cold up to ~30s.\n- `fleet_query_conversations` — locate conversations across every connected bot. → returns `{ conversations: [{ botId, botName, conversationId, startedAt, lastActivity, turnCount }], pagination, fleet, unreachable }`. **Pair with `get_conversation(id, conversationId)` for content** — that\'s the second step of the fleet-locate → per-bot-read pattern.\n- `verify_fleet_chains` — walk the tamper-evident hash chain across every reachable bot. → returns `{ valid, totalTurns, invalidTurns, conversationsVerified, failed, perBot, fleet, unreachable }`. `valid: true` requires zero invalid turns **AND** zero unreachable bots — a dark bot can\'t be audited. This is the one fleet operation that\'s uniquely agent-shaped; humans won\'t manually audit chains.\n\n### Operate (read what deployed bots have captured)\n- `list_deployments` — list bots known to the control plane. → returns `{ total, limit, offset, deployments: [{ id, botName, status, url, lastSeenAt, configHash, lastBuiltHash, ragMode, embeddingChunkCount, cloud, createdAt, updatedAt }] }`. No transcript data.\n- `get_deployment` — full row for one bot. → returns the list-shape fields above, **plus** `config` (the bot\'s identity, suggested prompts, enabled protocols, generated form/appointment/triage/optical-read configs — credentials redacted), `botSummary`, `documentIds`. **The identity prompt, form schema, and per-protocol configs all live under `config`** — this is the tool to call when a catalyst says "read the bot\'s identity" or "read the form schema."\n- `inspect_bot_env` — read the bot\'s container `.env` with sensitive values masked. → returns `{ path, vars: [{ key, value, masked, valueLength? }], maskedCount, note }`. **Use this instead of `cat .env`** — see the Secrets handling standing rule above. Takes `deploymentId` (resolves under `$MOJULO_HOME`) or an explicit `path` if the user unzipped elsewhere.\n- `query_conversations` — conversation summaries on a connected bot (proxied — conversation data lives in the bot\'s SQLite, not here). → returns `{ botName, total, conversations: [{ conversationId, startedAt, lastActivity, turnCount }] }`. No turn content; call `get_conversation` or `export_conversations` for that.\n- `get_conversation` — full turn list for one conversation. → returns `{ conversationId, turnCount, turns, verification }`. Turn fields: `id, conversationId, turn, timestamp, userPrompt, llmResponse, machineState, ragContext, contentHash, chainHash, eventType, handoffHash`.\n- `export_conversations` — bulk export full conversations and turns. → returns `{ botName, conversations: [{ conversationId, startedAt, lastActivity, turnCount, turns }] }`. Same turn shape as `get_conversation`.\n- `query_submissions` — list form-gathering submissions. → returns `{ botName, submissions: [{ id, conversationId, formData, metadata, schemaFingerprint, isComplete, submittedAt, webhookStatus, webhookError }], count, total }`. `formData` is an object keyed by form-field id — call `get_deployment` to read the field schema you\'ll be mapping from.\n- `verify_chain` — walk the tamper-evident hash chain for one conversation. → returns the bot\'s verification result (valid / invalid + per-turn details). See `docs/turn-hashing.md` for chain semantics.\n\n### Designing a new protocol\n\n- `custom_protocol` — author\'s guide for designing a new mojulo protocol (a new bot capability that fires inside a conversation). Returns posture-check rules, the mental model (stackable cartridges + composed response template), the intent-loop-first validation discipline, and the touch-point map. Call this when the user says they want to **extend what their bot does during a turn** — recognize a new intent class, collect a new shape of structured data, render a new UI affordance via the envelope, read a new modality. Do NOT call this for after-the-conversation work (CRM sync, digests, audits) — that\'s catalyst-shaped; route to `recommend_catalysts` / `custom_catalyst` instead. The guide explicitly disambiguates protocol vs. catalyst vs. skill; the most common misfire is calling it when the user actually wants a catalyst.\n\n### Catalysts (consult on outcomes; turn captured signal into action)\n\nMojulo is a **consultation surface**, not a strict executor. When the user asks what to do with a deployed bot, you should be ready to suggest workflows even when they require an integration the user doesn\'t yet have installed — framed as opt-in upgrades, never as blockers.\n\n- `recommend_catalysts` — given a `deploymentId` (single-bot mode) OR `scope: \'fleet\'` / `deploymentIds: [...]` (fleet mode), return catalysts whose shape matches the bot(s), each annotated with a `valueHook` (one-sentence user-outcome), `destinationCategory` (kind of MCP needed), and `destinationExamples` (named MCPs that satisfy it). Single-bot mode adds `missingProtocols`; fleet mode adds `applicableDeployments: [{ id, botName }]` plus `crossBot: true` when a catalyst spans ≥2 bots — those are the cross-bot patterns fleet aggregation unlocks (e.g., "weekly digest of qualified leads across every intake bot into one CRM"). Response includes a `consultationPosture` block with framing rules — read it. **This is the entry point for "what can I do with this bot?" or "what can I do across all my bots?"** Cross-reference `destinationExamples` against MCPs available in this session: examples installed → "you can do this now"; examples not installed → soft suggestion.\n- `list_catalysts` — flat catalog of every shipped recipe, filterable by category. Use when the user wants to browse what mojulo offers in general, or when no specific bot is in scope.\n- `get_catalyst` — read one recipe\'s full body (the response also includes a synthesizer briefing) so you can write a local skill into the user\'s `.claude/skills/`.\n- `custom_catalyst` — author\'s guide for **contributing a new catalyst back to the mojulo library**. Use when the user wants to propose / write / contribute a catalyst (not when they want to automate something just for themselves — that\'s a local skill, synthesized from `get_catalyst` or from intent directly).\n\n---\n\n## Quick orientation rules\n\n- User wants to **build a new bot**: start with `infer_intent`, or jump straight to the specific `generate_*` tool if the user already knows what they need.\n- User wants to **see what bots exist**: `list_deployments`.\n- User wants to **understand state across multiple bots** ("how is the fleet doing?", "which bots are busiest this week?"): `fleet_analytics_summary`. For finding specific conversations across the fleet: `fleet_query_conversations` to locate, then `get_conversation` against the named bot to read content. For auditing chain integrity across every bot at once: `verify_fleet_chains`. The fleet tools never expose conversation content — they\'re the "where to look" surface; per-bot `get_conversation` is the "read it" surface.\n- User wants to **do something with what a bot has collected** OR is asking "what can this bot unlock for me?": `recommend_catalysts` with the bot\'s deployment id. Surface suggestions in consultation form — including catalysts whose destination MCP isn\'t installed yet, framed as opt-in upgrades. Then `get_catalyst` to read the recipe before writing a skill.\n- User wants to **automate something that spans multiple bots** ("digest leads from every bot", "audit all my appointment bookings together"): `recommend_catalysts` with `scope: \'fleet\'`. Fleet-applicable catalysts come back with `applicableDeployments` so the synthesized skill knows which bots to iterate over; `crossBot: true` flags the patterns that only make sense across multiple bots.\n- User wants to **browse the catalyst library** without a specific bot in mind: `list_catalysts`.\n- User wants to **contribute a new catalyst** (write / propose / add one to mojulo\'s shipped library): `custom_catalyst`. This returns an author\'s guide. If the user only wants to automate something for themselves and isn\'t trying to contribute, do *not* call `custom_catalyst` — synthesize a local skill from `get_catalyst` or from intent instead.\n- User wants to **extend what the bot does inside a conversation** ("I want my bot to recognize a new intent and track new state", "can my bot read X from the user?", "I want to add a new capability to mojulo"): `custom_protocol`. Returns the protocol design guide. Critical disambiguation up front: if the work happens *after* the conversation (sync to CRM, weekly digest, ticket on signal), that\'s a catalyst, not a protocol — route to `recommend_catalysts` instead. Protocols fire during the agent loop, on every reply, in the LLM\'s envelope. The guide walks the posture-check first.\n- User wants to **audit** a conversation\'s integrity: `verify_chain`.\n- Conversation and submission data are never copied into the control plane. If you need transcript content, fetch it through the operate tools — don\'t try to cache it server-side.\n'}]}}async function f(a,b){return{content:[{type:"text",text:"# Designing a mojulo protocol — author's guide\n\nYou are about to help the user think through a new mojulo **protocol** — a bot capability that fires inside a conversation, on every reply, in the LLM's envelope. Five ship today (`knowledge`, `formGathering`, `appointments`, `triage`, `opticalRead`). A new one is a code change to mojulo, not a config tweak, and it ripples through the cartridge composer, the response envelope, the wizard, and the chat builder.\n\nIf you're unclear on protocol vs. catalyst vs. skill, call `forward_context` first — those three terms overlap, and protocol design goes sideways fast if they're not kept distinct.\n\n---\n\n## Step 0 — Posture check (push back here, before designing anything)\n\nProtocols are a heavier commitment than catalysts. Many requests that *sound* protocol-shaped are actually catalysts; a few are identity-prompt tweaks. Walk these before drafting.\n\n**A protocol is the wrong tool if any apply:**\n\n1. **The work happens after the conversation.** Pushing form submissions to a CRM, summarizing a week of chats, scanning logs for signal — these run on already-captured data; the bot has nothing to do with them during a turn. → **catalyst.**\n2. **The work is operator- or scheduler-initiated.** \"Once a week, email me a digest\", \"when someone fills the form, file a ticket\" — the end user shouldn't have to trigger it by talking to the bot. → **catalyst.**\n3. **The work touches external systems with credentials.** CRM, ticketing, calendar, Slack, docs. Mojulo deliberately keeps integration credentials in Claude Code (where the user's MCP servers live), not in the bot's runtime — adding them to a protocol would invert that architecture for one capability. → **catalyst.**\n4. **The capability is bespoke to one client, vertical, or workflow.** Upstream protocols have to clear a broader-applicability bar (the existing five did). One-off needs belong in a fork or as catalyst-synthesized skills. → **fork or skill.**\n5. **The work is purely about how the bot phrases something.** \"Be more empathetic\", \"ask a follow-up before answering\" — that's the identity prompt or the objective string, not a new protocol. → **`compose_identity` or bot objective.**\n\nIf any apply, name it explicitly to the user and route them — don't try to fit the request into a protocol shape.\n\n**Example pushback:**\n\n> User: \"I want a protocol that emails me whenever someone fills out the form.\"\n>\n> You: That's catalyst-shaped, not protocol-shaped — the work happens *after* the conversation, it's operator-initiated, and it touches an external system with credentials. The `formGathering` protocol you already have captures the submission; a catalyst is what routes the captured data outward. Want me to walk you through `recommend_catalysts` instead? If you want to *contribute* a new catalyst back to mojulo's library, that's `custom_catalyst`.\n\n---\n\n## The mental model — three properties that drive the design\n\nIf your protocol idea violates any of these, the design is probably wrong. Test against all three before drafting.\n\n1. **Stackable, not switched.** Bots are rarely \"just knowledge\" or \"just forms.\" A clinic bot wants knowledge + forms + appointments; a concierge wants knowledge + triage. The composer takes an `{ knowledge, formGathering, appointments, triage, opticalRead, <yours> }` toggle map and **concatenates** the matching cartridges. Adding a sixth capability is a new file + a registry entry, not a refactor.\n2. **Prose AND response shape come out together.** Every protocol that asks the LLM to *do* something also adds *fields the LLM must return*. Forms need `formTracker`. Appointments need `calendarId`. Triage needs `deploymentId`. Optical-read needs `extractedFields`. If your protocol adds new behavior but no envelope fields, you don't have a protocol — you have an identity-prompt tweak. If it adds new fields, both halves get composed from the same toggle map and ship as one document.\n3. **The artifact is the contract.** The wizard and chat builder are convenience layers; they produce the same `instructions.txt` + envelope a hand-author would. So the engineering question for a new protocol is narrow: **can you get an LLM to emit your new top-level envelope field reliably, given a hand-crafted prompt?** If yes, the wiring through the composer and builders is mechanical. If no, no amount of plumbing fixes flaky prose.\n\n---\n\n## Step 1 — Validate the intent loop on hand-authored instructions, BEFORE touching the composer\n\nThis is the single most load-bearing piece of protocol design and the step that gets skipped most often. Steps 2-onward wire a *working* cartridge into the system; they do not make a flaky cartridge less flaky.\n\n**What \"the intent loop\" means:** a turn comes in, the LLM reads `instructions.txt`, matches the user's input against your protocol's inline data, and emits an envelope with your new top-level field (`yourField`, `appointment.calendarId`, `triage.deploymentId`) **populated when expected and empty otherwise**.\n\nValidate this **without** the composer, **without** the wizard, **without** the chat builder, on an unzipped `lite-template/`:\n\n1. Hand-author `config/instructions.txt`: start with the contents of `00_base.txt` (the safety floor every bot ships with), append your cartridge prose, then your inline data pasted under a `## <YOUR_PROTOCOL>` header, then a `## RESPONSE FORMAT PROTOCOL` block listing your new field alongside `answer` and `suggestions`.\n2. Point `config/config.json` at an **OpenAI or Ollama** provider. **Do NOT use Anthropic for this step.** Anthropic's forced tool use enforces the canonical envelope schema with `additionalProperties: false` and silently drops fields you haven't added there yet — you'll think your protocol is broken when actually the wire layer is filtering it. OpenAI and Ollama extract via prose, so they pass new fields through unchanged.\n3. `npm install && npm start`, POST to `/api/chat`, inspect responses. Tune cartridge prose and inline-data shape until your field fires consistently on the inputs you expect and stays empty on the ones you don't.\n\nEncourage the user to do this **before** any composer/wizard wiring. If they can't get the intent firing here, every other step is wasted work. The composer just hands the same prompt to the same model.\n\n---\n\n## Step 2 — Design the inline data shape\n\nEach existing protocol ships per-deploy data alongside its prose, **stripped to the minimum the LLM needs**:\n\n- `formGathering` → form structure stripped to `id, label, condition, required`. Field types, validation, UI hints stay on the frontend.\n- `appointments` → calendar destinations as-is (small shape, no leakable secrets).\n- `triage` → routes stripped to `deploymentId, name, description`. The `url` field is **deliberately excluded** — it's a client-side redirect handle, and keeping it out of the prompt prevents the LLM from emitting raw URLs in `answer` text.\n- `opticalRead` → extraction fields stripped to `idName, label, hint`. Wizard widget metadata stays out.\n\nFor the user's protocol, design a `build<Name>Section()` helper that takes per-deploy config and returns either a header + JSON section or an empty string on missing/invalid input. **Strip aggressively.** Never leak URLs, credentials, or rendering-side metadata into the prompt — they cost tokens and tempt the LLM to leak them back out in `answer`.\n\n---\n\n## Step 3 — Design the response attribute group\n\nIf the protocol adds envelope fields (it almost certainly does — that's the engineering question of step 1), it adds a `<NAME>_ATTRIBUTES` group to the response-builder. **Use inline descriptions as values**, not a separate description block:\n\n```js\nconst YOUR_ATTRIBUTES = {\n yourField: 'description of what the LLM should put here',\n yourFlag: 'true/false',\n // ...\n};\n```\n\nThe LLM sees the field name AND a hint about what to put there in one place. Easier to keep in sync than two parallel documents.\n\nWatch for the `suggestions` collision pattern: `formGathering` and `triage` both override the core `suggestions` description with one specific to that protocol. Last write wins in protocol order. If the user's protocol has its own preferred phrasing for `suggestions`, mention this — they may want to override.\n\nKnowledge protocol adds **no** response attributes — it shapes how `answer` should be written (paragraph length, RAG anchoring) but doesn't introduce new fields. That's a legitimate shape too, but rarer; most useful protocols emit at least one new envelope field.\n\n---\n\n## Step 4 — Map the touch points\n\nA new protocol, end to end, touches these files:\n\n| File | What to add |\n|---|---|\n| `control/lib/composer/protocols/XT_<name>.txt` | The cartridge prose. Imperative voice, blunt, no preamble — written for the LLM, not for a human reader. |\n| `control/lib/composer/composer.js` | Entries in `PROTOCOL_FILES` (the toggle-to-file map) and `PROTOCOL_ORDER` (the deterministic stacking order). If the protocol needs inline data, write a `build<Name>Section()` helper here too. |\n| `control/lib/composer/response-builder.js` | The `<NAME>_ATTRIBUTES` group from step 3 + a conditional `Object.assign` in `buildResponseFormatSection` keyed on the toggle. |\n| `lite-template/helper/envelope-schema.js` | Add the new top-level fields to the canonical envelope. **Without this, Anthropic forced tool use silently drops them at the wire.** |\n| `control/lib/envelope-schema.js` | **Mirror the same change.** This file is duplicated by hand — there is no shared layer between control plane and bot runtime. Missing the mirror is a common rake. |\n| Wizard step + chat-builder tool | Both write to the same `enabledProtocols.<name>` toggle and the same `protocolData.<name>` bucket so the composer doesn't care which builder produced the config. |\n| `control/lib/llm-providers.js` (maybe) | Decide whether `RESTRICTED_OLLAMA_MODELS` (qwen3, mistral-nemo) can run the new protocol. If it's tool-use-heavy (multi-step state tracking like forms / appointments / triage / optical-read), leave the allowlist alone and it's implicitly gated off for small Ollama models. If it's knowledge-style (RAG + free text, no multi-step state), add the protocol ID to the allowlist. |\n\nWhat you do **not** touch: the deployer, the bot runtime, the prompt assembler, the response parser. Past `composeInstructions`, nothing branches on which protocols are on. The composed `instructions.txt` is the contract, and a new file with a new toggle is enough.\n\n---\n\n## Step 5 — Hand off\n\nWhen you've walked the user through the design, tell them:\n\n- This is a **code change to mojulo**, not a config — there are two paths:\n - **Fork.** Keep the protocol in the user's fork; deploy bots from there. Right path for bespoke / client-specific capabilities.\n - **Upstream PR** against https://github.com/zombico/mojulo. The bar is \"broader applicability than one workflow.\" The existing five cleared it; a sixth has to too.\n- The most likely failure mode is **skipping step 1** (validating the intent loop on hand-authored instructions). Encourage the user to prove the intent fires on OpenAI or Ollama before wiring anything else.\n- The second most likely failure mode is **forgetting the envelope-schema mirror**. Both files have to change in lockstep, or Anthropic deploys silently drop the new fields.\n- If the user is not confident their idea clears the upstream bar, point them at the catalyst path instead — local skills synthesized from catalysts cover the \"I want this for my specific bot\" case without changing mojulo's runtime.\n\n---\n\n## Anti-patterns — things NOT to do\n\n- **Don't add credentials or destination URLs to the cartridge prose or inline data.** Those belong in catalysts, not protocols. The architecture deliberately keeps the bot runtime free of integration credentials so the bot stays portable.\n- **Don't add a protocol that only rewords the bot's answer.** Identity prompts and the objective string handle phrasing. A protocol is justified by *new envelope state* or *new multi-turn structure*, not by tone.\n- **Don't propose a protocol when you mean a catalyst.** Walk Step 0 carefully. \"I want my bot to send X to Y\" is almost always a catalyst.\n- **Don't skip the envelope-schema mirror.** Update both `lite-template/helper/envelope-schema.js` AND `control/lib/envelope-schema.js`. Anthropic enforces the canonical schema at the wire; missing fields are dropped silently and the bot looks broken with no error.\n- **Don't ship a protocol whose intent loop only works on one model.** A capability that fires reliably on gpt-5 but flakes on Claude Sonnet 4.5 isn't ready. Tune the cartridge prose until it works across the providers mojulo supports — or scope the protocol to the providers that can carry it.\n\n---\n\n## Final reminders\n\n- **The cartridge prose is read by an LLM, not a human.** Short lines, imperative voice, no preamble. Look at the existing five cartridges for the texture — bluntness is a feature.\n- **Stripping is a discipline.** Every byte in the prompt either earns its tokens by helping the LLM make a decision, or it doesn't. Inline-data helpers exist to strip aggressively.\n- **The artifact is the contract.** A bot whose `instructions.txt` was written by hand is indistinguishable at runtime from one the wizard produced. The composer, wizard, and chat builder exist for ergonomics; they don't improve how reliably the intent fires.\n"}]}}async function g(a,b){return{content:[{type:"text",text:JSON.stringify({server:{name:d.gz,version:(0,d.Sw)()},protocolVersion:d.WP,node:process.version,platform:{os:process.platform,arch:process.arch},botImage:process.env.BOT_IMAGE||"ghcr.io/zombico/mojulo-bot:0.5.1",offlineBuild:"1"===process.env.MOJULO_OFFLINE_BUILD,mojuloHome:process.env.MOJULO_HOME||null},null,2)}]}}function h(){(0,d.SR)({name:"forward_context",description:"Forward the agent the full mojulo orientation: concept glossary (bot, deployment, protocol, chain, catalyst), the build → deploy → connect → operate lifecycle, and a one-line description of every tool in this MCP. Call this FIRST whenever the user asks what mojulo is, how it works, or which tool to pick — or whenever you (the agent) feel uncertain about mojulo's vocabulary or which entry point fits the user's intent. Read-only, no inputs, idempotent.",inputSchema:{type:"object",properties:{}},handler:e}),(0,d.SR)({name:"version",description:"Report runtime versions: server name + version (from package.json), MCP protocol version, Node version, platform os/arch, the pinned bot container image tag, whether MOJULO_OFFLINE_BUILD is on, and the active MOJULO_HOME. Use this to diagnose version mismatches between a user-reported issue and what their control plane is actually running, or to confirm a version bump landed after a publish. Read-only, no inputs, idempotent.",inputSchema:{type:"object",properties:{}},handler:g}),(0,d.SR)({name:"custom_protocol",description:"Return an author's guide for designing a new mojulo PROTOCOL — a bot capability that fires inside a conversation (every turn, in the LLM's envelope). Use this when the user wants to extend what their bot does *during a turn*: recognize a new intent class, collect a new shape of structured data across turns, render a new UI affordance via the envelope, read a new modality. Do NOT call this when the user wants something that happens *after* the conversation (CRM sync, weekly digests, log scans, ticket-on-signal) — that's catalyst-shaped, route to recommend_catalysts / custom_catalyst instead. The guide opens with a posture check disambiguating protocol vs. catalyst vs. identity-prompt-tweak (the most common misfire is calling this when the user actually wants a catalyst), then walks the mental model (stackable cartridges + composed response envelope, prove the intent loop on hand-authored instructions before wiring), then the touch-point map (cartridge file, registry entry, response attributes, envelope schema mirror, builder hooks). The output of this workflow is a design the user takes to a fork or an upstream PR — not a single file like custom_catalyst produces. Read-only, no inputs, idempotent.",inputSchema:{type:"object",properties:{}},handler:f})}}};
1
+ "use strict";exports.id=2550,exports.ids=[2550],exports.modules={2550:(a,b,c)=>{c.d(b,{registerContextTools:()=>h});var d=c(23786);async function e(a,b){return{content:[{type:"text",text:"# Mojulo, oriented\n\nMojulo is a control plane for **chatbot-based solutions**. You build a chatbot, deploy it where users can reach it, let it collect conversations and form submissions, then turn what it captured into action in the tools the user already runs — typically via the other MCP servers they already have installed (Gmail, Google Drive, Google Calendar, plus whichever CRM / ticketing / warehouse MCPs they use).\n\n---\n\n## Two faces, one state\n\nMojulo ships two binaries against the same `~/.mojulo/` state:\n\n- **`mojulo`** — this MCP, the agent-shaped face. You're talking to it right now. Build via chat, drive operations programmatically.\n- **`mojulo-ui`** — a local Next.js dashboard, the human-shaped face. Bound to 127.0.0.1, launched with `npx -y -p mojulo mojulo-ui`. Reads the same SQLite at `~/.mojulo/data/mojulo-lite.db` via WAL mode, so the two run side-by-side and a bot you minted via MCP shows up in the dashboard's fleet view immediately.\n\nSuggest the dashboard when the user asks for something the visual surface does better:\n\n- **Browse** conversations or submissions interactively (filter, scroll, scan) rather than paging through tool output.\n- **Mint** a bot via the wizard form rather than chat-builder turn-taking — useful when the user wants to set fields directly without describing them.\n- **Inspect** fleet analytics as charts rather than JSON tables.\n- **Manage** deploys (re-build, rotate keys via Settings, manually trigger cloud-deploy) by clicking rather than orchestrating tool calls.\n\nThe default mode is still MCP — don't push the dashboard for tasks that work fine in chat. Suggest it when the user explicitly wants to *look*, *browse*, or *click*, or when you've exhausted a few rounds of tool output and they're still missing something a visual scan would catch in a second.\n\n---\n\n## Secrets handling (standing rule)\n\nA compiled mojulo bot ships a `.env` containing the bot's auto-generated `MOJULO_API_KEY` (gates the bot's `/api/conversations` admin endpoints). After unzip, the user is expected to paste their LLM provider key (Anthropic / OpenAI / AWS / etc.) into the same `.env` before `docker compose up`. From that point on, the file holds two account-grade secrets.\n\n**Never `cat` or `Read` those `.env` files.** A routine \"let me check your .env to debug\" reads the raw secret into your conversation context, where it gets persisted, forwarded to the next prompt, and out of the user's control.\n\nUse `inspect_bot_env` instead. It returns `{ key, value, masked }` entries — sensitive values come through masked (first 4 + last 4), non-sensitive values (`LLM_PROVIDER=anthropic`, ports, webhook URLs) come through clear. You can still see which keys are present and whether the user has actually pasted a value, without the raw secret crossing into context.\n\nRecommended defense-in-depth: the user can add a deny rule to `.claude/settings.json` so the harness blocks the routine `cat`/`Read` path even if an agent forgets the rule. The MCP doesn't enforce this — it's the user's choice — but suggesting it on first connect is a reasonable nudge:\n\n```json\n{\n \"permissions\": {\n \"deny\": [\n \"Read(~/.mojulo/**/.env)\",\n \"Read(~/.mojulo/**/.env.*)\",\n \"Bash(cat ~/.mojulo/**/.env*)\"\n ]\n }\n}\n```\n\nThis applies the same rule **control-plane API keys are already protected by**: `mojulo-config` writes provider keys into the encrypted `api_keys` table via the same AES-GCM path the Settings UI uses, never to plaintext `.env`. The container-side `.env` is the remaining surface, and `inspect_bot_env` is the safe affordance for it.\n\n---\n\n## Verification posture (standing rule)\n\nMojulo **synthesizes; it does not certify.** Every artifact this MCP emits — bot configs from the build tools, catalyst recommendations, synthesized skills written to `.claude/skills/` — is an LLM output and inherits LLM failure modes: hallucinated field names, optimistic destination mappings, assumptions about which MCPs are installed that don't match reality.\n\nBefore any artifact graduates from one-shot to recurring execution or fleet-wide fan-out:\n\n1. **Dry-run on one real input.** Use an actual submission / conversation / bot, not a synthetic example.\n2. **Inspect the result.** Validate field shapes, destination payloads, idempotency keys — by reading, not by trusting.\n3. **Only then promote.** Schedule, loop, or fan out across the fleet.\n\nThis applies even when the user has run the same workflow before — schema drift, MCP version bumps, and bot config changes invalidate prior validation silently.\n\n---\n\n## Concepts\n\n- **Bot** — a deployed chatbot service. Runs as its own process (local Docker container or Fly.io app). Owns its own SQLite database; **every conversation and submission lives there and never leaves**.\n- **Deployment** — the control plane's row for a bot: id, name, status, URL, enabled capabilities, last_seen_at. The deployment ≠ the bot itself — it's the metadata that lets the control plane locate and describe the bot.\n- **Protocol** — a capability a bot can have turned on. Five of them ship today:\n - `knowledge` — answers questions from documents the user uploads (in-process RAG; no external embedding API calls at runtime).\n - `formGathering` — collects structured fields conversationally and writes a submission row.\n - `appointments` — books slots against a configured schedule.\n - `triage` — routes a conversation to a specialist bot via a federated handoff (the audit chain extends across bots).\n - `opticalRead` — extracts data from photos / screenshots (vision-capable models only).\n- **Chain** — every bot turn is hash-linked to the previous one, so the transcript is tamper-evident. `verify_chain` walks the chain for any conversation.\n- **Catalyst** — a workflow recipe shipped with mojulo. You read one with `get_catalyst`, then combine it with what a bot has captured + an MCP the user already has installed to write a local Claude Code skill (`.claude/skills/<name>/SKILL.md`) that turns the captured signal into action. The catalyst itself is a starting point — adapt freely, or skip it and synthesize from scratch. *See the texture preview below.*\n\n---\n\n## Catalyst texture preview\n\nTo set expectations, here is the opening of the canonical `qualify-lead-to-crm` catalyst body — every catalyst is shaped like this:\n\n> **How to synthesize the skill**\n>\n> 1. Call `get_deployment(deploymentId)` to read the bot's form schema. The synthesized skill's mapping is derived from this schema — never guess field names.\n> 2. Ask the user the three `parameters` questions in one round.\n> 3. Inspect the bound destination MCP to learn its contact-create surface (field names, required props, search-by-property tool). Field mapping is the catalyst's value-add — don't assume it's `name`/`email`/`phone` everywhere; HubSpot uses `firstname`/`lastname`, Salesforce uses `FirstName`/`LastName`, Attio uses object/attribute pairs.\n> 4. Write `.claude/skills/<bot-slug>-crm-sync/SKILL.md` with the synthesized workflow.\n\nThat density runs through the whole body — numbered synthesis steps, mapping rules per field type, pitfalls (PII through the LLM, idempotency, irreversible writes), and calibration tips. Plan to read the entire catalyst before writing the skill; don't skim.\n\n---\n\n## Lifecycle: build → deploy → connect → operate\n\n1. **Build.** Pick which protocols (capabilities) the bot needs, generate their configs, upload any documents the bot should know, compose the bot's identity. Either drive this step-by-step through the build tools, or just describe the user's goal and let the build tools sequence themselves starting from `infer_intent`.\n\n *Builder-session scope.* Build tools share state via a **builder session** keyed on the `mcp-session-id` header your client sends. The session row persists in the control plane's SQLite, but the header→session binding is held in process memory. So: the same client reconnecting during a single control-plane process lifetime resumes its in-progress config, while a **control-plane restart drops the binding** and the user's next build tool call starts a fresh bot (the orphaned row stays in SQLite). Inside the same connection, `start_new_bot` deliberately discards in-progress config and starts over.\n2. **Deploy.** `save_modular_bot` compiles the configured bot into a zip artifact on disk and returns its absolute path in `artifactPath`. The user runs it locally (`unzip` + `docker compose up`) or in the cloud (Fly.io). Over stdio MCP the zip lives under `$MOJULO_HOME/data/artifacts/` (default `~/.mojulo/data/artifacts/`) — hand the user the `artifactPath` value verbatim. The legacy `downloadUrl` field in the response is a Next.js-route path; ignore it over stdio. The container image is bot-agnostic — per-bot config is injected at start time, so the same image runs every bot the user has.\n3. **Connect.** Once the bot starts, it phones home to the control plane with its URL. From then on the control plane can reach it through a bearer-authenticated proxy. **Conversation data stays in the bot's SQLite forever** — the control plane only stores `url` and `last_seen_at`. Any tool that needs transcript data proxies through to the bot in real time.\n4. **Operate.** Use the operate tools to read what bots have captured. Use the catalyst tools to turn that captured signal into action via the user's other installed MCPs.\n5. **Operate the fleet.** Once multiple bots are connected, fleet-level questions (\"how is the whole fleet doing?\", \"which bots saw the most activity?\", \"find any conversation across every bot that mentioned X\") have their own surface — the `fleet_*` tools. They fan out across every connected bot and aggregate in process memory; conversation content still stays on each bot. The natural two-step pattern is **fleet-locate** with `fleet_query_conversations` → **per-bot-read** with `get_conversation`. Same posture as single-bot operate, just batched. Cross-bot catalysts (the new category fleet aggregation enables) come from `recommend_catalysts` with `scope: 'fleet'`.\n\n---\n\n## Tool index (one line each)\n\n### Orientation\n- `forward_context` — (you are reading its output) glossary, lifecycle, tool index.\n- `version` — runtime versions: server, MCP protocol, Node, platform, pinned bot image tag, offline-build flag, MOJULO_HOME. Use to diagnose version mismatches.\n\n### Build, synchronous\n- `infer_intent` — read a free-text description of what the user wants and produce a structured intent the rest of the build tools can act on.\n- `recommend_protocols` — given the intent, suggest which protocols to enable (clamped to what the selected model can reliably support).\n- `compose_identity` — generate the bot's name, persona, and starter prompts.\n- `generate_form_schema` — produce the form-field schema for `formGathering`.\n- `generate_appointment_config` — produce booking config for `appointments`.\n- `generate_triage_config` — produce routing config for `triage`.\n- `generate_optical_read_config` — produce extraction config for `opticalRead`.\n- `set_suggested_prompts` — overwrite the starter prompts shown in the bot UI.\n- `generate_bot_summary` — produce the one-line summary stored on the deployment.\n- `get_builder_session` — read the in-progress bot config for this MCP connection.\n- `start_new_bot` — discard the in-progress config and start fresh in this MCP connection.\n\n### Build, documents and artifact compilation\n- `upload_document_from_url` — **sync**, ~1–5s. Upload a PDF / DOCX / TXT / MD / HTML the bot should learn from. Accepts a URL, base64, or pre-extracted text. → returns `{ documentId, originalName, mimeType, sizeBytes, message }`. Pass `documentId` into `process_documents`.\n- `process_documents` — **async**, returns `{ jobId }`. ~10–30s **per document** (parse + chunk + embed + per-doc LLM summary). Many or large docs can run minutes. Makes documents available to the `knowledge` protocol.\n- `save_modular_bot` — **async**, returns `{ jobId }`. ~10–60s in prebuilt-image mode (compose cartridges + write config + zip); longer when the control plane is in offline-build mode (`MOJULO_OFFLINE_BUILD=1` bundles full bot source). Compiles the configured bot into a zip on disk. Polled result: `{ deploymentId, status, botName, artifactPath, buildError, ... }`. `artifactPath` is the absolute path to the zip — that's the value to surface to the user.\n- `poll_job` — **sync**. Check the status of any async job. → returns `{ jobId, tool, status: \"pending\" | \"running\" | \"done\" | \"error\", progress, result, error }`. Reasonable polling cadence is every 2–5s.\n\n### Operate (fleet)\n\nAggregates and metadata only. For conversation content, use `get_conversation` against a specific bot — `fleet_query_conversations` exists to *locate which bot* a conversation lives on; it does not return turn content. All fleet tools return a consistent `unreachable: [{ botId, botName, reason }]` field so you can tell at a glance whether the answer reflects the whole fleet.\n\n- `fleet_analytics_summary` — fleet-wide totals + daily breakdown + top bots + protocol mix + per-bot breakdown. → returns `{ totals, daily, heatmap, topBots, protocolMix, perBot, unreachable, cache }`. Hits a 60s in-process cache; check `cache.fromCache` before answering \"is this current?\". Warm ~1–3s, cold up to ~30s.\n- `fleet_query_conversations` — locate conversations across every connected bot. → returns `{ conversations: [{ botId, botName, conversationId, startedAt, lastActivity, turnCount }], pagination, fleet, unreachable }`. **Pair with `get_conversation(id, conversationId)` for content** — that's the second step of the fleet-locate → per-bot-read pattern.\n- `verify_fleet_chains` — walk the tamper-evident hash chain across every reachable bot. → returns `{ valid, totalTurns, invalidTurns, conversationsVerified, failed, perBot, fleet, unreachable }`. `valid: true` requires zero invalid turns **AND** zero unreachable bots — a dark bot can't be audited. This is the one fleet operation that's uniquely agent-shaped; humans won't manually audit chains.\n\n### Operate (read what deployed bots have captured)\n- `list_deployments` — list bots known to the control plane. → returns `{ total, limit, offset, deployments: [{ id, botName, status, url, lastSeenAt, configHash, lastBuiltHash, ragMode, embeddingChunkCount, cloud, createdAt, updatedAt }] }`. No transcript data.\n- `get_deployment` — full row for one bot. → returns the list-shape fields above, **plus** `config` (the bot's identity, suggested prompts, enabled protocols, generated form/appointment/triage/optical-read configs — credentials redacted), `botSummary`, `documentIds`. **The identity prompt, form schema, and per-protocol configs all live under `config`** — this is the tool to call when a catalyst says \"read the bot's identity\" or \"read the form schema.\"\n- `inspect_bot_env` — read the bot's container `.env` with sensitive values masked. → returns `{ path, vars: [{ key, value, masked, valueLength? }], maskedCount, note }`. **Use this instead of `cat .env`** — see the Secrets handling standing rule above. Takes `deploymentId` (resolves under `$MOJULO_HOME`) or an explicit `path` if the user unzipped elsewhere.\n- `query_conversations` — conversation summaries on a connected bot (proxied — conversation data lives in the bot's SQLite, not here). → returns `{ botName, total, conversations: [{ conversationId, startedAt, lastActivity, turnCount }] }`. No turn content; call `get_conversation` or `export_conversations` for that.\n- `get_conversation` — full turn list for one conversation. → returns `{ conversationId, turnCount, turns, verification }`. Turn fields: `id, conversationId, turn, timestamp, userPrompt, llmResponse, machineState, ragContext, contentHash, chainHash, eventType, handoffHash`.\n- `export_conversations` — bulk export full conversations and turns. → returns `{ botName, conversations: [{ conversationId, startedAt, lastActivity, turnCount, turns }] }`. Same turn shape as `get_conversation`.\n- `query_submissions` — list form-gathering submissions. → returns `{ botName, submissions: [{ id, conversationId, formData, metadata, schemaFingerprint, isComplete, submittedAt, webhookStatus, webhookError }], count, total }`. `formData` is an object keyed by form-field id — call `get_deployment` to read the field schema you'll be mapping from.\n- `verify_chain` — walk the tamper-evident hash chain for one conversation. → returns the bot's verification result (valid / invalid + per-turn details). See `docs/turn-hashing.md` for chain semantics.\n\n### Designing a new protocol\n\n- `custom_protocol` — author's guide for designing a new mojulo protocol (a new bot capability that fires inside a conversation). Returns posture-check rules, the mental model (stackable cartridges + composed response template), the intent-loop-first validation discipline, and the touch-point map. Call this when the user says they want to **extend what their bot does during a turn** — recognize a new intent class, collect a new shape of structured data, render a new UI affordance via the envelope, read a new modality. Do NOT call this for after-the-conversation work (CRM sync, digests, audits) — that's catalyst-shaped; route to `recommend_catalysts` / `custom_catalyst` instead. The guide explicitly disambiguates protocol vs. catalyst vs. skill; the most common misfire is calling it when the user actually wants a catalyst.\n\n### Catalysts (consult on outcomes; turn captured signal into action)\n\nMojulo is a **consultation surface**, not a strict executor. When the user asks what to do with a deployed bot, you should be ready to suggest workflows even when they require an integration the user doesn't yet have installed — framed as opt-in upgrades, never as blockers.\n\n- `recommend_catalysts` — given a `deploymentId` (single-bot mode) OR `scope: 'fleet'` / `deploymentIds: [...]` (fleet mode), return catalysts whose shape matches the bot(s), each annotated with a `valueHook` (one-sentence user-outcome), `destinationCategory` (kind of MCP needed), and `destinationExamples` (named MCPs that satisfy it). Single-bot mode adds `missingProtocols`; fleet mode adds `applicableDeployments: [{ id, botName }]` plus `crossBot: true` when a catalyst spans ≥2 bots — those are the cross-bot patterns fleet aggregation unlocks (e.g., \"weekly digest of qualified leads across every intake bot into one CRM\"). Response includes a `consultationPosture` block with framing rules — read it. **This is the entry point for \"what can I do with this bot?\" or \"what can I do across all my bots?\"** Cross-reference `destinationExamples` against MCPs available in this session: examples installed → \"you can do this now\"; examples not installed → soft suggestion.\n- `list_catalysts` — flat catalog of every shipped recipe, filterable by category. Use when the user wants to browse what mojulo offers in general, or when no specific bot is in scope.\n- `get_catalyst` — read one recipe's full body (the response also includes a synthesizer briefing) so you can write a local skill into the user's `.claude/skills/`.\n- `custom_catalyst` — author's guide for **contributing a new catalyst back to the mojulo library**. Use when the user wants to propose / write / contribute a catalyst (not when they want to automate something just for themselves — that's a local skill, synthesized from `get_catalyst` or from intent directly).\n\n---\n\n## Quick orientation rules\n\n- User wants to **build a new bot**: start with `infer_intent`, or jump straight to the specific `generate_*` tool if the user already knows what they need.\n- User wants to **see what bots exist**: `list_deployments`.\n- User wants to **understand state across multiple bots** (\"how is the fleet doing?\", \"which bots are busiest this week?\"): `fleet_analytics_summary`. For finding specific conversations across the fleet: `fleet_query_conversations` to locate, then `get_conversation` against the named bot to read content. For auditing chain integrity across every bot at once: `verify_fleet_chains`. The fleet tools never expose conversation content — they're the \"where to look\" surface; per-bot `get_conversation` is the \"read it\" surface.\n- User wants to **do something with what a bot has collected** OR is asking \"what can this bot unlock for me?\": `recommend_catalysts` with the bot's deployment id. Surface suggestions in consultation form — including catalysts whose destination MCP isn't installed yet, framed as opt-in upgrades. Then `get_catalyst` to read the recipe before writing a skill.\n- User wants to **automate something that spans multiple bots** (\"digest leads from every bot\", \"audit all my appointment bookings together\"): `recommend_catalysts` with `scope: 'fleet'`. Fleet-applicable catalysts come back with `applicableDeployments` so the synthesized skill knows which bots to iterate over; `crossBot: true` flags the patterns that only make sense across multiple bots.\n- User wants to **browse the catalyst library** without a specific bot in mind: `list_catalysts`.\n- User wants to **contribute a new catalyst** (write / propose / add one to mojulo's shipped library): `custom_catalyst`. This returns an author's guide. If the user only wants to automate something for themselves and isn't trying to contribute, do *not* call `custom_catalyst` — synthesize a local skill from `get_catalyst` or from intent instead.\n- User wants to **extend what the bot does inside a conversation** (\"I want my bot to recognize a new intent and track new state\", \"can my bot read X from the user?\", \"I want to add a new capability to mojulo\"): `custom_protocol`. Returns the protocol design guide. Critical disambiguation up front: if the work happens *after* the conversation (sync to CRM, weekly digest, ticket on signal), that's a catalyst, not a protocol — route to `recommend_catalysts` instead. Protocols fire during the agent loop, on every reply, in the LLM's envelope. The guide walks the posture-check first.\n- User wants to **audit** a conversation's integrity: `verify_chain`.\n- Conversation and submission data are never copied into the control plane. If you need transcript content, fetch it through the operate tools — don't try to cache it server-side.\n"}]}}async function f(a,b){return{content:[{type:"text",text:"# Designing a mojulo protocol — author's guide\n\nYou are about to help the user think through a new mojulo **protocol** — a bot capability that fires inside a conversation, on every reply, in the LLM's envelope. Five ship today (`knowledge`, `formGathering`, `appointments`, `triage`, `opticalRead`). A new one is a code change to mojulo, not a config tweak, and it ripples through the cartridge composer, the response envelope, the wizard, and the chat builder.\n\nIf you're unclear on protocol vs. catalyst vs. skill, call `forward_context` first — those three terms overlap, and protocol design goes sideways fast if they're not kept distinct.\n\n---\n\n## Step 0 — Posture check (push back here, before designing anything)\n\nProtocols are a heavier commitment than catalysts. Many requests that *sound* protocol-shaped are actually catalysts; a few are identity-prompt tweaks. Walk these before drafting.\n\n**A protocol is the wrong tool if any apply:**\n\n1. **The work happens after the conversation.** Pushing form submissions to a CRM, summarizing a week of chats, scanning logs for signal — these run on already-captured data; the bot has nothing to do with them during a turn. → **catalyst.**\n2. **The work is operator- or scheduler-initiated.** \"Once a week, email me a digest\", \"when someone fills the form, file a ticket\" — the end user shouldn't have to trigger it by talking to the bot. → **catalyst.**\n3. **The work touches external systems with credentials.** CRM, ticketing, calendar, Slack, docs. Mojulo deliberately keeps integration credentials in Claude Code (where the user's MCP servers live), not in the bot's runtime — adding them to a protocol would invert that architecture for one capability. → **catalyst.**\n4. **The capability is bespoke to one client, vertical, or workflow.** Upstream protocols have to clear a broader-applicability bar (the existing five did). One-off needs belong in a fork or as catalyst-synthesized skills. → **fork or skill.**\n5. **The work is purely about how the bot phrases something.** \"Be more empathetic\", \"ask a follow-up before answering\" — that's the identity prompt or the objective string, not a new protocol. → **`compose_identity` or bot objective.**\n\nIf any apply, name it explicitly to the user and route them — don't try to fit the request into a protocol shape.\n\n**Example pushback:**\n\n> User: \"I want a protocol that emails me whenever someone fills out the form.\"\n>\n> You: That's catalyst-shaped, not protocol-shaped — the work happens *after* the conversation, it's operator-initiated, and it touches an external system with credentials. The `formGathering` protocol you already have captures the submission; a catalyst is what routes the captured data outward. Want me to walk you through `recommend_catalysts` instead? If you want to *contribute* a new catalyst back to mojulo's library, that's `custom_catalyst`.\n\n---\n\n## The mental model — three properties that drive the design\n\nIf your protocol idea violates any of these, the design is probably wrong. Test against all three before drafting.\n\n1. **Stackable, not switched.** Bots are rarely \"just knowledge\" or \"just forms.\" A clinic bot wants knowledge + forms + appointments; a concierge wants knowledge + triage. The composer takes an `{ knowledge, formGathering, appointments, triage, opticalRead, <yours> }` toggle map and **concatenates** the matching cartridges. Adding a sixth capability is a new file + a registry entry, not a refactor.\n2. **Prose AND response shape come out together.** Every protocol that asks the LLM to *do* something also adds *fields the LLM must return*. Forms need `formTracker`. Appointments need `calendarId`. Triage needs `deploymentId`. Optical-read needs `extractedFields`. If your protocol adds new behavior but no envelope fields, you don't have a protocol — you have an identity-prompt tweak. If it adds new fields, both halves get composed from the same toggle map and ship as one document.\n3. **The artifact is the contract.** The wizard and chat builder are convenience layers; they produce the same `instructions.txt` + envelope a hand-author would. So the engineering question for a new protocol is narrow: **can you get an LLM to emit your new top-level envelope field reliably, given a hand-crafted prompt?** If yes, the wiring through the composer and builders is mechanical. If no, no amount of plumbing fixes flaky prose.\n\n---\n\n## Step 1 — Validate the intent loop on hand-authored instructions, BEFORE touching the composer\n\nThis is the single most load-bearing piece of protocol design and the step that gets skipped most often. Steps 2-onward wire a *working* cartridge into the system; they do not make a flaky cartridge less flaky.\n\n**What \"the intent loop\" means:** a turn comes in, the LLM reads `instructions.txt`, matches the user's input against your protocol's inline data, and emits an envelope with your new top-level field (`yourField`, `appointment.calendarId`, `triage.deploymentId`) **populated when expected and empty otherwise**.\n\nValidate this **without** the composer, **without** the wizard, **without** the chat builder, on an unzipped `lite-template/`:\n\n1. Hand-author `config/instructions.txt`: start with the contents of `00_base.txt` (the safety floor every bot ships with), append your cartridge prose, then your inline data pasted under a `## <YOUR_PROTOCOL>` header, then a `## RESPONSE FORMAT PROTOCOL` block listing your new field alongside `answer` and `suggestions`.\n2. Point `config/config.json` at an **OpenAI or Ollama** provider. **Do NOT use Anthropic for this step.** Anthropic's forced tool use enforces the canonical envelope schema with `additionalProperties: false` and silently drops fields you haven't added there yet — you'll think your protocol is broken when actually the wire layer is filtering it. OpenAI and Ollama extract via prose, so they pass new fields through unchanged.\n3. `npm install && npm start`, POST to `/api/chat`, inspect responses. Tune cartridge prose and inline-data shape until your field fires consistently on the inputs you expect and stays empty on the ones you don't.\n\nEncourage the user to do this **before** any composer/wizard wiring. If they can't get the intent firing here, every other step is wasted work. The composer just hands the same prompt to the same model.\n\n---\n\n## Step 2 — Design the inline data shape\n\nEach existing protocol ships per-deploy data alongside its prose, **stripped to the minimum the LLM needs**:\n\n- `formGathering` → form structure stripped to `id, label, condition, required`. Field types, validation, UI hints stay on the frontend.\n- `appointments` → calendar destinations as-is (small shape, no leakable secrets).\n- `triage` → routes stripped to `deploymentId, name, description`. The `url` field is **deliberately excluded** — it's a client-side redirect handle, and keeping it out of the prompt prevents the LLM from emitting raw URLs in `answer` text.\n- `opticalRead` → extraction fields stripped to `idName, label, hint`. Wizard widget metadata stays out.\n\nFor the user's protocol, design a `build<Name>Section()` helper that takes per-deploy config and returns either a header + JSON section or an empty string on missing/invalid input. **Strip aggressively.** Never leak URLs, credentials, or rendering-side metadata into the prompt — they cost tokens and tempt the LLM to leak them back out in `answer`.\n\n---\n\n## Step 3 — Design the response attribute group\n\nIf the protocol adds envelope fields (it almost certainly does — that's the engineering question of step 1), it adds a `<NAME>_ATTRIBUTES` group to the response-builder. **Use inline descriptions as values**, not a separate description block:\n\n```js\nconst YOUR_ATTRIBUTES = {\n yourField: 'description of what the LLM should put here',\n yourFlag: 'true/false',\n // ...\n};\n```\n\nThe LLM sees the field name AND a hint about what to put there in one place. Easier to keep in sync than two parallel documents.\n\nWatch for the `suggestions` collision pattern: `formGathering` and `triage` both override the core `suggestions` description with one specific to that protocol. Last write wins in protocol order. If the user's protocol has its own preferred phrasing for `suggestions`, mention this — they may want to override.\n\nKnowledge protocol adds **no** response attributes — it shapes how `answer` should be written (paragraph length, RAG anchoring) but doesn't introduce new fields. That's a legitimate shape too, but rarer; most useful protocols emit at least one new envelope field.\n\n---\n\n## Step 4 — Map the touch points\n\nA new protocol, end to end, touches these files:\n\n| File | What to add |\n|---|---|\n| `control/lib/composer/protocols/XT_<name>.txt` | The cartridge prose. Imperative voice, blunt, no preamble — written for the LLM, not for a human reader. |\n| `control/lib/composer/composer.js` | Entries in `PROTOCOL_FILES` (the toggle-to-file map) and `PROTOCOL_ORDER` (the deterministic stacking order). If the protocol needs inline data, write a `build<Name>Section()` helper here too. |\n| `control/lib/composer/response-builder.js` | The `<NAME>_ATTRIBUTES` group from step 3 + a conditional `Object.assign` in `buildResponseFormatSection` keyed on the toggle. |\n| `lite-template/helper/envelope-schema.js` | Add the new top-level fields to the canonical envelope. **Without this, Anthropic forced tool use silently drops them at the wire.** |\n| `control/lib/envelope-schema.js` | **Mirror the same change.** This file is duplicated by hand — there is no shared layer between control plane and bot runtime. Missing the mirror is a common rake. |\n| Wizard step + chat-builder tool | Both write to the same `enabledProtocols.<name>` toggle and the same `protocolData.<name>` bucket so the composer doesn't care which builder produced the config. |\n| `control/lib/llm-providers.js` (maybe) | Decide whether `RESTRICTED_OLLAMA_MODELS` (qwen3, mistral-nemo) can run the new protocol. If it's tool-use-heavy (multi-step state tracking like forms / appointments / triage / optical-read), leave the allowlist alone and it's implicitly gated off for small Ollama models. If it's knowledge-style (RAG + free text, no multi-step state), add the protocol ID to the allowlist. |\n\nWhat you do **not** touch: the deployer, the bot runtime, the prompt assembler, the response parser. Past `composeInstructions`, nothing branches on which protocols are on. The composed `instructions.txt` is the contract, and a new file with a new toggle is enough.\n\n---\n\n## Step 5 — Hand off\n\nWhen you've walked the user through the design, tell them:\n\n- This is a **code change to mojulo**, not a config — there are two paths:\n - **Fork.** Keep the protocol in the user's fork; deploy bots from there. Right path for bespoke / client-specific capabilities.\n - **Upstream PR** against https://github.com/zombico/mojulo. The bar is \"broader applicability than one workflow.\" The existing five cleared it; a sixth has to too.\n- The most likely failure mode is **skipping step 1** (validating the intent loop on hand-authored instructions). Encourage the user to prove the intent fires on OpenAI or Ollama before wiring anything else.\n- The second most likely failure mode is **forgetting the envelope-schema mirror**. Both files have to change in lockstep, or Anthropic deploys silently drop the new fields.\n- If the user is not confident their idea clears the upstream bar, point them at the catalyst path instead — local skills synthesized from catalysts cover the \"I want this for my specific bot\" case without changing mojulo's runtime.\n\n---\n\n## Anti-patterns — things NOT to do\n\n- **Don't add credentials or destination URLs to the cartridge prose or inline data.** Those belong in catalysts, not protocols. The architecture deliberately keeps the bot runtime free of integration credentials so the bot stays portable.\n- **Don't add a protocol that only rewords the bot's answer.** Identity prompts and the objective string handle phrasing. A protocol is justified by *new envelope state* or *new multi-turn structure*, not by tone.\n- **Don't propose a protocol when you mean a catalyst.** Walk Step 0 carefully. \"I want my bot to send X to Y\" is almost always a catalyst.\n- **Don't skip the envelope-schema mirror.** Update both `lite-template/helper/envelope-schema.js` AND `control/lib/envelope-schema.js`. Anthropic enforces the canonical schema at the wire; missing fields are dropped silently and the bot looks broken with no error.\n- **Don't ship a protocol whose intent loop only works on one model.** A capability that fires reliably on gpt-5 but flakes on Claude Sonnet 4.5 isn't ready. Tune the cartridge prose until it works across the providers mojulo supports — or scope the protocol to the providers that can carry it.\n\n---\n\n## Final reminders\n\n- **The cartridge prose is read by an LLM, not a human.** Short lines, imperative voice, no preamble. Look at the existing five cartridges for the texture — bluntness is a feature.\n- **Stripping is a discipline.** Every byte in the prompt either earns its tokens by helping the LLM make a decision, or it doesn't. Inline-data helpers exist to strip aggressively.\n- **The artifact is the contract.** A bot whose `instructions.txt` was written by hand is indistinguishable at runtime from one the wizard produced. The composer, wizard, and chat builder exist for ergonomics; they don't improve how reliably the intent fires.\n"}]}}async function g(a,b){return{content:[{type:"text",text:JSON.stringify({server:{name:d.gz,version:(0,d.Sw)()},protocolVersion:d.WP,node:process.version,platform:{os:process.platform,arch:process.arch},botImage:process.env.BOT_IMAGE||"ghcr.io/zombico/mojulo-bot:0.5.1",offlineBuild:"1"===process.env.MOJULO_OFFLINE_BUILD,mojuloHome:process.env.MOJULO_HOME||null},null,2)}]}}function h(){(0,d.SR)({name:"forward_context",description:"Forward the agent the full mojulo orientation: concept glossary (bot, deployment, protocol, chain, catalyst), the build → deploy → connect → operate lifecycle, and a one-line description of every tool in this MCP. Call this FIRST whenever the user asks what mojulo is, how it works, or which tool to pick — or whenever you (the agent) feel uncertain about mojulo's vocabulary or which entry point fits the user's intent. Read-only, no inputs, idempotent.",inputSchema:{type:"object",properties:{}},handler:e}),(0,d.SR)({name:"version",description:"Report runtime versions: server name + version (from package.json), MCP protocol version, Node version, platform os/arch, the pinned bot container image tag, whether MOJULO_OFFLINE_BUILD is on, and the active MOJULO_HOME. Use this to diagnose version mismatches between a user-reported issue and what their control plane is actually running, or to confirm a version bump landed after a publish. Read-only, no inputs, idempotent.",inputSchema:{type:"object",properties:{}},handler:g}),(0,d.SR)({name:"custom_protocol",description:"Return an author's guide for designing a new mojulo PROTOCOL — a bot capability that fires inside a conversation (every turn, in the LLM's envelope). Use this when the user wants to extend what their bot does *during a turn*: recognize a new intent class, collect a new shape of structured data across turns, render a new UI affordance via the envelope, read a new modality. Do NOT call this when the user wants something that happens *after* the conversation (CRM sync, weekly digests, log scans, ticket-on-signal) — that's catalyst-shaped, route to recommend_catalysts / custom_catalyst instead. The guide opens with a posture check disambiguating protocol vs. catalyst vs. identity-prompt-tweak (the most common misfire is calling this when the user actually wants a catalyst), then walks the mental model (stackable cartridges + composed response envelope, prove the intent loop on hand-authored instructions before wiring), then the touch-point map (cartridge file, registry entry, response attributes, envelope schema mirror, builder hooks). The output of this workflow is a design the user takes to a fork or an upstream PR — not a single file like custom_catalyst produces. Read-only, no inputs, idempotent.",inputSchema:{type:"object",properties:{}},handler:f})}}};
@@ -1 +1 @@
1
- globalThis.__BUILD_MANIFEST={polyfillFiles:["static/chunks/polyfills-42372ed130431b0a.js"],devFiles:[],lowPriorityFiles:["static/M4n2s70bp3uicUyR8DQUW/_buildManifest.js","static/M4n2s70bp3uicUyR8DQUW/_ssgManifest.js"],rootMainFiles:["static/chunks/webpack-67cbd0917f84c95c.js","static/chunks/4bd1b696-d3a0b478714afd8c.js","static/chunks/3794-eb6127acb14cbca0.js","static/chunks/main-app-9004260ca53d2edf.js"],rootMainFilesTree:{},pages:{"/_app":[]}};
1
+ globalThis.__BUILD_MANIFEST={polyfillFiles:["static/chunks/polyfills-42372ed130431b0a.js"],devFiles:[],lowPriorityFiles:["static/PfPW5h-7TCjPjzndQKTL8/_buildManifest.js","static/PfPW5h-7TCjPjzndQKTL8/_ssgManifest.js"],rootMainFiles:["static/chunks/webpack-67cbd0917f84c95c.js","static/chunks/4bd1b696-d3a0b478714afd8c.js","static/chunks/3794-eb6127acb14cbca0.js","static/chunks/main-app-9004260ca53d2edf.js"],rootMainFilesTree:{},pages:{"/_app":[]}};
@@ -18,11 +18,11 @@
18
18
  "wasm": [],
19
19
  "assets": [],
20
20
  "env": {
21
- "__NEXT_BUILD_ID": "M4n2s70bp3uicUyR8DQUW",
22
- "NEXT_SERVER_ACTIONS_ENCRYPTION_KEY": "Y+qFAC9xFt1qjPGkpJnZrasFs9Aq977SNFdSkVHfNUY=",
23
- "__NEXT_PREVIEW_MODE_ID": "1067d2418886a1420bef010118c34392",
24
- "__NEXT_PREVIEW_MODE_SIGNING_KEY": "532766d07af06addd442bdce5d39f3d7766784e01383b39c8c408a9a71b1e94b",
25
- "__NEXT_PREVIEW_MODE_ENCRYPTION_KEY": "07dcf3653b866ad85ab3ce0c48a5a2c7b894dade60740ef9958af9f72f17fb22"
21
+ "__NEXT_BUILD_ID": "PfPW5h-7TCjPjzndQKTL8",
22
+ "NEXT_SERVER_ACTIONS_ENCRYPTION_KEY": "E/P49WCTOidDRCFUWwhVH4QZPmPmge5usc7DOK6jKf0=",
23
+ "__NEXT_PREVIEW_MODE_ID": "322f901a3c13d4c80b66605f354ff723",
24
+ "__NEXT_PREVIEW_MODE_SIGNING_KEY": "73ce710a1a223302e21855e911ca2eb1e307158cae4b8cdcb276bb552d3049cd",
25
+ "__NEXT_PREVIEW_MODE_ENCRYPTION_KEY": "bde05ebd8ef17b0346d68ba9eb1b3c3577dd55a363348c4448f71b200c2759d1"
26
26
  }
27
27
  }
28
28
  },
@@ -1 +1 @@
1
- <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-67cbd0917f84c95c.js"/><script src="/_next/static/chunks/4bd1b696-d3a0b478714afd8c.js" async=""></script><script src="/_next/static/chunks/3794-eb6127acb14cbca0.js" async=""></script><script src="/_next/static/chunks/main-app-9004260ca53d2edf.js" async=""></script><meta name="next-size-adjust" content=""/><title>500: This page couldn’t load</title><style>:root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }</style><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;display:flex;align-items:center;justify-content:center"><div style="margin-top:-32px;max-width:325px;padding:32px 28px;text-align:left"><svg width="32" height="32" viewBox="-0.2 -1.5 32 32" fill="none" style="margin-bottom:24px"><path d="M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z" fill="var(--next-error-title)"></path></svg><h1 style="font-size:24px;font-weight:500;letter-spacing:-0.02em;line-height:32px;margin:0 0 12px 0;color:var(--next-error-title)">This page couldn’t load</h1><p style="font-size:14px;font-weight:400;line-height:21px;margin:0 0 20px 0;color:var(--next-error-message)">A server error occurred. Reload to try again.</p><form style="margin:0"><button type="submit" style="display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;font-size:14px;font-weight:500;line-height:20px;border-radius:6px;cursor:pointer;color:var(--next-error-btn-text);background:var(--next-error-btn-bg);border:var(--next-error-btn-border)">Reload</button></form></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-67cbd0917f84c95c.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[7121,[],\"\"]\n3:I[4581,[],\"\"]\n4:I[484,[],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[484,[],\"ViewportBoundary\"]\na:I[484,[],\"MetadataBoundary\"]\nc:I[7123,[],\"default\",1]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"_global-error\",{\"children\":[\"__PAGE__\",{}]}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"title\",null,{\"children\":\"500: This page couldn’t load\"}],[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\"}}]]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"-32px\",\"maxWidth\":\"325px\",\"padding\":\"32px 28px\",\"textAlign\":\"left\"},\"children\":[[\"$\",\"svg\",null,{\"width\":\"32\",\"height\":\"32\",\"viewBox\":\"-0.2 -1.5 32 32\",\"fill\":\"none\",\"style\":{\"marginBottom\":\"24px\"},\"children\":[\"$\",\"path\",null,{\"d\":\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\",\"fill\":\"var(--next-error-title)\"}]}],[\"$\",\"h1\",null,{\"style\":{\"fontSize\":\"24px\",\"fontWeight\":500,\"letterSpacing\":\"-0.02em\",\"lineHeight\":\"32px\",\"margin\":\"0 0 12px 0\",\"color\":\"var(--next-error-title)\"},\"children\":\"This page couldn’t load\"}],[\"$\",\"p\",null,{\"style\":{\"fontSize\":\"14px\",\"fontWeight\":400,\"lineHeight\":\"21px\",\"margin\":\"0 0 20px 0\",\"color\":\"var(--next-error-message)\"},\"children\":\"A server error occurred. Reload to try again.\"}],[\"$\",\"form\",null,{\"style\":{\"margin\":0},\"children\":[\"$\",\"button\",null,{\"type\":\"submit\",\"style\":{\"display\":\"inline-flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"height\":\"32px\",\"padding\":\"0 12px\",\"fontSize\":\"14px\",\"fontWeight\":500,\"lineHeight\":\"20px\",\"borderRadius\":\"6px\",\"cursor\":\"pointer\",\"color\":\"var(--next-error-btn-text)\",\"background\":\"var(--next-error-btn-bg)\",\"border\":\"var(--next-error-btn-border)\"},\"children\":\"Reload\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$@7\"]},null,false,\"$@7\"],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"M4n2s70bp3uicUyR8DQUW\"}\n"])</script><script>self.__next_f.push([1,"d:[]\n7:\"$Wd\"\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\nb:[]\n"])</script></body></html>
1
+ <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-67cbd0917f84c95c.js"/><script src="/_next/static/chunks/4bd1b696-d3a0b478714afd8c.js" async=""></script><script src="/_next/static/chunks/3794-eb6127acb14cbca0.js" async=""></script><script src="/_next/static/chunks/main-app-9004260ca53d2edf.js" async=""></script><meta name="next-size-adjust" content=""/><title>500: This page couldn’t load</title><style>:root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }</style><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;display:flex;align-items:center;justify-content:center"><div style="margin-top:-32px;max-width:325px;padding:32px 28px;text-align:left"><svg width="32" height="32" viewBox="-0.2 -1.5 32 32" fill="none" style="margin-bottom:24px"><path d="M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z" fill="var(--next-error-title)"></path></svg><h1 style="font-size:24px;font-weight:500;letter-spacing:-0.02em;line-height:32px;margin:0 0 12px 0;color:var(--next-error-title)">This page couldn’t load</h1><p style="font-size:14px;font-weight:400;line-height:21px;margin:0 0 20px 0;color:var(--next-error-message)">A server error occurred. Reload to try again.</p><form style="margin:0"><button type="submit" style="display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;font-size:14px;font-weight:500;line-height:20px;border-radius:6px;cursor:pointer;color:var(--next-error-btn-text);background:var(--next-error-btn-bg);border:var(--next-error-btn-border)">Reload</button></form></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-67cbd0917f84c95c.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[7121,[],\"\"]\n3:I[4581,[],\"\"]\n4:I[484,[],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[484,[],\"ViewportBoundary\"]\na:I[484,[],\"MetadataBoundary\"]\nc:I[7123,[],\"default\",1]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"_global-error\",{\"children\":[\"__PAGE__\",{}]}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"title\",null,{\"children\":\"500: This page couldn’t load\"}],[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\"}}]]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"-32px\",\"maxWidth\":\"325px\",\"padding\":\"32px 28px\",\"textAlign\":\"left\"},\"children\":[[\"$\",\"svg\",null,{\"width\":\"32\",\"height\":\"32\",\"viewBox\":\"-0.2 -1.5 32 32\",\"fill\":\"none\",\"style\":{\"marginBottom\":\"24px\"},\"children\":[\"$\",\"path\",null,{\"d\":\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\",\"fill\":\"var(--next-error-title)\"}]}],[\"$\",\"h1\",null,{\"style\":{\"fontSize\":\"24px\",\"fontWeight\":500,\"letterSpacing\":\"-0.02em\",\"lineHeight\":\"32px\",\"margin\":\"0 0 12px 0\",\"color\":\"var(--next-error-title)\"},\"children\":\"This page couldn’t load\"}],[\"$\",\"p\",null,{\"style\":{\"fontSize\":\"14px\",\"fontWeight\":400,\"lineHeight\":\"21px\",\"margin\":\"0 0 20px 0\",\"color\":\"var(--next-error-message)\"},\"children\":\"A server error occurred. Reload to try again.\"}],[\"$\",\"form\",null,{\"style\":{\"margin\":0},\"children\":[\"$\",\"button\",null,{\"type\":\"submit\",\"style\":{\"display\":\"inline-flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"height\":\"32px\",\"padding\":\"0 12px\",\"fontSize\":\"14px\",\"fontWeight\":500,\"lineHeight\":\"20px\",\"borderRadius\":\"6px\",\"cursor\":\"pointer\",\"color\":\"var(--next-error-btn-text)\",\"background\":\"var(--next-error-btn-bg)\",\"border\":\"var(--next-error-btn-border)\"},\"children\":\"Reload\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$@7\"]},null,false,\"$@7\"],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"PfPW5h-7TCjPjzndQKTL8\"}\n"])</script><script>self.__next_f.push([1,"d:[]\n7:\"$Wd\"\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\nb:[]\n"])</script></body></html>
@@ -1 +1 @@
1
- {"node":{},"edge":{},"encryptionKey":"Y+qFAC9xFt1qjPGkpJnZrasFs9Aq977SNFdSkVHfNUY="}
1
+ {"node":{},"edge":{},"encryptionKey":"E/P49WCTOidDRCFUWwhVH4QZPmPmge5usc7DOK6jKf0="}
@@ -26,6 +26,24 @@ Mojulo is a control plane for **chatbot-based solutions**. You build a chatbot,
26
26
 
27
27
  ---
28
28
 
29
+ ## Two faces, one state
30
+
31
+ Mojulo ships two binaries against the same \`~/.mojulo/\` state:
32
+
33
+ - **\`mojulo\`** — this MCP, the agent-shaped face. You're talking to it right now. Build via chat, drive operations programmatically.
34
+ - **\`mojulo-ui\`** — a local Next.js dashboard, the human-shaped face. Bound to 127.0.0.1, launched with \`npx -y -p mojulo mojulo-ui\`. Reads the same SQLite at \`~/.mojulo/data/mojulo-lite.db\` via WAL mode, so the two run side-by-side and a bot you minted via MCP shows up in the dashboard's fleet view immediately.
35
+
36
+ Suggest the dashboard when the user asks for something the visual surface does better:
37
+
38
+ - **Browse** conversations or submissions interactively (filter, scroll, scan) rather than paging through tool output.
39
+ - **Mint** a bot via the wizard form rather than chat-builder turn-taking — useful when the user wants to set fields directly without describing them.
40
+ - **Inspect** fleet analytics as charts rather than JSON tables.
41
+ - **Manage** deploys (re-build, rotate keys via Settings, manually trigger cloud-deploy) by clicking rather than orchestrating tool calls.
42
+
43
+ The default mode is still MCP — don't push the dashboard for tasks that work fine in chat. Suggest it when the user explicitly wants to *look*, *browse*, or *click*, or when you've exhausted a few rounds of tool output and they're still missing something a visual scan would catch in a second.
44
+
45
+ ---
46
+
29
47
  ## Secrets handling (standing rule)
30
48
 
31
49
  A compiled mojulo bot ships a \`.env\` containing the bot's auto-generated \`MOJULO_API_KEY\` (gates the bot's \`/api/conversations\` admin endpoints). After unzip, the user is expected to paste their LLM provider key (Anthropic / OpenAI / AWS / etc.) into the same \`.env\` before \`docker compose up\`. From that point on, the file holds two account-grade secrets.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mojulo",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Mojulo — MCP server for building self-hosted chatbots from inside Claude.",
6
6
  "author": "Franz Ombico",
package/README.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  **MCP server for building self-hosted chatbots from inside Claude.** Describe the bot you want — Mojulo compiles it into a portable Docker artifact you own. Conversations live in the bot's own SQLite, hash-chained turn by turn. The MCP surface composes alongside your other MCPs (Drive, Gmail, your CRM), so the build/deploy/operate loop runs entirely inside a Claude session.
4
4
 
5
+ Two binaries, one install:
6
+
7
+ - `mojulo` — stdio MCP server (`npx -y mojulo`, wired into Claude).
8
+ - `mojulo-ui` — local dashboard for visual operation (`npx -y -p mojulo mojulo-ui`).
9
+ - `mojulo-config` — provider key CLI (`npx -y -p mojulo mojulo-config set anthropic sk-...`).
10
+
11
+ Both `mojulo` and `mojulo-ui` share the same `~/.mojulo/` state, so anything you mint through Claude shows up in the dashboard's fleet view immediately, and vice versa.
12
+
5
13
  ## Quickstart
6
14
 
7
15
  ```bash
@@ -14,6 +22,12 @@ npx -y -p mojulo mojulo-config set anthropic sk-ant-...
14
22
 
15
23
  # 3. In a Claude session, ask:
16
24
  # "build me a triage bot for my dental practice"
25
+
26
+ # 4. Operate the fleet visually (optional, anytime):
27
+ npx -y -p mojulo mojulo-ui
28
+ # Opens a local dashboard at 127.0.0.1 and pops your browser. Shares
29
+ # ~/.mojulo/ with the MCP, so any bot you mint via Claude appears in
30
+ # the fleet view immediately. Flags: --port <n>, --no-open, --help.
17
31
  ```
18
32
 
19
33
  Compiled bots land in `~/.mojulo/data/artifacts/`. Run them with `docker compose up`, or set a Fly token (`npx -y -p mojulo mojulo-config set fly fo1_...`) and ask Claude to deploy to the cloud.
@@ -35,14 +49,22 @@ Claude reads one, binds it to a destination MCP you already have installed, and
35
49
 
36
50
  ## Dashboard
37
51
 
38
- There's a browser dashboard (Settings UI, in-app builder, wizard) but it's not shipped in this npm package yet — clone the repo to run it:
52
+ The `mojulo-ui` bin boots a local Next.js dashboard on 127.0.0.1, no clone required:
39
53
 
40
54
  ```bash
41
- git clone https://github.com/zombico/mojulo.git
42
- cd mojulo/control && cp .env.example .env && npm install && npm run dev
55
+ npx -y -p mojulo mojulo-ui # auto-port, opens browser
56
+ npx -y -p mojulo mojulo-ui --port 3999 # pin the port
57
+ npx -y -p mojulo mojulo-ui --no-open # skip browser launch
43
58
  ```
44
59
 
45
- The MCP surface in the npm package covers the build/deploy/operate loop end-to-end without it.
60
+ Same primitives as the MCP, different face useful when you want to:
61
+
62
+ - Browse conversations and submissions interactively (filter, scroll, scan).
63
+ - Mint a bot via the wizard form rather than chat-builder turn-taking.
64
+ - See fleet analytics as charts rather than JSON tables.
65
+ - Click around between bots without leaving the browser.
66
+
67
+ Shares `~/.mojulo/data/mojulo-lite.db` with the MCP via WAL mode, so the two can run side-by-side.
46
68
 
47
69
  ## More
48
70
 
package/lib/mcp/server.js CHANGED
@@ -48,6 +48,8 @@ const SERVER_INSTRUCTIONS = `Mojulo is a control plane for **chatbot-based solut
48
48
 
49
49
  **When the user asks what mojulo is, how it works, or which tools to pick — call \`forward_context\` first.** It returns the concept glossary, the bot capability model, the deploy/connect lifecycle, and a one-line description of every tool, so you can orient before acting.
50
50
 
51
+ **There's also a dashboard.** Mojulo ships a second bin, \`mojulo-ui\`, which boots a local Next.js dashboard at 127.0.0.1 sharing the same \`~/.mojulo/\` state as this MCP. Launch with \`npx -y -p mojulo mojulo-ui\`. Suggest it when the user wants to browse conversations / submissions interactively, mint a bot via the wizard form, see fleet analytics as charts, or click around between bots — anywhere the visual surface beats reading tool output.
52
+
51
53
  **Standing secrets rule:** treat \`.env\` files under \`$MOJULO_HOME\` and inside any unzipped mojulo bot as containing user secrets that must not enter your conversation context. To inspect a bot's environment, call \`inspect_bot_env\` — it returns masked values. Never \`cat\`, \`Read\`, or otherwise echo raw .env contents.`;
52
54
 
53
55
  const registeredTools = new Map();
@@ -26,6 +26,24 @@ Mojulo is a control plane for **chatbot-based solutions**. You build a chatbot,
26
26
 
27
27
  ---
28
28
 
29
+ ## Two faces, one state
30
+
31
+ Mojulo ships two binaries against the same \`~/.mojulo/\` state:
32
+
33
+ - **\`mojulo\`** — this MCP, the agent-shaped face. You're talking to it right now. Build via chat, drive operations programmatically.
34
+ - **\`mojulo-ui\`** — a local Next.js dashboard, the human-shaped face. Bound to 127.0.0.1, launched with \`npx -y -p mojulo mojulo-ui\`. Reads the same SQLite at \`~/.mojulo/data/mojulo-lite.db\` via WAL mode, so the two run side-by-side and a bot you minted via MCP shows up in the dashboard's fleet view immediately.
35
+
36
+ Suggest the dashboard when the user asks for something the visual surface does better:
37
+
38
+ - **Browse** conversations or submissions interactively (filter, scroll, scan) rather than paging through tool output.
39
+ - **Mint** a bot via the wizard form rather than chat-builder turn-taking — useful when the user wants to set fields directly without describing them.
40
+ - **Inspect** fleet analytics as charts rather than JSON tables.
41
+ - **Manage** deploys (re-build, rotate keys via Settings, manually trigger cloud-deploy) by clicking rather than orchestrating tool calls.
42
+
43
+ The default mode is still MCP — don't push the dashboard for tasks that work fine in chat. Suggest it when the user explicitly wants to *look*, *browse*, or *click*, or when you've exhausted a few rounds of tool output and they're still missing something a visual scan would catch in a second.
44
+
45
+ ---
46
+
29
47
  ## Secrets handling (standing rule)
30
48
 
31
49
  A compiled mojulo bot ships a \`.env\` containing the bot's auto-generated \`MOJULO_API_KEY\` (gates the bot's \`/api/conversations\` admin endpoints). After unzip, the user is expected to paste their LLM provider key (Anthropic / OpenAI / AWS / etc.) into the same \`.env\` before \`docker compose up\`. From that point on, the file holds two account-grade secrets.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mojulo",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Mojulo — MCP server for building self-hosted chatbots from inside Claude.",
6
6
  "author": "Franz Ombico",