@lowdefy/server-dev 6.1.0 → 7.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 (624) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +2 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +222 -36
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +78 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +727 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/describeTargetKey.js +29 -0
  104. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  105. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  106. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  107. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  108. package/lib/docs/dataSets/getDataStore.js +106 -0
  109. package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
  110. package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
  111. package/lib/docs/dataSets/groupDataSetByCollection.js +52 -0
  112. package/lib/docs/dataSets/insertFixture.js +57 -0
  113. package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
  114. package/lib/docs/dataSets/isDataStoreGone.js +29 -0
  115. package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
  116. package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
  117. package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
  118. package/lib/docs/dataSets/openDataSession.js +133 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +493 -0
  120. package/lib/docs/dataSets/pickDataStorePort.js +58 -0
  121. package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
  122. package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
  123. package/lib/docs/dataSets/readDataSession.js +41 -0
  124. package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
  125. package/lib/docs/dataSets/readDataSet.js +83 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +251 -0
  127. package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
  128. package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
  129. package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
  130. package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
  131. package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
  132. package/lib/docs/dataSets/sessionDatabaseName.js +32 -0
  133. package/lib/docs/dataSets/sweepOrphanDatabases.js +36 -0
  134. package/lib/docs/dataSets/test/app/lowdefy.yaml +225 -0
  135. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  136. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +12 -0
  137. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  138. package/lib/docs/devEventBus.js +136 -0
  139. package/lib/docs/devEventBus.test.mjs +269 -0
  140. package/lib/docs/devToolDefinitions.js +529 -0
  141. package/lib/docs/docSources.js +21 -0
  142. package/lib/docs/docs.test.mjs +171 -15
  143. package/lib/docs/docsIndex.test.mjs +377 -0
  144. package/lib/docs/evalOperator.js +5 -3
  145. package/lib/docs/evalOperatorHeadless.js +84 -12
  146. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  147. package/lib/docs/evalOperatorInTab.js +2 -1
  148. package/lib/docs/findConfig.js +95 -49
  149. package/lib/docs/fitResponse.js +81 -0
  150. package/lib/docs/fitResponse.test.mjs +128 -0
  151. package/lib/docs/formatFeedback.js +26 -5
  152. package/lib/docs/formatFeedback.test.mjs +48 -0
  153. package/lib/docs/getAppMap.js +7 -1
  154. package/lib/docs/getBrowser.js +150 -80
  155. package/lib/docs/getBrowser.test.mjs +407 -16
  156. package/lib/docs/getBuildId.js +61 -0
  157. package/lib/docs/getBuildId.test.mjs +84 -0
  158. package/lib/docs/getBuildStatus.js +81 -7
  159. package/lib/docs/getBuildStatus.test.mjs +229 -0
  160. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  161. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  162. package/lib/docs/getCoreDoc.js +9 -1
  163. package/lib/docs/getDoc.js +100 -0
  164. package/lib/docs/getDocsIndex.js +152 -0
  165. package/lib/docs/getHazards.js +115 -0
  166. package/lib/docs/getHazards.test.mjs +175 -0
  167. package/lib/docs/getMailOutboxDirectory.js +26 -0
  168. package/lib/docs/getOverview.js +79 -13
  169. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  170. package/lib/docs/getPageBuildStatus.js +46 -0
  171. package/lib/docs/getPluginDoc.js +21 -23
  172. package/lib/docs/getSchema.js +33 -1
  173. package/lib/docs/getStaleStatus.js +36 -0
  174. package/lib/docs/getStaleStatus.test.mjs +74 -0
  175. package/lib/docs/guardJourneyOrigin.js +39 -0
  176. package/lib/docs/inspectState.js +5 -3
  177. package/lib/docs/inspectStateFromTab.js +2 -2
  178. package/lib/docs/inspectStateHeadless.js +89 -27
  179. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  180. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  181. package/lib/docs/installHeadlessShell.js +123 -0
  182. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  183. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  184. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  185. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  186. package/lib/docs/isPageReady.js +77 -0
  187. package/lib/docs/isPageReady.test.mjs +235 -0
  188. package/lib/docs/isRunErrorEntry.js +25 -0
  189. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  190. package/lib/docs/journeyRunRecording.js +35 -0
  191. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  192. package/lib/docs/keepEventStreamAlive.js +68 -0
  193. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  194. package/lib/docs/launchBrowser.js +83 -0
  195. package/lib/docs/launchBrowser.test.mjs +175 -0
  196. package/lib/docs/listModuleDocEntries.js +69 -0
  197. package/lib/docs/listPackageDocFiles.js +67 -0
  198. package/lib/docs/listPluginDocEntries.js +111 -0
  199. package/lib/docs/listPlugins.js +9 -58
  200. package/lib/docs/listTypes.js +14 -6
  201. package/lib/docs/loadFrameworkHazards.js +40 -0
  202. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  203. package/lib/docs/loadState.js +103 -35
  204. package/lib/docs/makeModuleManifestDoc.js +174 -0
  205. package/lib/docs/matchExercisedPages.js +35 -0
  206. package/lib/docs/matchJourneyRoute.js +69 -0
  207. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  208. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  209. package/lib/docs/nextJourneySequence.js +30 -0
  210. package/lib/docs/noBrowserError.js +22 -0
  211. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  212. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  213. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  214. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  215. package/lib/docs/observe/evaluateInvariants.js +183 -0
  216. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  217. package/lib/docs/observe/findingKey.js +29 -0
  218. package/lib/docs/observe/hasNoEffect.js +31 -0
  219. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  220. package/lib/docs/observe/installStepObserver.js +72 -0
  221. package/lib/docs/observe/isAppApiUrl.js +33 -0
  222. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  223. package/lib/docs/observe/readStepObserver.js +42 -0
  224. package/lib/docs/observe/relativeSource.js +29 -0
  225. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  226. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  227. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  228. package/lib/docs/observe/usesSearchStage.js +45 -0
  229. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  230. package/lib/docs/observe/watchJourneyContext.js +75 -0
  231. package/lib/docs/observe/withAppErrors.js +31 -0
  232. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  233. package/lib/docs/openJourney.js +94 -0
  234. package/lib/docs/openJourneyEmail.js +41 -0
  235. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  236. package/lib/docs/pageBlocks.js +47 -0
  237. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  238. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  239. package/lib/docs/rails/authEngineActions.js +53 -0
  240. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  241. package/lib/docs/rails/authEngineMethods.js +52 -0
  242. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  243. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  244. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  245. package/lib/docs/rails/checkClickRails.js +56 -0
  246. package/lib/docs/rails/createDataSetRails.js +82 -0
  247. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  248. package/lib/docs/rails/findExternalBlocks.js +147 -0
  249. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  250. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  251. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  252. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  253. package/lib/docs/readConnectionPlugins.js +26 -0
  254. package/lib/docs/readDevAuthMode.js +32 -0
  255. package/lib/docs/readDocEntry.js +40 -0
  256. package/lib/docs/readDocTitle.js +26 -0
  257. package/lib/docs/readJourneyEmailMatch.js +57 -0
  258. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  259. package/lib/docs/readJourneySession.js +56 -0
  260. package/lib/docs/readMailOutbox.js +37 -0
  261. package/lib/docs/readPagePath.js +27 -0
  262. package/lib/docs/readPluginPackages.js +99 -0
  263. package/lib/docs/readProxyBuildWait.js +38 -0
  264. package/lib/docs/readRoutineSteps.js +45 -0
  265. package/lib/docs/registerShownTextEngine.js +118 -0
  266. package/lib/docs/requestRestart.js +30 -0
  267. package/lib/docs/requestRestart.test.mjs +49 -0
  268. package/lib/docs/resolvePageInstance.js +46 -0
  269. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  270. package/lib/docs/resolveSource.js +11 -7
  271. package/lib/docs/resolveSource.test.mjs +1 -1
  272. package/lib/docs/resolveToolCaller.js +62 -0
  273. package/lib/docs/reviewPage.js +51 -0
  274. package/lib/docs/reviewPageBuilds.js +56 -0
  275. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  276. package/lib/docs/runCheck.js +75 -0
  277. package/lib/docs/runCheck.test.mjs +91 -0
  278. package/lib/docs/runCheckChild.js +49 -0
  279. package/lib/docs/runEndpoint.js +185 -0
  280. package/lib/docs/runEndpoint.test.mjs +509 -0
  281. package/lib/docs/runErrorBuffers.js +99 -0
  282. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  283. package/lib/docs/runJourney.js +347 -0
  284. package/lib/docs/runJourney.test.mjs +2383 -0
  285. package/lib/docs/runJourneyAsUsers.js +44 -0
  286. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  287. package/lib/docs/runJourneySteps.js +1320 -0
  288. package/lib/docs/runRequest.js +100 -59
  289. package/lib/docs/runRequest.test.mjs +190 -0
  290. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  291. package/lib/docs/scaffoldPage.js +3 -1
  292. package/lib/docs/screenshotPage.js +163 -11
  293. package/lib/docs/screenshotPage.test.mjs +263 -1
  294. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  295. package/lib/docs/searchDocs.js +82 -30
  296. package/lib/docs/searchDocs.test.mjs +100 -0
  297. package/lib/docs/searchIcons.js +90 -0
  298. package/lib/docs/searchIcons.test.mjs +127 -0
  299. package/lib/docs/selectExercised.js +39 -0
  300. package/lib/docs/selectFinalState.js +66 -0
  301. package/lib/docs/selectFinalState.test.mjs +51 -0
  302. package/lib/docs/serverErrorStore.js +54 -0
  303. package/lib/docs/serverErrorStore.test.mjs +50 -0
  304. package/lib/docs/setupTestFixtures.mjs +133 -39
  305. package/lib/docs/shownTextSelector.js +28 -0
  306. package/lib/docs/snapshotState.js +13 -5
  307. package/lib/docs/splitSearchTerms.js +62 -0
  308. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  309. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  310. package/lib/docs/tabAvailable.js +6 -5
  311. package/lib/docs/tabChannel.js +127 -21
  312. package/lib/docs/tabChannel.test.mjs +187 -29
  313. package/lib/docs/unsettledPageNote.js +24 -0
  314. package/lib/docs/validateJourneyMail.js +36 -0
  315. package/lib/docs/validateJourneyTimeout.js +38 -0
  316. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  317. package/lib/docs/validateRecordingParam.js +42 -0
  318. package/lib/docs/validateStateSelection.js +33 -0
  319. package/lib/docs/validateStateSelection.test.mjs +33 -0
  320. package/lib/docs/validateViewport.js +51 -0
  321. package/lib/docs/validateViewport.test.mjs +54 -0
  322. package/lib/docs/waitForBuild.js +50 -0
  323. package/lib/docs/waitForBuild.test.mjs +75 -0
  324. package/lib/docs/waitForJourneyEmail.js +77 -0
  325. package/lib/docs/withBrowserSlot.js +45 -0
  326. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  327. package/lib/docs/withDataSession.js +53 -0
  328. package/lib/server/applyDataSetRedirect.js +92 -0
  329. package/lib/server/applyDataSetRedirect.test.mjs +446 -0
  330. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +207 -0
  331. package/lib/server/applyDataSetRedirectWalled.test.mjs +134 -0
  332. package/lib/server/auth/createSystemContext.js +73 -0
  333. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  334. package/lib/server/auth/getHeadlessUser.js +9 -8
  335. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  336. package/lib/server/auth/getMockUser.js +18 -14
  337. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  338. package/lib/server/auth/headlessUser.js +3 -2
  339. package/lib/server/auth/journeyActor.js +35 -0
  340. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  341. package/lib/server/checkPageRecord.js +44 -0
  342. package/lib/server/contextMapBudget.js +24 -0
  343. package/lib/server/contextMapBudget.test.mjs +98 -0
  344. package/lib/server/continueJitKeys.js +28 -0
  345. package/lib/server/continueJitKeys.test.mjs +43 -0
  346. package/lib/server/createDataSessionEndedError.js +33 -0
  347. package/lib/server/createDataSetRefusal.js +26 -0
  348. package/lib/server/createLowdefyContext.js +168 -41
  349. package/lib/server/createLowdefyContext.test.mjs +331 -8
  350. package/lib/server/getClientAddress.js +51 -0
  351. package/lib/server/getClientAddress.test.mjs +56 -0
  352. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  353. package/lib/server/hashConfigContent.js +26 -0
  354. package/lib/server/importAppCode.test.mjs +57 -0
  355. package/lib/server/isRoutineStep.js +27 -0
  356. package/lib/server/jitPageBuilder.js +309 -126
  357. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  358. package/lib/server/jitPageBuilder.test.mjs +218 -0
  359. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  360. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  361. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  362. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  363. package/lib/server/journeyCookies.js +22 -0
  364. package/lib/server/journeyCookies.test.mjs +145 -0
  365. package/lib/server/loadDynamicJsMap.js +47 -0
  366. package/lib/server/log/createHandleError.js +38 -2
  367. package/lib/server/log/createHandleError.test.mjs +125 -2
  368. package/lib/server/log/createLogger.js +3 -0
  369. package/lib/server/log/createLogger.test.mjs +15 -0
  370. package/lib/server/log/logRequest.js +2 -3
  371. package/lib/server/mutantDataSetWrappers.test.mjs +115 -0
  372. package/lib/server/mutants/applyMutant.js +39 -0
  373. package/lib/server/mutants/artifactPatterns.js +25 -0
  374. package/lib/server/mutants/buildAnchor.js +45 -0
  375. package/lib/server/mutants/collectSharedIds.js +41 -0
  376. package/lib/server/mutants/configPathSegments.js +38 -0
  377. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  378. package/lib/server/mutants/describeEvent.js +22 -0
  379. package/lib/server/mutants/enumerateArtifact.js +56 -0
  380. package/lib/server/mutants/findNodeByKey.js +45 -0
  381. package/lib/server/mutants/findNodeByPointer.js +49 -0
  382. package/lib/server/mutants/getArtifactKind.js +32 -0
  383. package/lib/server/mutants/groupMutantCopies.js +56 -0
  384. package/lib/server/mutants/hashArtifact.js +25 -0
  385. package/lib/server/mutants/hashMutantTarget.js +34 -0
  386. package/lib/server/mutants/listMutants.js +122 -0
  387. package/lib/server/mutants/listMutants.test.mjs +502 -0
  388. package/lib/server/mutants/mutantId.js +30 -0
  389. package/lib/server/mutants/mutantRuns.js +57 -0
  390. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  391. package/lib/server/mutants/operators/dropAction.js +47 -0
  392. package/lib/server/mutants/operators/dropBlock.js +44 -0
  393. package/lib/server/mutants/operators/dropPayload.js +52 -0
  394. package/lib/server/mutants/operators/dropStep.js +44 -0
  395. package/lib/server/mutants/operators/flipVisible.js +53 -0
  396. package/lib/server/mutants/operators/index.js +37 -0
  397. package/lib/server/mutants/operators/retargetLink.js +72 -0
  398. package/lib/server/mutants/operators/skipValidate.js +77 -0
  399. package/lib/server/mutants/operators/swapIf.js +112 -0
  400. package/lib/server/mutants/operators.test.mjs +396 -0
  401. package/lib/server/mutants/positionFreePath.js +38 -0
  402. package/lib/server/mutants/removeFromParent.js +28 -0
  403. package/lib/server/mutants/test/fixtures.mjs +173 -0
  404. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  405. package/lib/server/mutants/validateMutantParam.js +69 -0
  406. package/lib/server/mutants/validateMutantsBody.js +88 -0
  407. package/lib/server/mutants/walkArtifact.js +179 -0
  408. package/lib/server/pageBuildRecords.js +131 -0
  409. package/lib/server/pageBuildRecords.test.mjs +258 -0
  410. package/lib/server/pageCache.mjs +22 -7
  411. package/lib/server/pageCache.test.mjs +22 -7
  412. package/lib/server/pageRebuilds.test.mjs +543 -0
  413. package/lib/server/pruneJitMaps.js +36 -0
  414. package/lib/server/pruneJitMaps.test.mjs +55 -0
  415. package/lib/server/readInstanceRecord.js +33 -0
  416. package/lib/server/readInstanceRecord.test.mjs +99 -0
  417. package/lib/server/readMergedMaps.js +86 -0
  418. package/lib/server/readMergedMaps.test.mjs +79 -0
  419. package/lib/server/recording/appendRecords.js +33 -0
  420. package/lib/server/recording/errorRecordingStamp.js +34 -0
  421. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  422. package/lib/server/recording/isRecordingEnabled.js +31 -0
  423. package/lib/server/recording/readRecordingCookie.js +71 -0
  424. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  425. package/lib/server/recording/recordingCookiePayload.js +33 -0
  426. package/lib/server/recording/servedBuilds.js +46 -0
  427. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  428. package/lib/server/sinkExternalConnections.js +95 -0
  429. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  430. package/lib/server/skipStaleMapWrites.js +62 -0
  431. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  432. package/lib/server/startIdleGc.js +67 -0
  433. package/lib/server/startIdleGc.test.mjs +138 -0
  434. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  435. package/lib/server/trackSessionWork.js +30 -0
  436. package/lib/vite/createDevServerExclude.js +40 -0
  437. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  438. package/manager/getContext.mjs +25 -3
  439. package/manager/processes/initialBuild.mjs +17 -1
  440. package/manager/processes/initialBuild.test.mjs +104 -0
  441. package/manager/processes/lowdefyBuild.mjs +31 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +72 -0
  443. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  444. package/manager/processes/optimizeDependencies.mjs +59 -0
  445. package/manager/processes/restartServer.mjs +34 -6
  446. package/manager/processes/restartServer.test.mjs +162 -0
  447. package/manager/processes/shutdownServer.mjs +11 -0
  448. package/manager/processes/shutdownServer.test.mjs +45 -0
  449. package/manager/processes/startFirstServer.mjs +46 -0
  450. package/manager/processes/startFirstServer.test.mjs +326 -0
  451. package/manager/processes/startMailSink.mjs +135 -0
  452. package/manager/processes/startMailSink.test.mjs +146 -0
  453. package/manager/processes/startProxy.mjs +129 -12
  454. package/manager/processes/startProxy.test.mjs +366 -0
  455. package/manager/processes/startServer.mjs +47 -6
  456. package/manager/processes/startServer.test.mjs +155 -0
  457. package/manager/processes/startWatchers.mjs +6 -4
  458. package/manager/processes/syncServer.mjs +69 -0
  459. package/manager/processes/syncServer.test.mjs +141 -0
  460. package/manager/processes/waitForDevServer.mjs +48 -0
  461. package/manager/processes/waitForDevServer.test.mjs +112 -0
  462. package/manager/run.mjs +128 -43
  463. package/manager/utils/BatchChanges.mjs +21 -5
  464. package/manager/utils/BatchChanges.test.mjs +77 -0
  465. package/manager/utils/acquireDevInstance.mjs +99 -0
  466. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  467. package/manager/utils/createBuildActivity.mjs +67 -0
  468. package/manager/utils/createBuildActivity.test.mjs +58 -0
  469. package/manager/utils/createChangeSignal.mjs +39 -0
  470. package/manager/utils/createChangeSignal.test.mjs +62 -0
  471. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  472. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  473. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  474. package/manager/utils/createDotPathIgnore.mjs +44 -0
  475. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  476. package/manager/utils/createIdleGc.mjs +51 -0
  477. package/manager/utils/createIdleGc.test.mjs +77 -0
  478. package/manager/utils/createRequestActivity.mjs +100 -0
  479. package/manager/utils/createRequestActivity.test.mjs +121 -0
  480. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  481. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  482. package/manager/utils/createServerEnv.mjs +39 -0
  483. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  485. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  486. package/manager/utils/importFresh.mjs +46 -0
  487. package/manager/utils/importFresh.test.mjs +72 -0
  488. package/manager/utils/importFreshWorker.mjs +20 -0
  489. package/manager/utils/killTaggedBrowser.mjs +35 -0
  490. package/manager/utils/pagePathChanged.mjs +56 -0
  491. package/manager/utils/pruneRecordings.mjs +85 -0
  492. package/manager/utils/pruneRecordings.test.mjs +117 -0
  493. package/manager/utils/publishBuildDirectory.mjs +121 -0
  494. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  495. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  496. package/manager/utils/readBasePath.mjs +27 -0
  497. package/manager/utils/readBasePath.test.mjs +43 -0
  498. package/manager/utils/readBuildStatusWait.mjs +67 -0
  499. package/manager/utils/readManagerVersion.mjs +30 -0
  500. package/manager/utils/readManagerVersion.test.mjs +35 -0
  501. package/manager/utils/readPluginDefinitions.mjs +36 -0
  502. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  503. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  504. package/manager/utils/resolvePorts.mjs +49 -0
  505. package/manager/utils/setupWatcher.mjs +19 -11
  506. package/manager/utils/startRecordingPruner.mjs +45 -0
  507. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  508. package/manager/utils/waitForChildExit.mjs +53 -0
  509. package/manager/utils/waitForChildExit.test.mjs +70 -0
  510. package/manager/utils/waitForServer.mjs +44 -0
  511. package/manager/utils/writeBuildStatus.mjs +1 -1
  512. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  513. package/manager/watchers/envWatcher.mjs +9 -2
  514. package/manager/watchers/envWatcher.test.mjs +87 -0
  515. package/manager/watchers/listServerSidePackages.mjs +47 -0
  516. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  517. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  518. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  519. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  520. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  521. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  522. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  523. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  524. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  525. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  526. package/package.json +60 -47
  527. package/package.original.json +60 -47
  528. package/src/app.js +131 -20
  529. package/src/html/renderDevPage.js +63 -11
  530. package/src/html/renderDevPage.test.mjs +138 -0
  531. package/src/lib/getRequestPath.js +29 -0
  532. package/src/middleware/createSameOriginGuard.js +37 -0
  533. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  534. package/src/middleware/errorHandler.js +51 -1
  535. package/src/middleware/errorHandler.test.mjs +173 -3
  536. package/src/middleware/isRebindingSafeHost.js +44 -0
  537. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  538. package/src/middleware/localDevToolsOnly.js +38 -0
  539. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  540. package/src/middleware/noStore.js +27 -0
  541. package/src/middleware/noStore.test.mjs +48 -0
  542. package/src/middleware/requestTimeout.js +38 -0
  543. package/src/middleware/staleFlag.js +76 -0
  544. package/src/middleware/staleFlag.test.mjs +127 -0
  545. package/src/middleware/stampCaller.js +38 -0
  546. package/src/middleware/stampCaller.test.mjs +64 -0
  547. package/src/routes/agent.js +15 -3
  548. package/src/routes/auth.js +29 -20
  549. package/src/routes/auth.test.mjs +30 -56
  550. package/src/routes/clientError.js +13 -13
  551. package/src/routes/cron.js +3 -1
  552. package/src/routes/cronForward.js +3 -1
  553. package/src/routes/detached.js +22 -9
  554. package/src/routes/devInspect.js +17 -8
  555. package/src/routes/devInspect.test.mjs +44 -7
  556. package/src/routes/devRecording.js +113 -0
  557. package/src/routes/devRecording.test.mjs +253 -0
  558. package/src/routes/docs/buildStatus.js +10 -2
  559. package/src/routes/docs/buildStatus.test.mjs +46 -0
  560. package/src/routes/docs/content.js +5 -3
  561. package/src/routes/docs/evalOperator.js +15 -1
  562. package/src/routes/docs/events.js +50 -0
  563. package/src/routes/docs/events.test.mjs +117 -0
  564. package/src/routes/docs/find.js +9 -1
  565. package/src/routes/docs/icons.js +30 -0
  566. package/src/routes/docs/inspectState.js +16 -1
  567. package/src/routes/docs/journey.js +194 -0
  568. package/src/routes/docs/journey.test.mjs +401 -0
  569. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  570. package/src/routes/docs/loadState.js +4 -2
  571. package/src/routes/docs/mcp.js +34 -3
  572. package/src/routes/docs/mcp.test.mjs +130 -0
  573. package/src/routes/docs/mutants.js +78 -0
  574. package/src/routes/docs/mutants.test.mjs +180 -0
  575. package/src/routes/docs/parsePathParamsParam.js +41 -0
  576. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  577. package/src/routes/docs/parseUserParam.js +27 -21
  578. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  579. package/src/routes/docs/restart.js +35 -0
  580. package/src/routes/docs/restart.test.mjs +64 -0
  581. package/src/routes/docs/runEndpoint.js +54 -0
  582. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  583. package/src/routes/docs/runRequest.js +25 -8
  584. package/src/routes/docs/runRequest.test.mjs +92 -0
  585. package/src/routes/docs/screenshot.js +50 -2
  586. package/src/routes/docs/screenshot.test.mjs +117 -0
  587. package/src/routes/docs/search.js +15 -2
  588. package/src/routes/docs/snapshotState.js +17 -3
  589. package/src/routes/endpoints.js +27 -26
  590. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  591. package/src/routes/feedback.js +10 -13
  592. package/src/routes/jitPage.js +99 -35
  593. package/src/routes/jitPage.test.mjs +248 -0
  594. package/src/routes/mcp.js +82 -5
  595. package/src/routes/mcp.test.mjs +215 -0
  596. package/src/routes/mcpMemberToken.test.mjs +125 -0
  597. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  598. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  599. package/src/routes/mountDevPageRoutes.js +25 -0
  600. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  601. package/src/routes/mountOauthDiscovery.js +44 -0
  602. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  604. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  605. package/src/routes/reload.js +16 -0
  606. package/src/routes/request.js +6 -4
  607. package/src/routes/request.test.mjs +9 -5
  608. package/src/routes/requestBody.test.mjs +159 -0
  609. package/src/routes/usage.js +6 -2
  610. package/src/routes/user.js +27 -0
  611. package/src/routes/websocket.js +29 -8
  612. package/src/routes/websocket.test.mjs +65 -0
  613. package/src/routes/wellKnownFallback.js +30 -0
  614. package/src/routes/wellKnownFallback.test.mjs +32 -0
  615. package/src/websocket/devWebSocket.js +41 -26
  616. package/src/websocket/devWebSocket.test.mjs +157 -0
  617. package/vite.config.js +53 -23
  618. package/lib/server/auth/getDevSession.js +0 -70
  619. package/lib/server/auth/getDevSession.test.mjs +0 -138
  620. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  621. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  622. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  623. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  624. package/src/routes/apiPage.js +0 -37
@@ -0,0 +1,529 @@
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 { z } from 'zod';
18
+
19
+ import docSources from './docSources.js';
20
+ import { MAX_JOURNEY_TIMEOUT } from './validateJourneyTimeout.js';
21
+ import { MAX_VIEWPORT_SIZE } from './validateViewport.js';
22
+
23
+ // The dev MCP tool contract - names, descriptions, input schemas and the
24
+ // server instructions - kept apart from the handlers so it can be read without
25
+ // a running dev server. The `lowdefy mcp` stdio shim lists these at session
26
+ // start, before any dev server exists, and forwards calls to the right one.
27
+ // This module must stay free of imports that touch build artifacts.
28
+
29
+ const INSTRUCTIONS = `Lowdefy documentation and feedback server for this project. Lowdefy apps are YAML config composing blocks (UI), operators (logic), actions (event handlers), and connections/requests (data).
30
+
31
+ Discovery workflow: start with lowdefy_overview. Use lowdefy_list_types with a kind to discover ALL installed blocks/operators/actions/connections/requests — never guess type names. Then lowdefy_get_schema and lowdefy_get_examples for the exact contract of a type, and lowdefy_get_doc / lowdefy_search_docs for concept documentation. Icons: use a semantic name (icon: edit); otherwise use a Lucide name in PascalCase (icon: Receipt) found with lowdefy_search_icons; never invent an icon name, never emoji; in any HTML string use <i data-icon="edit"></i>, data-tooltip="…" and data-popover="…", ClickableHtml with data-event for clicks (list each event in its dataEvents property; escape request and user data in the HTML), <a data-page-id="page" data-url-query="k=v"> for in-app links (never a hard-coded href; data-new-tab, not target), and data-tag="success" / data-status="success" for statuses (never inline-styled pills), <time datetime="…" data-time="relative">, data-format="currency" data-currency="USD" on raw numbers, data-avatar="Name" (never an avatar image service), data-copy, data-truncate="2", data-tone="secondary" (never inline grey colours) and, in ClickableHtml, confirm on destructive events in dataEvents ({ name: onDelete, confirm: "…" }); lowdefy_get_doc concepts/html-attributes lists them all. lowdefy_search_docs and lowdefy_get_doc also cover the docs this app's own plugins and modules ship (READMEs, docs/*.md, and modules/<id>/manifest for a module's components, exports and vars), so read those before reading plugin or module source.
32
+
33
+ Push events: build results, server restarts and browser/server errors arrive as notifications/message from logger "lowdefy" (data.type is one of build, restart, client_error, server_error; a build event carries status, errors, warnings and stale, for the config build only: pages build on request and send no event). Act on them without polling — lowdefy_build_status remains the full picture, and the only one that reports pages that fail to build.
34
+
35
+ Feedback loop: after EVERY config edit, call lowdefy_build_status with wait: true — the dev server rebuilds on file change, wait: true answers once your edit has been processed and the pages it touched have been built, and this returns build.status (ok when the config builds and no page failed its latest build; pages_failed, with failedPages naming them, when a page failed its latest build and has not changed since; error when the config build fails), the build errors/warnings and each failed page's errors (with source file locations), recent browser runtime errors, and recent server errors (request, endpoint, MCP and agent failures with their config source). Errors reported before your latest edit are listed apart under earlierErrors, since they may already be fixed. Fix what it reports, then confirm the page builds with lowdefy_get_page_config, and visually verify with lowdefy_screenshot_page. Use lowdefy_find_config to locate where any id (page, block, request) is defined. lowdefy_scaffold_page creates a canonical new page file. Use lowdefy_app_map first to understand an existing app. If a tool result begins with "STALE:", the last build FAILED and the answer comes from the previous successful build, not from your latest edits — call lowdefy_build_status and fix the reported errors before trusting anything else.
36
+
37
+ Live state: lowdefy_inspect_state reads the ACTUAL state, request results, and event log of a running page — when the developer has the page open in their browser it reads THEIR live tab (ask them to interact, then inspect), otherwise it runs the page headless. lowdefy_eval_operator evaluates any operator expression against that live state — use it to debug _state/_request bindings. lowdefy_run_request executes a request with a test payload to verify data shape (read-only unless the app opts into writes). A request or endpoint response too large to return inline is written in full to a file under .lowdefy/responses/ and the result gives its path as responseFile; pass saveResponse: true to always write it there. lowdefy_run_endpoint runs an Api endpoint routine headlessly with a test payload (always needs cli.agentTools.allowWriteRequests, since routines are not classified read-only); a :reject comes back as status "reject" with the routine's own error, not as a tool failure. Pass system: true to run a scheduled or detached-only InternalApi routine as a system context (no _user, auth not checked), exactly as cron would.
38
+
39
+ Behaviour, not just layout: a screenshot shows what rendered, not what works. To verify behaviour, drive the page with lowdefy_run_journey — a declarative list of steps (click, fill, select, press, back, goto, email, as, wait, screenshot, expect) addressed by blockId — and assert on state, visibility, text, url or title. A failing step stops the journey and comes back as data (passed: false, failure with expected/actual, the remaining steps skipped) together with the final page state, so you can read what the app actually did and write the next assertion. A large final state comes back as a summary of its keys; pass state with the paths you need. Pass user to act as a real member (e.g. {"roles":["admin"]}) when the flow is role-gated, or user "none" to test auth itself (sign-up, sign-in, invitations) through the app's own pages and sessions.
40
+
41
+ Role-gated pages: the headless renderer signs in as a roleless user, so a page or request gated on a role renders empty or refused. Pass user to lowdefy_screenshot_page, lowdefy_run_journey, lowdefy_inspect_state, lowdefy_eval_operator, lowdefy_load_state, lowdefy_run_request or lowdefy_run_endpoint to act as a specific caller, in the same forms on every one: a user object (e.g. {"roles":["admin"]}), "none" to act signed out, or the name of a data set user together with data (e.g. user "member", data "crm"), which runs the call on that data set's own database. Vary it per call to compare what different roles see. A request run without user runs as a roleless anonymous caller, so a tenant-walled or role-gated request returns empty rather than an error.
42
+
43
+ Safety: lowdefy_checkpoint snapshots the config files before risky multi-file changes; lowdefy_revert_checkpoint restores them.
44
+
45
+ Visual feedback: developers can press Cmd/Ctrl+/ in the running app to point at elements, draw, and copy annotated feedback to their clipboard, then paste it to you. Pasted annotation blocks start with "Feedback:" and carry the blockId, the resolved config file:line, drawn shapes, and usually an "Annotated screenshot:" file path — READ that image to see exactly what the developer drew. Treat them as precise UI feedback and use lowdefy_inspect_state for the page's live state.
46
+
47
+ State checkpoints (testing): lowdefy_snapshot_state captures a page's live state AND its request/api responses into .lowdefy/state-checkpoints/<name>/ (one file per part; gitignored — checkpoints contain user/session data). lowdefy_load_state puts the app back into that state: headless for your own verification, or registry-only which returns a ?_checkpoint URL the developer can open to manually test the app in that exact state (recorded request data is served automatically). lowdefy_checkpoint_to_mocks converts a checkpoint into e2e mocks.yaml fixtures — use it when asked to write e2e tests.`;
48
+
49
+ const HAZARDS_NOTE =
50
+ ' Results include `hazards`: behaviours of this type that its schema does not show. Read them before writing config.';
51
+
52
+ // One sentence, shared by the description of every tool that acts as someone,
53
+ // so the caller option reads the same wherever an agent meets it.
54
+ const CALLER_NOTE =
55
+ ' Pass user to act as someone: a user object such as {"roles":["admin"]}, "none" to act signed out, or the name of a user in the data set that data names (tests/data/<name>.yaml), which also runs the call on that data set\'s own database.';
56
+
57
+ // Shared by every tool that acts as someone, in the forms a journey takes, so
58
+ // one call can act as an admin and the next as a plain member - each headless
59
+ // call gets its own browser context, so they never share an identity.
60
+ const userSchema = z
61
+ .union([z.literal('none'), z.string().min(1), z.object({}).passthrough()])
62
+ .optional()
63
+ .describe(
64
+ 'Who the call acts as. Omitted: the default roleless headless user. An object: that injected caller, e.g. {"roles":["user-admin"]}, merged over the default, so include email/profile/attributes fields too if the page reads them; no auth engine runs for an injected caller, so nothing derives them. "none": no injected caller, so the app\'s own auth decides and the call runs signed out. Any other string: the name of a user in the data set named by data, e.g. "member". Headless only: it is never applied to a page the developer opens in their own browser, so combining it with source "tab" or load_state mode "registry-only" is an error rather than a silently dropped role.'
65
+ );
66
+
67
+ // Shared by every tool that takes userSchema.
68
+ const dataSchema = z
69
+ .string()
70
+ .optional()
71
+ .describe(
72
+ 'The name of a data set in tests/data/<name>.yaml. The call then runs against a fresh in-memory database of its own, loaded with the data set, as a journey with data does, and user can name one of its users. Headless only, like user.'
73
+ );
74
+
75
+ // Shared by every tool that names a page instance: a page whose `path` has
76
+ // placeholders has one instance per set of values.
77
+ const pathParamsSchema = z
78
+ .record(z.union([z.string(), z.number()]))
79
+ .optional()
80
+ .describe(
81
+ 'Values for the placeholders of a page whose path has them, read by _path_params, e.g. {"space": "support", "ticket_id": "1234"} for path "tickets/{space}/{ticket_id}" (lowdefy_app_map shows each page\'s path). Required to open such a page headless; a missing placeholder is an error naming it.'
82
+ );
83
+
84
+ // Shared by the request and endpoint runners.
85
+ const saveResponseSchema = z
86
+ .boolean()
87
+ .optional()
88
+ .describe(
89
+ 'Write the full response to a JSON file under .lowdefy/responses/ in the app directory and return its path (responseFile) instead of the response. A response too large to return inline is always written there.'
90
+ );
91
+
92
+ const devToolDefinitions = {
93
+ lowdefy_inspect_state: {
94
+ description:
95
+ "Read the LIVE state of a running page: state, request results, event log (recent actions fired), global, user, input, and urlQuery. If the developer has the page open in a browser it reads their actual tab (ask them to interact first, then inspect); otherwise it runs the page headless. Use this to see what the app's data model really looks like." +
96
+ CALLER_NOTE,
97
+ inputSchema: {
98
+ pageId: z.string().describe('The page id to inspect.'),
99
+ pathParams: pathParamsSchema.describe(
100
+ 'The page instance to inspect, for a page whose path has placeholders, e.g. {"ticket_id": "1"}. Without it a live tab is read at the instance it shows (or that page\'s most recently shown one); headless needs it to open the page.'
101
+ ),
102
+ source: z
103
+ .enum(['tab', 'headless'])
104
+ .optional()
105
+ .describe('Force a source. Default: live tab if connected, else headless.'),
106
+ user: userSchema,
107
+ data: dataSchema,
108
+ },
109
+ },
110
+
111
+ lowdefy_eval_operator: {
112
+ description:
113
+ 'Evaluate a Lowdefy operator expression against the live state of a running page — a REPL for config. Pass the operator object in the "expression" argument — any JSON value, e.g. {"_state": "customer.name"} or {"_if": {...}}. Evaluates in the real browser runtime (live tab if connected, else headless).' +
114
+ CALLER_NOTE,
115
+ inputSchema: {
116
+ pageId: z.string().describe('The page id whose context to evaluate against.'),
117
+ pathParams: pathParamsSchema.describe(
118
+ "The page instance to evaluate against, for a page whose path has placeholders. Without it a live tab is read at the instance it shows (or that page's most recently shown one); headless needs it to open the page."
119
+ ),
120
+ expression: z
121
+ .any()
122
+ .describe('The operator expression — any JSON value, e.g. {"_state": "key"}.'),
123
+ source: z.enum(['tab', 'headless']).optional(),
124
+ user: userSchema,
125
+ data: dataSchema,
126
+ },
127
+ },
128
+
129
+ lowdefy_run_request: {
130
+ description:
131
+ 'Execute a request in dev with a test payload to verify the data shape a page receives. The page is built first when it changed since its last build, so the request that runs is the one in the config now; a page that fails to build is refused with its build errors. Read-only request types always run; write requests are refused unless the app opts in (cli.agentTools.allowWriteRequests in lowdefy.yaml). A response too large to return inline is written in full to responseFile.' +
132
+ CALLER_NOTE,
133
+ inputSchema: {
134
+ pageId: z.string().describe('The page the request is defined on.'),
135
+ requestId: z.string().describe('The request id.'),
136
+ payload: z.record(z.any()).optional().describe('Test payload for _payload operators.'),
137
+ user: userSchema,
138
+ data: dataSchema,
139
+ saveResponse: saveResponseSchema,
140
+ },
141
+ },
142
+
143
+ lowdefy_run_endpoint: {
144
+ description:
145
+ 'Execute an Api endpoint routine in dev with a test payload and caller, to verify what it returns, rejects or throws. Requires agent write access (cli.agentTools.allowWriteRequests) because routines are not classified read-only. A :reject or :throw comes back as data (success: false, status "reject"/"error" with the routine\'s own error), not as a tool failure. Pass system: true to run it as a system context the way a cron or detached run does — no user (_user undefined), endpoint auth not checked, InternalApi endpoints allowed — which is the local test path for scheduled (schedules) and detached-only routines. Nested CallApi steps with detached: true still dispatch over HTTP and need CRON_SECRET set on the dev server; they are not faked.' +
146
+ CALLER_NOTE,
147
+ inputSchema: {
148
+ endpointId: z.string().describe('The Api endpoint id.'),
149
+ payload: z.record(z.any()).optional().describe('Test payload for _payload operators.'),
150
+ user: userSchema,
151
+ data: dataSchema,
152
+ system: z
153
+ .boolean()
154
+ .optional()
155
+ .describe(
156
+ 'Run as a system context (like /api/cron and /api/detached): no user, auth not checked, InternalApi allowed. Cannot be combined with user.'
157
+ ),
158
+ saveResponse: saveResponseSchema,
159
+ },
160
+ },
161
+
162
+ lowdefy_restart: {
163
+ description:
164
+ 'Restart the dev server process. Use when the server seems stuck or build_status looks stale, or after secrets a wrapper (for example infisical) injects have changed; .env edits and local plugin code are picked up without a restart. The connection drops: call lowdefy_build_status with wait: true, which answers once the restarted server is ready.',
165
+ inputSchema: {
166
+ reason: z.string().optional().describe('Why the restart is needed (logged by the manager).'),
167
+ },
168
+ },
169
+
170
+ lowdefy_app_map: {
171
+ description:
172
+ 'The whole-app graph in one call: every page (with its source file and, when built, block/request summaries), menus, connections, api endpoints, agents, and websockets. Call this first when working in an existing app.',
173
+ inputSchema: {},
174
+ },
175
+
176
+ lowdefy_snapshot_state: {
177
+ description:
178
+ "Capture the live state AND recorded request/api responses of a running page into a checkpoint folder (.lowdefy/state-checkpoints/<name>/, one file per part; gitignored — checkpoints contain user/session data). Snapshot the developer's open tab after they reproduce a scenario, or a headless run. Use for building test fixtures and reproducible app states.",
179
+ inputSchema: {
180
+ pageId: z.string().describe('The page to snapshot.'),
181
+ pathParams: pathParamsSchema.describe(
182
+ "The page instance to snapshot, for a page whose path has placeholders. The checkpoint keeps the instance's pathParams, and lowdefy_load_state opens that instance."
183
+ ),
184
+ name: z.string().describe('Checkpoint name (letters, numbers, - and _).'),
185
+ notes: z.string().optional().describe('What this checkpoint captures.'),
186
+ source: z.enum(['tab', 'headless']).optional(),
187
+ overwrite: z.boolean().optional(),
188
+ },
189
+ },
190
+
191
+ lowdefy_load_state: {
192
+ description:
193
+ "Put the app back into a saved state checkpoint. mode 'headless' (default) verifies the restored state itself; mode 'registry-only' loads the recorded request data into the dev server and returns a ?_checkpoint URL the developer can open to manually test the app in that exact state." +
194
+ CALLER_NOTE,
195
+ inputSchema: {
196
+ name: z.string().describe('The checkpoint name.'),
197
+ mode: z.enum(['headless', 'registry-only']).optional(),
198
+ user: userSchema,
199
+ data: dataSchema,
200
+ },
201
+ },
202
+
203
+ lowdefy_list_state_checkpoints: {
204
+ description: 'List saved state checkpoints (name, page, captured time, notes).',
205
+ inputSchema: {},
206
+ },
207
+
208
+ lowdefy_checkpoint_to_mocks: {
209
+ description:
210
+ 'Convert a state checkpoint into @lowdefy/e2e-utils mocks.yaml fixtures (requests/api entries plus rendered yaml). Use when writing e2e tests from a captured scenario.',
211
+ inputSchema: {
212
+ name: z.string().describe('The checkpoint name.'),
213
+ },
214
+ },
215
+
216
+ lowdefy_checkpoint: {
217
+ description:
218
+ 'Snapshot all config files before risky changes. Returns a checkpoint id for lowdefy_revert_checkpoint. Use before multi-file edits so you can restore instantly.',
219
+ inputSchema: {
220
+ label: z.string().describe('Short label for the checkpoint, e.g. "before-refactor".'),
221
+ },
222
+ },
223
+
224
+ lowdefy_revert_checkpoint: {
225
+ description:
226
+ 'Restore config files from a checkpoint made with lowdefy_checkpoint (restores changed files and removes files added since). Omit id to list available checkpoints.',
227
+ inputSchema: {
228
+ id: z.string().optional().describe('Checkpoint id. Omit to list checkpoints.'),
229
+ },
230
+ },
231
+
232
+ lowdefy_build_status: {
233
+ description:
234
+ 'Call after every config edit. Returns the current build status: build.status is ok when the config build succeeds and no page failed its latest build, pages_failed (with build.failedPages naming the pages) when the config builds but a page failed its latest build and has not changed since, and error when the config build fails; build.configStatus is the status of the config build alone. A failed page changed since its last build is not counted until it is rebuilt: it is listed under pages.changedSinceBuild, and wait: true rebuilds it. Then the errors and warnings from the last config build (with source file locations), the page builds (pages: failed holds the errors of each failed page, changedSinceBuild the pages changed since they were built, unbuilt how many pages nothing has built since the dev server started), recent browser runtime errors, and recent server errors — request, endpoint, MCP and agent tool failures with their config source. Errors reported under an earlier build come apart under earlierErrors. The dev server rebuilds automatically on file change — edit, then call this with wait: true to see what broke.',
235
+ inputSchema: {
236
+ wait: z
237
+ .boolean()
238
+ .optional()
239
+ .describe(
240
+ 'Wait until the dev server has processed your latest edits (the rebuild or page invalidation they trigger, and any server restart or plugin install the rebuild needs), then build every page they touched, before answering, instead of returning the status of the build before them. pages.checked lists the pages it built. Use it right after an edit. Waits up to two minutes; `settled: false` in the result means it gave up.'
241
+ ),
242
+ },
243
+ },
244
+
245
+ lowdefy_check: {
246
+ description:
247
+ 'Validate the whole app the way a production build (`lowdefy build`) would, without building it: every page, including ones not yet opened in dev, and the prod-only checks `lowdefy dev` reports only as warnings (they come back here as errors with prodError: true). Returns ok plus located errors and warnings (source file:line). Takes a few seconds. Call before telling the developer a change is done.',
248
+ inputSchema: {},
249
+ },
250
+
251
+ lowdefy_get_page_config: {
252
+ description:
253
+ 'Get the fully built config for a page, or its structured build errors if the page fails to build. Use to verify a page after editing it.',
254
+ inputSchema: {
255
+ pageId: z.string().describe('The page id.'),
256
+ },
257
+ },
258
+
259
+ lowdefy_find_config: {
260
+ description:
261
+ 'Find where a config entity is defined: pass a page, block, or request id and get the source yaml file (and line where available). For block/request ids also pass the owning pageId so the page is built first.' +
262
+ HAZARDS_NOTE,
263
+ inputSchema: {
264
+ id: z.string().describe('The id to find, e.g. a pageId, blockId, or requestId.'),
265
+ pageId: z
266
+ .string()
267
+ .optional()
268
+ .describe(
269
+ 'The page the id belongs to — required for block/request ids on pages not yet built.'
270
+ ),
271
+ },
272
+ },
273
+
274
+ lowdefy_screenshot_page: {
275
+ description:
276
+ 'Screenshot a page of the running dev server (headless Chromium) to visually verify layout and rendering. Returns a PNG image. Set width (and height) to check a narrow or phone layout, e.g. width 390, and colorScheme "dark" to check dark mode. To capture a state beyond the page as it loads — an OPEN Selector / MultipleSelector / AutoComplete / DateSelector (calendar) / Cascader / TreeSelector dropdown, a modal a button opens — pass steps, e.g. [{"open": "status"}]: they run after the page settles and before the capture. Popups antd renders in a portal are included, also with fullPage. Pass urlQuery for a page that reads _url_query, and pathParams for a page whose path has placeholders.' +
277
+ CALLER_NOTE,
278
+ inputSchema: {
279
+ pageId: z.string().describe('The page id to screenshot.'),
280
+ pathParams: pathParamsSchema,
281
+ urlQuery: z
282
+ .record(z.any())
283
+ .optional()
284
+ .describe(
285
+ 'Query params to open the page with, read by _url_query, e.g. {"framework": "popia"}.'
286
+ ),
287
+ steps: z
288
+ .array(z.record(z.any()))
289
+ .optional()
290
+ .describe(
291
+ 'Interaction steps run in order before the capture, addressed by blockId — the same steps as lowdefy_run_journey: {"open": target} (open an input\'s dropdown/picker popup and wait for it; works for every popup input) | {"click": target} (a button, e.g. to open a modal) | {"fill": {"blockId", "value"}} (type text, e.g. to filter an AutoComplete, then {"open": id}) | {"select": {"blockId", "value"}} | {"press": "Enter"} | {"wait": {"ms": n}}. A target is a blockId string or {"blockId", "text", "nth", ...}; {"text": "OK"} alone reaches modal/menu buttons. {"screenshot": name?} captures an earlier state too: its image follows the final capture. If a step fails the screenshot is still returned, showing where it stopped, with the failure alongside.'
292
+ ),
293
+ fullPage: z.boolean().optional().describe('Capture the full scrollable page.'),
294
+ clip: z
295
+ .object({
296
+ x: z.number(),
297
+ y: z.number(),
298
+ width: z.number(),
299
+ height: z.number(),
300
+ })
301
+ .optional()
302
+ .describe(
303
+ "Crop to a viewport-relative region — pass an annotation's geometry from feedback."
304
+ ),
305
+ scrollX: z.number().optional().describe('Scroll offset the clip was recorded at.'),
306
+ scrollY: z.number().optional().describe('Scroll offset the clip was recorded at.'),
307
+ width: z
308
+ .number()
309
+ .int()
310
+ .positive()
311
+ .max(MAX_VIEWPORT_SIZE)
312
+ .optional()
313
+ .describe(
314
+ `Viewport width in CSS pixels, at most ${MAX_VIEWPORT_SIZE}. Default 1280; 390 is a phone.`
315
+ ),
316
+ height: z
317
+ .number()
318
+ .int()
319
+ .positive()
320
+ .max(MAX_VIEWPORT_SIZE)
321
+ .optional()
322
+ .describe(
323
+ `Viewport height in CSS pixels, at most ${MAX_VIEWPORT_SIZE}. Default 800. Use fullPage for a long page.`
324
+ ),
325
+ colorScheme: z
326
+ .enum(['light', 'dark'])
327
+ .optional()
328
+ .describe(
329
+ 'The colour scheme the page\'s prefers-color-scheme reports. Default "light". An app that follows the system theme renders dark with "dark"; a darkMode fixed in the app config wins.'
330
+ ),
331
+ user: userSchema,
332
+ data: dataSchema,
333
+ },
334
+ },
335
+
336
+ lowdefy_run_journey: {
337
+ description:
338
+ 'Drive a page of the running dev server headless through declarative steps and assert what happens — the way to verify behaviour (a form submits, a modal opens, a filter works), not just layout. Blocks are addressed by blockId; a target object narrows to a grid row/cell ({"blockId": "grid", "row": 1, "column": "actions"}), to the control with exactly some text ({"blockId": "grid", "row": 1, "text": "Edit"}), or reaches portal-rendered controls page-wide by text alone ({"text": "OK"} for a confirm dialog or modal footer button, a menu item). A step that fails stops the journey and is returned as data (passed: false, failure with index/step/expected/actual/message, later steps "skipped") — never as a tool error. A journey also fails at the step that causes an app error its own browsers raised (an action failing with an error that is not a user error, an uncaught page error, a server error or 5xx from a request or endpoint): failure.kind "app-error" with errors [{kind, message, source, configKey, key}], or phase "open" when opening the page raised it. A failed Validate, a Throw action and 401/403 refusals never count. Where an error is the intended outcome, {"expect": {"error": text}} straight after the interaction claims its errors whose message contains text. Returns the final page state (whole when small, otherwise stateOmitted with its size and top-level keys; see the state param) and any screenshots taken (as images after the JSON text).' +
339
+ CALLER_NOTE,
340
+ inputSchema: {
341
+ pageId: z.string().describe('The page id to open.'),
342
+ steps: z
343
+ .array(z.record(z.any()))
344
+ .describe(
345
+ 'Ordered steps, one key each: {"click": target} (a target object may add "count": 2 or 3 for that many clicks in quick succession, a double click, before the runner settles) | {"open": target} (open an input\'s dropdown or picker popup - Selector, MultipleSelector, AutoComplete, DateSelector, Cascader... - and wait for it to show, so a following screenshot step captures it) | {"fill": {...target, "value"}} | {"fill": {...target, "fromEmail": {"to", "subject"?, "match"}}} (type text read from the newest email to that address, like the email step, without leaving the page: the first match of the regular expression "match", or its first capture group, e.g. "\\\\b\\\\d{6}\\\\b" for a one-time sign-in code) | {"select": {...target, "value"}} (option by exact text: a dropdown option, a radio, button or segmented option in the block, or a checkbox option in the block, which it ticks and never unticks) | {"press": "Enter" | "Mod+k"} (Mod is Meta/Control per platform) | {"back": true} (the browser Back button) | {"goto": pageId | {"pageId", "pathParams", "urlQuery"}} (load an app page like a typed URL, its path built from pathParams, one string per placeholder of the page\'s path; a protected page may redirect to sign-in) | {"email": {"to", "subject"?}} (open the newest email to that address, subject containing the text, that arrived during this journey, waiting for it if needed; opening it again opens the same message; then click its links by text, e.g. {"click": {"text": "Verify email address"}}; needs the dev server started with LOWDEFY_DEV_SMTP_PORT and the app\'s SMTP connection pointed at 127.0.0.1 on that port) | {"as": name} (switch to another person, each with their own browser and cookies; the journey starts as "main", and a new name opens the journey\'s page) | {"wait": {"ms": n} | {"request": requestId} (a call started since the last interaction, or since the page opened, has finished) | {"state": path}} | {"screenshot": name?} | {"expect": {"state": {"path", "equals"}} | {"visible": target} | {"hidden": target} (nothing the target names is visible; passes at once when nothing matches yet, so pair it with something that must be present first) | {"text": {...target, "contains"}} | {"url": {"contains"}} | {"title": {"equals"} | {"contains"}} | {"calls": {"request", "pageId"?, "count"} | {"endpoint", "count"}} | {"error": text} | {"effect": true}} (title is the document title; calls compares, once the page settles, how many times this actor called the request - on pageId, default the current page - or the endpoint since the journey started; error must directly follow an interaction step and claims the app errors it raised whose message contains text, with the failed action that reported them; effect must directly follow an interaction step and fails when that interaction did nothing: no event ran, the page did not change, no request or endpoint was called and the URL stayed the same, which proves a dead click). A target is a blockId string, or an object of {"blockId", "row" (zero-based grid row as displayed), "column" (grid col-id), "text" (exact text of the interactive control to use), "containing" (text the element shows, e.g. the email a list row shows; a click on it reaches the row\'s click handler), "nth" (zero-based pick among several matches; a click, open, fill or select whose text or containing matches more than one visible element fails without it)}; "text" without "blockId" searches the whole page, which is how confirm dialog / modal footer buttons, dropdown menu items and email links are reached. fill, select and expect.text need a blockId. Every blockId, requestId, endpoint and goto pageId a step names is checked against the build before it runs (and an "as" name against the data set\'s users on a data set run): an unknown one fails the step and names it. fill, select and expect.state take an optional "from": "recorded" (the value came from a recorded trace) or "shape" (value: null, a placeholder a trace could not hold); a journey holding a from: shape placeholder is refused until it is filled in. Each step gets 5s, or the journey\'s timeout: every wait for something to happen (a control to be actionable, an expect to match, a request, state or email to arrive) is bounded by it; page opens get at least 15s; after an interaction the runner waits at most 5s for the page\'s pending events and requests to settle, without failing; wait.ms is exact. lowdefy_get_schema with kind "journey-step" returns this grammar as a JSON schema, with examples of every step.'
346
+ ),
347
+ user: z
348
+ .union([
349
+ z.literal('none'),
350
+ z.string().min(1),
351
+ z.object({}).passthrough(),
352
+ z.array(z.string().min(1)).min(1),
353
+ ])
354
+ .optional()
355
+ .describe(
356
+ 'Who the journey acts as. Omitted: the default roleless headless user. An object: that injected caller, e.g. {"roles":["user-admin"]}, merged over the default. "none": no injected caller - the journey starts signed out and the app\'s own auth decides who it is, so sign-up, sign-in, invitations and session cookies run for real (every actor opened with "as" starts signed out too); a protected page opened signed out lands on the sign-in page. Refused on a data set while auth is configured. Any other string: the name of a user in the journey\'s data set (needs data), e.g. "member". A list of data set user names (needs data), e.g. ["admin", "member"]: the journey runs once as each, in a fresh data session each, and returns {passed, runs: [{user, passed, failure, state, ...}]}, passed only when every run passed; the screenshots of every run follow, run by run.'
357
+ ),
358
+ data: z
359
+ .string()
360
+ .optional()
361
+ .describe(
362
+ 'The name of a data set in tests/data/<name>.yaml. The journey then runs against a fresh in-memory MongoDB database of its own, loaded with the data set\'s fixtures and generated documents (generate: seeded background rows, the same on every machine), while the developer\'s own tabs keep the app\'s real database: it may write freely. user and "as" step names can name the data set\'s users ("as": "outsider" opens that actor as the data set user outsider). Values the journey types, selects or asserts should come from the data set\'s fixtures or users, not its generated documents. The data set format: GET /lowdefy-docs/content/testing/journey-data-sets. Refused while a dev mock user is active.'
363
+ ),
364
+ pathParams: z
365
+ .record(z.string())
366
+ .optional()
367
+ .describe(
368
+ 'Values for the placeholders of the page\'s path, read by _path_params, e.g. {"space": "support", "ticket_id": "1"} for path "{space}/tickets/{ticket_id}".'
369
+ ),
370
+ urlQuery: z
371
+ .record(z.any())
372
+ .optional()
373
+ .describe('Query params to open the page with, read by _url_query, e.g. {"id": "1"}.'),
374
+ timeout: z
375
+ .number()
376
+ .int()
377
+ .min(1)
378
+ .max(MAX_JOURNEY_TIMEOUT)
379
+ .optional()
380
+ .describe(
381
+ `How long each step may wait, in milliseconds (at most ${MAX_JOURNEY_TIMEOUT}). Default 5000. Raise it for a slow machine instead of adding wait ms steps; a step still moves on as soon as its condition holds. Page opens get at least 15000; the settle after an interaction stays at most 5000.`
382
+ ),
383
+ state: z
384
+ .union([z.boolean(), z.array(z.string().min(1))])
385
+ .optional()
386
+ .describe(
387
+ 'What the result carries of the final page state. Omitted: the whole state when it is at most 10000 characters of JSON, otherwise stateOmitted (its size and each top-level key with its size). An array of state paths, e.g. ["form.name", "rows"]: state is {path: value} for each, null where undefined. true: the whole state, however large. false: no state.'
388
+ ),
389
+ exercised: z
390
+ .boolean()
391
+ .optional()
392
+ .describe(
393
+ "What the result carries of what the run exercised (every request, endpoint, event and rendered block it touched). Omitted: only a count of each. true: the full block, e.g. to check that a block rendered or which requests ran. false: leave it out. `lowdefy test` and the journey lints always read the full block; this only shapes this tool's response."
394
+ ),
395
+ },
396
+ },
397
+
398
+ lowdefy_scaffold_page: {
399
+ description:
400
+ 'Create a new page yaml file with a canonical minimal structure. Refuses if the page already exists. Returns the created file path and the lowdefy.yaml registration step you must do next.',
401
+ inputSchema: {
402
+ pageId: z.string().describe('The new page id (letters, numbers, - and _).'),
403
+ title: z.string().optional().describe('Page title. Defaults to the pageId.'),
404
+ },
405
+ },
406
+
407
+ lowdefy_overview: {
408
+ description:
409
+ 'Start here. Overview of everything this Lowdefy project has available: counts of blocks/operators/actions/connections/requests, installed plugins, doc sections, and which tool to use next.',
410
+ inputSchema: {},
411
+ },
412
+
413
+ lowdefy_list_types: {
414
+ description:
415
+ 'List ALL available types of a kind — every block, operator, action, connection, or request type installed in this project (core and local plugins), whether used yet or not. Call this before writing any config to get exact type names.',
416
+ inputSchema: {
417
+ kind: z
418
+ .enum([
419
+ 'blocks',
420
+ 'operators',
421
+ 'actions',
422
+ 'connections',
423
+ 'requests',
424
+ 'agents',
425
+ 'notifications',
426
+ 'websockets',
427
+ ])
428
+ .describe('The kind of types to list.'),
429
+ },
430
+ },
431
+
432
+ lowdefy_list_plugins: {
433
+ description:
434
+ "List installed plugin packages (including this project's local custom plugins) and the type names each provides.",
435
+ inputSchema: {},
436
+ },
437
+
438
+ lowdefy_get_schema: {
439
+ description:
440
+ 'Get the JSON Schema for a specific type: all properties, events, and their descriptions. Use the exact type name from lowdefy_list_types.' +
441
+ HAZARDS_NOTE,
442
+ inputSchema: {
443
+ kind: z
444
+ .enum(['blocks', 'operators', 'actions', 'connections', 'requests', 'journey-step'])
445
+ .describe(
446
+ 'The kind of the type. "journey-step" returns the lowdefy_run_journey step grammar, with examples of every step.'
447
+ ),
448
+ type: z
449
+ .string()
450
+ .optional()
451
+ .describe(
452
+ 'The exact type name, e.g. "Button", "_get", "MongoDBFind". Required, except for kind "journey-step", where it names one step (e.g. "wait") and is omitted for every step.'
453
+ ),
454
+ },
455
+ },
456
+
457
+ lowdefy_get_examples: {
458
+ description:
459
+ 'Get real YAML usage examples for a block type (gallery and example configs shipped with the plugin).',
460
+ inputSchema: {
461
+ type: z.string().describe('The exact block type name, e.g. "Button".'),
462
+ },
463
+ },
464
+
465
+ lowdefy_get_doc: {
466
+ description:
467
+ 'Get a Lowdefy documentation page as markdown. Look up by slug (e.g. "concepts/lowdefy-schema", "operators/_get") or by kind + type name. Also returns the docs this app\'s own plugins and modules ship: "plugins/<package>" and "modules/<id>" for a README, "plugins/<package>/<file>" and "modules/<id>/<file>" for a docs/*.md file, and "modules/<id>/manifest" for the components, exports and vars a module declares. Key concept slugs: concepts/lowdefy-schema, concepts/blocks, concepts/events-and-actions, concepts/connections-and-requests, concepts/operators, concepts/page-and-app-state.' +
468
+ HAZARDS_NOTE,
469
+ inputSchema: {
470
+ slug: z.string().optional().describe('Doc slug, e.g. "operators/_get".'),
471
+ kind: z
472
+ .enum(['block', 'operator', 'action', 'connection'])
473
+ .optional()
474
+ .describe('Kind of the type to find the doc for.'),
475
+ type: z.string().optional().describe('Type name to find the doc for, e.g. "_get".'),
476
+ },
477
+ },
478
+
479
+ lowdefy_search_docs: {
480
+ description:
481
+ 'Search the Lowdefy docs by keywords. The query is split into words, and pages are ranked by how many match, title matches first. Returns up to 20 hits, each with its slug, snippet, source, package and version; read one with lowdefy_get_doc.',
482
+ inputSchema: {
483
+ query: z.string().describe('Search keywords, e.g. "Link action pathParams".'),
484
+ source: z
485
+ .enum(docSources)
486
+ .optional()
487
+ .describe('Only search docs from this source. Default: all.'),
488
+ },
489
+ },
490
+
491
+ lowdefy_search_icons: {
492
+ description:
493
+ 'Search icon names before using one — never invent a name. Returns matching semantic names (edit, delete, warning, … and this app\'s theme.icons.aliases) with their targets, then Lucide names in PascalCase, then qualified names (set:Name) from installed icon set plugins. Use a semantic name when one fits, otherwise a Lucide name, in any icon property (icon: edit, icon: Receipt) or in HTML as <i data-icon="edit"></i>.',
494
+ inputSchema: {
495
+ query: z.string().describe('What the icon shows, e.g. "trash" or "arrow right".'),
496
+ limit: z
497
+ .number()
498
+ .int()
499
+ .positive()
500
+ .optional()
501
+ .describe('Max icon names (after semantic names). Default 30.'),
502
+ },
503
+ },
504
+
505
+ lowdefy_get_plugin_doc: {
506
+ description:
507
+ 'Get all the markdown an installed plugin package ships (README, docs/*.md) as one page. lowdefy_search_docs and lowdefy_get_doc reach the same files page by page.',
508
+ inputSchema: {
509
+ package: z.string().describe('The package name, e.g. "@lowdefy/blocks-antd".'),
510
+ },
511
+ },
512
+
513
+ lowdefy_journey_session: {
514
+ description:
515
+ 'Read what the developer did in this app\'s dev server, to write journeys from. Without an id: the recorded dev sessions, newest first, one line each (id, time span, pages, interactions, failures). With an id: that session as a log, one line per interaction with what the app did in response, e.g. `click save → Validate failed [priority]`, `fill title "Quarterly"`, `click save → ran Validate, request createTicket ok`, and `page <pageId>` per page view. Failed attempts and their retries are both kept; values typed in dev and generated ids show as recorded. Write each journey from the log yourself (assert outcomes, not generated ids) and prove it with lowdefy_run_journey or `lowdefy test --repeat 3 <file>`.',
516
+ inputSchema: {
517
+ id: z.string().optional().describe('The session to read, as the list names it.'),
518
+ since: z
519
+ .string()
520
+ .optional()
521
+ .describe(
522
+ 'Only sessions with records at or after this time: a duration back from now (30m, 2h, 7d) or an ISO date.'
523
+ ),
524
+ },
525
+ },
526
+ };
527
+
528
+ export { HAZARDS_NOTE, INSTRUCTIONS };
529
+ export default devToolDefinitions;
@@ -0,0 +1,21 @@
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
+ // Where a docs index entry comes from. Search hits name it, and the search
18
+ // source filter takes one of these.
19
+ const docSources = ['core', 'plugin', 'local-plugin', 'module'];
20
+
21
+ export default docSources;