@lowdefy/server-dev 5.6.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/{pages/_app.js → client/App.jsx} +21 -33
  2. package/client/DevStreamContext.js +30 -0
  3. package/client/Inspector.jsx +226 -0
  4. package/{lib/client/Page.js → client/Page.jsx} +4 -8
  5. package/client/Reload.jsx +85 -0
  6. package/client/Routing.jsx +117 -0
  7. package/client/clientBuildImports.test.mjs +61 -0
  8. package/client/feedback/FeedbackMount.jsx +83 -0
  9. package/client/feedback/FeedbackOverlay.jsx +901 -0
  10. package/client/feedback/captureTabScreenshot.js +84 -0
  11. package/client/feedback/copyToClipboard.js +45 -0
  12. package/client/feedback/drawAnnotationsSvg.js +119 -0
  13. package/client/feedback/elementInspect.js +99 -0
  14. package/client/feedback/feedbackStyles.js +301 -0
  15. package/client/feedback/pinResponsiveImages.js +59 -0
  16. package/client/feedback/sendFeedback.js +39 -0
  17. package/client/feedback/useFeedbackToggle.js +70 -0
  18. package/client/main.jsx +42 -0
  19. package/client/openInEditor/OpenInEditorListener.jsx +268 -0
  20. package/client/openInEditor/findBlockLocation.js +51 -0
  21. package/lib/build/app.js +8 -1
  22. package/lib/build/appMeta.js +8 -1
  23. package/lib/build/auth.js +8 -1
  24. package/lib/build/config.js +8 -1
  25. package/lib/build/i18n.js +8 -1
  26. package/lib/build/logger.js +8 -1
  27. package/lib/build/theme.js +8 -1
  28. package/lib/client/auth/{Auth.js → Auth.jsx} +11 -2
  29. package/lib/client/auth/{AuthConfigured.js → AuthConfigured.jsx} +22 -10
  30. package/lib/client/setPageId.js +6 -5
  31. package/lib/client/utils/useMutateCache.js +14 -1
  32. package/lib/client/utils/usePageConfig.js +45 -43
  33. package/lib/client/utils/usePageConfig.test.mjs +62 -0
  34. package/lib/docs/captureAnnotatedScreenshot.js +88 -0
  35. package/lib/docs/checkpointPaths.js +91 -0
  36. package/lib/docs/checkpointStore.js +240 -0
  37. package/lib/docs/checkpointToMocks.js +65 -0
  38. package/lib/docs/clientErrorStore.js +36 -0
  39. package/lib/docs/configCheckpoints.test.mjs +156 -0
  40. package/lib/docs/createConfigCheckpoint.js +92 -0
  41. package/lib/docs/createDocsMcpServer.js +531 -0
  42. package/lib/docs/createDocsMcpServer.test.mjs +141 -0
  43. package/lib/docs/devMockRegistry.js +68 -0
  44. package/lib/docs/docs.test.mjs +448 -0
  45. package/lib/docs/enrichFeedback.js +71 -0
  46. package/lib/docs/evalOperator.js +47 -0
  47. package/lib/docs/evalOperator.test.mjs +157 -0
  48. package/lib/docs/evalOperatorHeadless.js +87 -0
  49. package/lib/docs/evalOperatorHeadless.test.mjs +72 -0
  50. package/lib/docs/evalOperatorInTab.js +48 -0
  51. package/lib/docs/findConfig.js +260 -0
  52. package/lib/docs/formatFeedback.js +121 -0
  53. package/lib/docs/formatFeedback.test.mjs +204 -0
  54. package/lib/docs/getAppMap.js +180 -0
  55. package/lib/docs/getBrowser.js +127 -0
  56. package/lib/docs/getBrowser.test.mjs +122 -0
  57. package/lib/docs/getBuildStatus.js +37 -0
  58. package/lib/docs/getCoreDoc.js +63 -0
  59. package/lib/docs/getDocsManifest.js +40 -0
  60. package/lib/docs/getExamples.js +58 -0
  61. package/lib/docs/getOverview.js +115 -0
  62. package/lib/docs/getPageConfig.js +53 -0
  63. package/lib/docs/getPluginDoc.js +55 -0
  64. package/lib/docs/getSchema.js +65 -0
  65. package/lib/docs/inspectState.js +47 -0
  66. package/lib/docs/inspectState.test.mjs +140 -0
  67. package/lib/docs/inspectStateFromTab.js +40 -0
  68. package/lib/docs/inspectStateHeadless.js +84 -0
  69. package/lib/docs/inspectStateHeadless.test.mjs +60 -0
  70. package/lib/docs/isWriteRequestsAllowed.js +50 -0
  71. package/lib/docs/listConfigCheckpoints.js +55 -0
  72. package/lib/docs/listPlugins.js +80 -0
  73. package/lib/docs/listTypes.js +105 -0
  74. package/lib/docs/loadState.js +152 -0
  75. package/lib/docs/mapPageBuildErrors.js +28 -0
  76. package/lib/docs/normalizeTypeKind.js +40 -0
  77. package/{pages/api/endpoints/[...endpointId].js → lib/docs/readBuildArtifact.js} +15 -12
  78. package/lib/docs/readPageArtifact.js +26 -0
  79. package/lib/docs/resolvePluginDir.js +49 -0
  80. package/lib/docs/resolveSource.js +48 -0
  81. package/lib/docs/resolveSource.test.mjs +100 -0
  82. package/lib/docs/revertConfigCheckpoint.js +73 -0
  83. package/lib/docs/runRequest.js +141 -0
  84. package/lib/docs/saveAnnotatedScreenshot.js +43 -0
  85. package/lib/docs/scaffoldPage.js +75 -0
  86. package/lib/docs/scaffoldPage.test.mjs +97 -0
  87. package/lib/docs/screenshotPage.js +110 -0
  88. package/lib/docs/screenshotPage.test.mjs +59 -0
  89. package/lib/docs/screenshotPageClip.test.mjs +102 -0
  90. package/lib/docs/searchDocs.js +67 -0
  91. package/lib/docs/setupTestFixtures.mjs +335 -0
  92. package/lib/docs/snapshotState.js +59 -0
  93. package/lib/docs/stateCheckpoints.test.mjs +366 -0
  94. package/lib/docs/tabAvailable.js +28 -0
  95. package/lib/docs/tabChannel.js +119 -0
  96. package/lib/docs/tabChannel.test.mjs +130 -0
  97. package/lib/server/auth/{getAuthOptions.js → getAuthConfig.js} +9 -4
  98. package/lib/server/auth/getDevSession.js +70 -0
  99. package/lib/server/auth/getDevSession.test.mjs +138 -0
  100. package/lib/server/auth/getHeadlessUser.js +40 -0
  101. package/lib/server/auth/{getMockSession.js → getMockUser.js} +8 -21
  102. package/lib/server/auth/headlessUser.js +29 -0
  103. package/lib/server/auth/resolveHeadlessUser.js +38 -0
  104. package/lib/server/auth/resolveHeadlessUser.test.mjs +78 -0
  105. package/lib/server/auth/session.js +39 -0
  106. package/lib/server/auth/strategies.js +47 -0
  107. package/lib/server/createLowdefyContext.js +128 -0
  108. package/lib/server/createLowdefyContext.test.mjs +117 -0
  109. package/lib/server/getPageJitEnrichment.test.mjs +105 -0
  110. package/lib/server/jitPageBuilder.js +125 -12
  111. package/lib/server/log/createHandleError.test.mjs +126 -0
  112. package/lib/server/log/createLogger.test.mjs +62 -0
  113. package/lib/server/scrubSecrets.js +23 -0
  114. package/manager/getContext.mjs +3 -7
  115. package/manager/processes/initialBuild.mjs +0 -2
  116. package/manager/processes/installPlugins.mjs +19 -11
  117. package/manager/processes/lowdefyBuild.mjs +29 -7
  118. package/manager/processes/lowdefyBuild.test.mjs +102 -0
  119. package/manager/processes/readDotEnv.mjs +13 -1
  120. package/manager/processes/shutdownServer.mjs +6 -6
  121. package/manager/processes/startProxy.mjs +160 -0
  122. package/manager/processes/startProxy.test.mjs +102 -0
  123. package/manager/processes/startServer.mjs +33 -16
  124. package/manager/processes/startWatchers.mjs +2 -2
  125. package/manager/processes/warnAuthUrlPortMismatch.mjs +47 -0
  126. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +93 -0
  127. package/manager/run.mjs +50 -0
  128. package/manager/utils/createCustomPluginMessagesMap.mjs +8 -1
  129. package/manager/utils/createCustomPluginTypesMap.mjs +1 -0
  130. package/manager/utils/formatNoticeBox.mjs +30 -0
  131. package/manager/utils/{getNextBin.mjs → getViteBin.mjs} +7 -10
  132. package/manager/utils/updatePageTailwindCss.mjs +7 -1
  133. package/manager/utils/writeBuildStatus.mjs +39 -0
  134. package/manager/utils/writeBuildStatus.test.mjs +76 -0
  135. package/manager/watchers/lowdefyBuildWatcher.mjs +0 -1
  136. package/manager/watchers/moduleBuildWatcher.mjs +0 -1
  137. package/manager/watchers/{nextBuildWatcher.mjs → serverArtifactWatcher.mjs} +18 -20
  138. package/package.json +62 -49
  139. package/package.original.json +65 -52
  140. package/postcss.config.cjs +1 -0
  141. package/src/app.js +175 -0
  142. package/src/html/renderDevPage.js +86 -0
  143. package/src/lib/getPathSegments.js +25 -0
  144. package/src/lib/safeScriptJson.js +38 -0
  145. package/src/middleware/apiContext.js +32 -0
  146. package/src/middleware/errorHandler.js +48 -0
  147. package/src/middleware/errorHandler.test.mjs +247 -0
  148. package/src/routes/agent.js +64 -0
  149. package/src/routes/apiPage.js +37 -0
  150. package/src/routes/auth.js +49 -0
  151. package/src/routes/auth.test.mjs +84 -0
  152. package/src/routes/clientError.js +57 -0
  153. package/src/routes/cron.js +47 -0
  154. package/src/routes/cronForward.js +47 -0
  155. package/src/routes/detached.js +47 -0
  156. package/src/routes/devInspect.js +111 -0
  157. package/src/routes/devInspect.test.mjs +119 -0
  158. package/{pages/[[...pageId]].js → src/routes/devTools.js} +4 -2
  159. package/{pages/404.js → src/routes/docs/appMap.js} +6 -2
  160. package/{pages/api/dev-tools.js → src/routes/docs/buildStatus.js} +5 -4
  161. package/src/routes/docs/checkpointsCreate.js +28 -0
  162. package/src/routes/docs/checkpointsList.js +23 -0
  163. package/{pages/api/icons/dynamic.js → src/routes/docs/checkpointsRevert.js} +11 -12
  164. package/src/routes/docs/content.js +31 -0
  165. package/src/routes/docs/evalOperator.js +56 -0
  166. package/src/routes/docs/examples.js +33 -0
  167. package/src/routes/docs/find.js +30 -0
  168. package/src/routes/docs/index.js +23 -0
  169. package/src/routes/docs/inspectState.js +44 -0
  170. package/src/routes/docs/loadState.js +49 -0
  171. package/src/routes/docs/mcp.js +30 -0
  172. package/src/routes/docs/pageConfig.js +36 -0
  173. package/src/routes/docs/parseUserParam.js +52 -0
  174. package/src/routes/docs/parseUserParam.test.mjs +45 -0
  175. package/src/routes/docs/pluginDoc.js +33 -0
  176. package/src/routes/docs/plugins.js +23 -0
  177. package/src/routes/docs/runRequest.js +36 -0
  178. package/{lib/server/auth/getServerSession.js → src/routes/docs/schema.js} +17 -15
  179. package/src/routes/docs/screenshot.js +72 -0
  180. package/src/routes/docs/search.js +27 -0
  181. package/src/routes/docs/snapshotState.js +36 -0
  182. package/src/routes/docs/stateCheckpointsList.js +26 -0
  183. package/src/routes/docs/types.js +33 -0
  184. package/src/routes/endpoints.js +71 -0
  185. package/src/routes/feedback.js +110 -0
  186. package/src/routes/feedback.test.mjs +204 -0
  187. package/src/routes/jitPage.js +86 -0
  188. package/src/routes/mcp.js +34 -0
  189. package/{pages/api → src/routes}/ping.js +4 -2
  190. package/src/routes/reload.js +79 -0
  191. package/src/routes/request.js +74 -0
  192. package/src/routes/request.test.mjs +124 -0
  193. package/{pages/api → src/routes}/root.js +4 -4
  194. package/src/routes/usage.js +46 -0
  195. package/src/routes/websocket.js +33 -0
  196. package/src/websocket/devWebSocket.js +78 -0
  197. package/vite.config.js +111 -0
  198. package/.eslintrc.yaml +0 -1
  199. package/lib/client/App.js +0 -80
  200. package/lib/client/Reload.js +0 -60
  201. package/lib/server/apiWrapper.js +0 -109
  202. package/lib/server/compileCss.js +0 -39
  203. package/manager/processes/compileCss.mjs +0 -54
  204. package/manager/processes/nextBuild.mjs +0 -50
  205. package/next.config.js +0 -25
  206. package/pages/_document.js +0 -109
  207. package/pages/api/agent/[...path].js +0 -86
  208. package/pages/api/auth/[...nextauth].js +0 -48
  209. package/pages/api/client-error.js +0 -45
  210. package/pages/api/js/[env].js +0 -42
  211. package/pages/api/page/[...pageId].js +0 -74
  212. package/pages/api/reload.js +0 -53
  213. package/pages/api/request/[...path].js +0 -41
  214. /package/lib/client/{BuildErrorPage.js → BuildErrorPage.jsx} +0 -0
  215. /package/lib/client/{BuildingPage.js → BuildingPage.jsx} +0 -0
  216. /package/lib/client/{ErrorBar.js → ErrorBar.jsx} +0 -0
  217. /package/lib/client/{InstallingPluginsPage.js → InstallingPluginsPage.jsx} +0 -0
  218. /package/lib/client/{RestartingPage.js → RestartingPage.jsx} +0 -0
@@ -0,0 +1,111 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { serializer } from '@lowdefy/helpers';
18
+
19
+ import { readCheckpoint } from '../../lib/docs/checkpointStore.js';
20
+ import { loadMocks } from '../../lib/docs/devMockRegistry.js';
21
+ import getPathSegments from '../lib/getPathSegments.js';
22
+ import { listTabs, resolveTabRequest, updateTabPage } from '../../lib/docs/tabChannel.js';
23
+
24
+ // Origin/host check mirrors clientError.js — this endpoint accepts a POST
25
+ // from Inspector.jsx (a same-origin dev-only browser component), not from
26
+ // arbitrary agents, so it needs the same CSRF-style guard.
27
+ function checkOrigin(c) {
28
+ const origin = c.req.header('origin');
29
+ if (!origin) {
30
+ return false;
31
+ }
32
+ try {
33
+ return new URL(origin).host === c.req.header('host');
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ // Dev-only agent-state-xray: a human tab bootstrapping from a
40
+ // `?_checkpoint=<name>` URL (client/Inspector.jsx) fetches this. It returns
41
+ // the checkpoint's state part + manifest for the tab to inject client-side,
42
+ // and — as a side effect — loads the checkpoint's recorded requests into
43
+ // devMockRegistry server-side, so that tab's own requests (src/routes/
44
+ // request.js) replay recorded data instead of hitting real connections.
45
+ function handleCheckpointBootstrap(c, { name }) {
46
+ let checkpoint;
47
+ try {
48
+ checkpoint = readCheckpoint({ name });
49
+ } catch (error) {
50
+ return c.json({ error: error.message }, 404);
51
+ }
52
+ loadMocks({ pageId: checkpoint.checkpoint.pageId, mocks: checkpoint.requests });
53
+ return c.json(
54
+ serializer.serialize({
55
+ checkpoint: checkpoint.checkpoint,
56
+ state: checkpoint.state,
57
+ urlQuery: checkpoint.urlQuery,
58
+ input: checkpoint.input,
59
+ user: checkpoint.user,
60
+ global: checkpoint.global,
61
+ })
62
+ );
63
+ }
64
+
65
+ // Inspector.jsx posts { tabId, pageId } here whenever its tab navigates, so
66
+ // the tab registry (lib/docs/tabChannel.js) knows which page each connected
67
+ // tab is on without the tab reconnecting its event stream.
68
+ async function handleTabPage(c) {
69
+ const { tabId, pageId } = await c.req.json();
70
+ if (!tabId) {
71
+ return c.json({ error: 'Missing "tabId".' }, 400);
72
+ }
73
+ updateTabPage({ id: tabId, pageId });
74
+ return c.json({ ok: true });
75
+ }
76
+
77
+ // GET lists connected tabs for diagnostics (or, under /checkpoint/<name>,
78
+ // bootstraps a human tab from a state checkpoint); POST /page updates the
79
+ // page a connected tab is on; POST at the root is the answer leg of the SSE
80
+ // request/response round trip (Inspector.jsx posts back {requestId, result}
81
+ // after handling an inspect-request/eval-request event).
82
+ async function devInspectHandler(c) {
83
+ if (c.req.method === 'GET') {
84
+ const segments = getPathSegments(c, '/api/dev-inspect/');
85
+ if (segments[0] === 'checkpoint' && segments[1]) {
86
+ return handleCheckpointBootstrap(c, { name: segments[1] });
87
+ }
88
+ return c.json({ tabs: listTabs() });
89
+ }
90
+
91
+ if (c.req.method !== 'POST') {
92
+ throw new Error('Only GET and POST requests are supported.');
93
+ }
94
+
95
+ if (!checkOrigin(c)) {
96
+ return c.json({ error: 'Forbidden' }, 403);
97
+ }
98
+
99
+ if (getPathSegments(c, '/api/dev-inspect/')[0] === 'page') {
100
+ return handleTabPage(c);
101
+ }
102
+
103
+ const { requestId, result } = await c.req.json();
104
+ if (!requestId) {
105
+ return c.json({ error: 'Missing "requestId".' }, 400);
106
+ }
107
+ const resolved = resolveTabRequest({ requestId, result });
108
+ return c.json({ ok: resolved });
109
+ }
110
+
111
+ export default devInspectHandler;
@@ -0,0 +1,119 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { Hono } from 'hono';
18
+ import { jest } from '@jest/globals';
19
+
20
+ const { listTabs, registerTab, requestFromTab, unregisterTab } = await import(
21
+ '../../lib/docs/tabChannel.js'
22
+ );
23
+ const { default: devInspectHandler } = await import('./devInspect.js');
24
+
25
+ function createApp() {
26
+ const app = new Hono();
27
+ app.all('/api/dev-inspect', devInspectHandler);
28
+ app.all('/api/dev-inspect/*', devInspectHandler);
29
+ return app;
30
+ }
31
+
32
+ const sameOriginHeaders = {
33
+ host: 'localhost:3001',
34
+ origin: 'http://localhost:3001',
35
+ 'content-type': 'application/json',
36
+ };
37
+
38
+ // tabChannel keeps its registries at module scope, so clear connected tabs
39
+ // between tests to keep them independent.
40
+ afterEach(() => {
41
+ listTabs().forEach((tab) => unregisterTab({ id: tab.id }));
42
+ });
43
+
44
+ test('GET /api/dev-inspect lists connected tabs', async () => {
45
+ registerTab({ id: 'tab-1', pageId: 'home', send: jest.fn() });
46
+ const res = await createApp().request('/api/dev-inspect');
47
+ expect(res.status).toBe(200);
48
+ const body = await res.json();
49
+ expect(body.tabs).toHaveLength(1);
50
+ expect(body.tabs[0]).toMatchObject({ id: 'tab-1', pageId: 'home' });
51
+ });
52
+
53
+ test('POST /api/dev-inspect/page returns 403 when the Origin header is missing', async () => {
54
+ registerTab({ id: 'tab-1', pageId: 'home', send: jest.fn() });
55
+ const res = await createApp().request('/api/dev-inspect/page', {
56
+ method: 'POST',
57
+ headers: { host: 'localhost:3001', 'content-type': 'application/json' },
58
+ body: JSON.stringify({ tabId: 'tab-1', pageId: 'about' }),
59
+ });
60
+ expect(res.status).toBe(403);
61
+ expect(listTabs()[0].pageId).toBe('home');
62
+ });
63
+
64
+ test('POST /api/dev-inspect/page moves a connected tab to the posted page', async () => {
65
+ registerTab({ id: 'tab-1', pageId: 'home', send: jest.fn() });
66
+ const res = await createApp().request('/api/dev-inspect/page', {
67
+ method: 'POST',
68
+ headers: sameOriginHeaders,
69
+ body: JSON.stringify({ tabId: 'tab-1', pageId: 'about' }),
70
+ });
71
+ expect(res.status).toBe(200);
72
+ expect(await res.json()).toEqual({ ok: true });
73
+ expect(listTabs()[0].pageId).toBe('about');
74
+ });
75
+
76
+ test('POST /api/dev-inspect/page returns 400 when tabId is missing', async () => {
77
+ const res = await createApp().request('/api/dev-inspect/page', {
78
+ method: 'POST',
79
+ headers: sameOriginHeaders,
80
+ body: JSON.stringify({ pageId: 'about' }),
81
+ });
82
+ expect(res.status).toBe(400);
83
+ expect(await res.json()).toEqual({ error: 'Missing "tabId".' });
84
+ });
85
+
86
+ test('POST /api/dev-inspect/page for a tab that already disconnected returns ok', async () => {
87
+ const res = await createApp().request('/api/dev-inspect/page', {
88
+ method: 'POST',
89
+ headers: sameOriginHeaders,
90
+ body: JSON.stringify({ tabId: 'gone', pageId: 'about' }),
91
+ });
92
+ expect(res.status).toBe(200);
93
+ expect(listTabs()).toHaveLength(0);
94
+ });
95
+
96
+ test('POST /api/dev-inspect resolves a pending tab request with the posted result', async () => {
97
+ const send = jest.fn();
98
+ registerTab({ id: 'tab-1', pageId: 'home', send });
99
+ const pending = requestFromTab({ pageId: 'home', event: 'inspect-request' });
100
+ const [, { requestId }] = send.mock.calls[0];
101
+
102
+ const res = await createApp().request('/api/dev-inspect', {
103
+ method: 'POST',
104
+ headers: sameOriginHeaders,
105
+ body: JSON.stringify({ requestId, result: { state: { a: 1 } } }),
106
+ });
107
+ expect(res.status).toBe(200);
108
+ expect(await res.json()).toEqual({ ok: true });
109
+ await expect(pending).resolves.toEqual({ state: { a: 1 } });
110
+ });
111
+
112
+ test('POST /api/dev-inspect returns 400 when requestId is missing', async () => {
113
+ const res = await createApp().request('/api/dev-inspect', {
114
+ method: 'POST',
115
+ headers: sameOriginHeaders,
116
+ body: JSON.stringify({ result: {} }),
117
+ });
118
+ expect(res.status).toBe(400);
119
+ });
@@ -14,6 +14,8 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import App from '../lib/client/App.js';
17
+ function devToolsHandler(c) {
18
+ return c.json({ directories: { config: process.env.LOWDEFY_DIRECTORY_CONFIG } });
19
+ }
18
20
 
19
- export default App;
21
+ export default devToolsHandler;
@@ -14,6 +14,10 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import App from '../lib/client/App.js';
17
+ import getAppMap from '../../../lib/docs/getAppMap.js';
18
18
 
19
- export default App;
19
+ function docsAppMapHandler(c) {
20
+ return c.json(getAppMap());
21
+ }
22
+
23
+ export default docsAppMapHandler;
@@ -13,10 +13,11 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
- import apiWrapper from '../../lib/server/apiWrapper.js';
17
16
 
18
- async function handler({ req, res }) {
19
- res.status(200).json({ directories: { config: process.env.LOWDEFY_DIRECTORY_CONFIG } });
17
+ import getBuildStatus from '../../../lib/docs/getBuildStatus.js';
18
+
19
+ function docsBuildStatusHandler(c) {
20
+ return c.json(getBuildStatus());
20
21
  }
21
22
 
22
- export default apiWrapper(handler);
23
+ export default docsBuildStatusHandler;
@@ -0,0 +1,28 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import createConfigCheckpoint from '../../../lib/docs/createConfigCheckpoint.js';
18
+
19
+ // Lets an agent snapshot the config directory before an experimental change,
20
+ // so it can revert cleanly with POST /lowdefy-docs/checkpoints/revert if the
21
+ // change doesn't work out.
22
+ async function docsCheckpointsCreateHandler(c) {
23
+ const body = await c.req.json().catch(() => ({}));
24
+ const result = createConfigCheckpoint({ label: body.label });
25
+ return c.json(result);
26
+ }
27
+
28
+ export default docsCheckpointsCreateHandler;
@@ -0,0 +1,23 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import listConfigCheckpoints from '../../../lib/docs/listConfigCheckpoints.js';
18
+
19
+ function docsCheckpointsListHandler(c) {
20
+ return c.json(listConfigCheckpoints());
21
+ }
22
+
23
+ export default docsCheckpointsListHandler;
@@ -14,21 +14,20 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import fs from 'fs';
18
- import path from 'path';
17
+ import revertConfigCheckpoint from '../../../lib/docs/revertConfigCheckpoint.js';
19
18
 
20
- async function handler(req, res) {
21
- const filePath = path.join(process.cwd(), 'build', 'plugins', 'iconsDynamic.js');
19
+ async function docsCheckpointsRevertHandler(c) {
20
+ const body = await c.req.json().catch(() => ({}));
21
+ if (!body.id) {
22
+ return c.json({ error: 'revert requires an "id" of the checkpoint to restore.' }, 400);
23
+ }
22
24
 
23
25
  try {
24
- const content = fs.readFileSync(filePath, 'utf8');
25
- res.setHeader('Content-Type', 'application/javascript');
26
- res.status(200).send(content);
27
- } catch {
28
- // Return empty default export if file doesn't exist yet
29
- res.setHeader('Content-Type', 'application/javascript');
30
- res.status(200).send('export default {};');
26
+ const result = revertConfigCheckpoint({ id: body.id });
27
+ return c.json(result);
28
+ } catch (error) {
29
+ return c.json({ error: error.message }, 400);
31
30
  }
32
31
  }
33
32
 
34
- export default handler;
33
+ export default docsCheckpointsRevertHandler;
@@ -0,0 +1,31 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import getCoreDoc from '../../../lib/docs/getCoreDoc.js';
18
+
19
+ function docsContentHandler(c) {
20
+ const slug = c.req.param('slug');
21
+ const doc = getCoreDoc({ slug });
22
+ if (doc === null) {
23
+ return c.json(
24
+ { error: `No doc found for slug "${slug}". Use GET /lowdefy-docs/search?q=... to find the right slug.` },
25
+ 404
26
+ );
27
+ }
28
+ return c.text(doc.markdown, 200, { 'Content-Type': 'text/markdown; charset=utf-8' });
29
+ }
30
+
31
+ export default docsContentHandler;
@@ -0,0 +1,56 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import evalOperator from '../../../lib/docs/evalOperator.js';
18
+ import parseUserParam from './parseUserParam.js';
19
+
20
+ async function docsEvalOperatorHandler(c) {
21
+ const body = await c.req.json().catch(() => ({}));
22
+ const { pageId, source } = body;
23
+ // "operator" is accepted as an alias for "expression" — agents commonly
24
+ // guess it as the body key for this endpoint.
25
+ const expression = body.expression ?? body.operator;
26
+ if (expression === undefined) {
27
+ return c.json(
28
+ {
29
+ error:
30
+ 'POST /lowdefy-docs/eval-operator requires an "expression" in the JSON body — the operator expression to evaluate, e.g. {"pageId": "home", "expression": {"_state": "key"}}.',
31
+ },
32
+ 400
33
+ );
34
+ }
35
+ // Derived from the incoming request rather than a config value — this is
36
+ // the origin an agent can actually reach the dev server on (host/port it
37
+ // just connected to), regardless of how the server is bound.
38
+ const origin = new URL(c.req.url).origin;
39
+
40
+ const { user, error: userError } = parseUserParam({ value: body.user });
41
+ if (userError) {
42
+ return c.json({ error: userError }, 400);
43
+ }
44
+
45
+ const result = await evalOperator({ origin, pageId, expression, source, user });
46
+ if (result.error) {
47
+ // A contradictory call (`user` with `source: 'tab'`) is the caller's
48
+ // mistake, not a failed render — 502 would read as "the renderer broke" and
49
+ // invite a pointless retry of the same request.
50
+ const status = result.invalidInput ? 400 : 502;
51
+ return c.json({ error: result.error, source: result.source }, status);
52
+ }
53
+ return c.json(result);
54
+ }
55
+
56
+ export default docsEvalOperatorHandler;
@@ -0,0 +1,33 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import getExamples from '../../../lib/docs/getExamples.js';
18
+
19
+ function docsExamplesHandler(c) {
20
+ const type = c.req.param('type');
21
+ const examples = getExamples({ type });
22
+ if (examples === null) {
23
+ return c.json(
24
+ {
25
+ error: `No examples shipped for block type "${type}". Use GET /lowdefy-docs/schema/blocks/${type} for its schema.`,
26
+ },
27
+ 404
28
+ );
29
+ }
30
+ return c.json(examples);
31
+ }
32
+
33
+ export default docsExamplesHandler;
@@ -0,0 +1,30 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import findConfig from '../../../lib/docs/findConfig.js';
18
+
19
+ async function docsFindHandler(c) {
20
+ const id = c.req.param('id');
21
+ const pageId = c.req.query('pageId');
22
+ try {
23
+ const result = await findConfig({ id, pageId });
24
+ return c.json(result);
25
+ } catch (error) {
26
+ return c.json({ error: error.message }, 400);
27
+ }
28
+ }
29
+
30
+ export default docsFindHandler;
@@ -0,0 +1,23 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import getOverview from '../../../lib/docs/getOverview.js';
18
+
19
+ function docsIndexHandler(c) {
20
+ return c.text(getOverview(), 200, { 'Content-Type': 'text/markdown; charset=utf-8' });
21
+ }
22
+
23
+ export default docsIndexHandler;
@@ -0,0 +1,44 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import inspectState from '../../../lib/docs/inspectState.js';
18
+ import parseUserParam from './parseUserParam.js';
19
+
20
+ async function docsInspectStateHandler(c) {
21
+ const pageId = c.req.param('pageId');
22
+ const source = c.req.query('source');
23
+ // Derived from the incoming request rather than a config value — this is
24
+ // the origin an agent can actually reach the dev server on (host/port it
25
+ // just connected to), regardless of how the server is bound.
26
+ const origin = new URL(c.req.url).origin;
27
+
28
+ const { user, error: userError } = parseUserParam({ value: c.req.query('user') });
29
+ if (userError) {
30
+ return c.json({ error: userError }, 400);
31
+ }
32
+
33
+ const result = await inspectState({ origin, pageId, source, user });
34
+ if (result.error) {
35
+ // A contradictory call (`user` with `source=tab`) is the caller's mistake,
36
+ // not a failed render — 502 would read as "the renderer broke" and invite a
37
+ // pointless retry of the same request.
38
+ const status = result.invalidInput ? 400 : 502;
39
+ return c.json({ error: result.error, source: result.source }, status);
40
+ }
41
+ return c.json(result);
42
+ }
43
+
44
+ export default docsInspectStateHandler;
@@ -0,0 +1,49 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import loadState from '../../../lib/docs/loadState.js';
18
+ import parseUserParam from './parseUserParam.js';
19
+
20
+ // Loads a state checkpoint back into the running app. mode: 'headless'
21
+ // (default) drives a headless page and injects state directly for an agent
22
+ // to verify; mode: 'registry-only' just loads the recorded requests into
23
+ // devMockRegistry and returns a URL for a human to open in a real browser
24
+ // tab (client/Inspector.jsx does the state injection there).
25
+ async function docsLoadStateHandler(c) {
26
+ const body = await c.req.json();
27
+ const { name, mode } = body;
28
+ // Derived from the incoming request rather than a config value — this is
29
+ // the origin an agent can actually reach the dev server on (host/port it
30
+ // just connected to), regardless of how the server is bound.
31
+ const origin = new URL(c.req.url).origin;
32
+
33
+ const { user, error: userError } = parseUserParam({ value: body.user });
34
+ if (userError) {
35
+ return c.json({ error: userError }, 400);
36
+ }
37
+
38
+ const result = await loadState({ origin, name, mode, user });
39
+ if (result.error) {
40
+ // A contradictory call (`user` in 'registry-only' mode) is the caller's
41
+ // mistake, not a failed render — 502 would read as "the renderer broke" and
42
+ // invite a pointless retry of the same request.
43
+ const status = result.invalidInput ? 400 : 502;
44
+ return c.json({ error: result.error }, status);
45
+ }
46
+ return c.json(result);
47
+ }
48
+
49
+ export default docsLoadStateHandler;
@@ -0,0 +1,30 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { StreamableHTTPTransport } from '@hono/mcp';
18
+
19
+ import createDocsMcpServer from '../../../lib/docs/createDocsMcpServer.js';
20
+
21
+ // Stateless per-request server: docs tools read build artifacts fresh on
22
+ // every call, so there is no session state worth keeping between requests.
23
+ async function mcpHandler(c) {
24
+ const server = createDocsMcpServer({ origin: new URL(c.req.url).origin, honoContext: c });
25
+ const transport = new StreamableHTTPTransport();
26
+ await server.connect(transport);
27
+ return transport.handleRequest(c);
28
+ }
29
+
30
+ export default mcpHandler;