dineway 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) hide show
  1. package/dist/activity-DUaz_w7p.mjs +79 -0
  2. package/dist/{activity-events-y6eT8UW6.mjs → activity-events-CJDvE2uc.mjs} +1 -77
  3. package/dist/{actor-CrObAPV0.mjs → actor-0QJ1Gbtf.mjs} +1 -1
  4. package/dist/{allowed-origins-_oeyP6x-.mjs → allowed-origins--OwHHiY8.mjs} +1 -1
  5. package/dist/api/route-utils.d.mts +9 -10
  6. package/dist/api/route-utils.mjs +14 -14
  7. package/dist/api/schemas/index.d.mts +2 -2
  8. package/dist/api/schemas/index.mjs +5 -5
  9. package/dist/{api-ByrU2q-Z.mjs → api-B4PHIF4V.mjs} +51 -23
  10. package/dist/{api-tokens-CjVMQT-X.mjs → api-tokens-tbK_sORH.mjs} +1 -1
  11. package/dist/{apply-bZswmZPm.mjs → apply-DjTqq6Ae.mjs} +19 -19
  12. package/dist/astro/index.d.mts +10 -11
  13. package/dist/astro/index.mjs +10 -5
  14. package/dist/astro/middleware/auth.d.mts +9 -19
  15. package/dist/astro/middleware/auth.mjs +6 -6
  16. package/dist/astro/middleware/redirect.mjs +5 -5
  17. package/dist/astro/middleware/request-context.mjs +2 -2
  18. package/dist/astro/middleware/seed.d.mts +1 -1
  19. package/dist/astro/middleware/seed.mjs +1 -1
  20. package/dist/astro/middleware/setup.mjs +1 -1
  21. package/dist/astro/middleware.mjs +140 -98
  22. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  23. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  24. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
  25. package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -6
  26. package/dist/astro/routes/api/admin/briefing.mjs +18 -18
  27. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
  28. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +9 -9
  29. package/dist/astro/routes/api/admin/byline-fields/index.mjs +9 -9
  30. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +9 -9
  31. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +16 -16
  32. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +16 -16
  33. package/dist/astro/routes/api/admin/bylines/index.mjs +16 -16
  34. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +12 -12
  35. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  36. package/dist/astro/routes/api/admin/comments/bulk.mjs +10 -10
  37. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  38. package/dist/astro/routes/api/admin/comments/index.mjs +10 -10
  39. package/dist/astro/routes/api/admin/context/_id_/history.mjs +25 -24
  40. package/dist/astro/routes/api/admin/context/_id_/index.mjs +25 -24
  41. package/dist/astro/routes/api/admin/context/_id_/review.mjs +30 -29
  42. package/dist/astro/routes/api/admin/context/_id_/supersede.mjs +30 -29
  43. package/dist/astro/routes/api/admin/context/diff.mjs +29 -28
  44. package/dist/astro/routes/api/admin/context/index.mjs +30 -29
  45. package/dist/astro/routes/api/admin/context/stale.mjs +29 -28
  46. package/dist/astro/routes/api/admin/hitl-requests/_id_/index.mjs +25 -24
  47. package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.mjs +29 -28
  48. package/dist/astro/routes/api/admin/hitl-requests/index.mjs +29 -28
  49. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +26 -25
  50. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +2 -2
  51. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +6 -6
  52. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +6 -6
  53. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +52 -51
  54. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +52 -51
  55. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +44 -44
  56. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +51 -50
  57. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +51 -50
  58. package/dist/astro/routes/api/admin/plugins/index.mjs +44 -44
  59. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
  60. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +44 -44
  61. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +51 -50
  62. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +44 -44
  63. package/dist/astro/routes/api/admin/plugins/updates.mjs +44 -44
  64. package/dist/astro/routes/api/admin/review-requests/_id_/index.mjs +5 -5
  65. package/dist/astro/routes/api/admin/review-requests/_id_/resolve.mjs +30 -65
  66. package/dist/astro/routes/api/admin/review-requests/index.d.mts +2 -1
  67. package/dist/astro/routes/api/admin/review-requests/index.mjs +58 -11
  68. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +44 -44
  69. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
  70. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +44 -44
  71. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  72. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
  73. package/dist/astro/routes/api/admin/users/_id_/index.mjs +7 -7
  74. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  75. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  76. package/dist/astro/routes/api/auth/dev-bypass.mjs +4 -4
  77. package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
  78. package/dist/astro/routes/api/auth/invite/complete.mjs +10 -10
  79. package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
  80. package/dist/astro/routes/api/auth/invite/register-options.mjs +9 -9
  81. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  82. package/dist/astro/routes/api/auth/magic-link/send.mjs +9 -9
  83. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  84. package/dist/astro/routes/api/auth/me.mjs +8 -8
  85. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  86. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +2 -2
  87. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  88. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  89. package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
  90. package/dist/astro/routes/api/auth/passkey/options.mjs +11 -11
  91. package/dist/astro/routes/api/auth/passkey/register/options.mjs +9 -9
  92. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +10 -10
  93. package/dist/astro/routes/api/auth/passkey/verify.mjs +10 -10
  94. package/dist/astro/routes/api/auth/setup-token/index.mjs +5 -5
  95. package/dist/astro/routes/api/auth/setup-token/verify.mjs +2 -2
  96. package/dist/astro/routes/api/auth/signup/complete.mjs +10 -10
  97. package/dist/astro/routes/api/auth/signup/request.mjs +9 -9
  98. package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
  99. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +13 -13
  100. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +12 -12
  101. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
  102. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +6 -5
  103. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +6 -5
  104. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +6 -5
  105. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +10 -10
  106. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +49 -21
  107. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +6 -5
  108. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
  109. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +67 -33
  110. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +14 -14
  111. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
  112. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -5
  113. package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -10
  114. package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
  115. package/dist/astro/routes/api/content/_collection_/index.mjs +11 -10
  116. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  117. package/dist/astro/routes/api/dashboard.mjs +8 -8
  118. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  119. package/dist/astro/routes/api/health.mjs +1 -1
  120. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  121. package/dist/astro/routes/api/import/probe.mjs +19 -19
  122. package/dist/astro/routes/api/import/wordpress/analyze.mjs +8 -8
  123. package/dist/astro/routes/api/import/wordpress/execute.d.mts +8 -9
  124. package/dist/astro/routes/api/import/wordpress/execute.mjs +35 -34
  125. package/dist/astro/routes/api/import/wordpress/media.d.mts +8 -9
  126. package/dist/astro/routes/api/import/wordpress/media.mjs +12 -12
  127. package/dist/astro/routes/api/import/wordpress/prepare.mjs +34 -33
  128. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +9 -10
  129. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +31 -30
  130. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  131. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +22 -22
  132. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +8 -9
  133. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +40 -39
  134. package/dist/astro/routes/api/manifest.mjs +4 -4
  135. package/dist/astro/routes/api/mcp.mjs +554 -167
  136. package/dist/astro/routes/api/media/_id_/confirm.mjs +10 -10
  137. package/dist/astro/routes/api/media/_id_.mjs +8 -8
  138. package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
  139. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
  140. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
  141. package/dist/astro/routes/api/media/providers/index.mjs +2 -2
  142. package/dist/astro/routes/api/media/upload-url.mjs +11 -11
  143. package/dist/astro/routes/api/media.mjs +14 -14
  144. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +30 -29
  145. package/dist/astro/routes/api/menus/_name_/items.mjs +30 -29
  146. package/dist/astro/routes/api/menus/_name_/reorder.mjs +30 -29
  147. package/dist/astro/routes/api/menus/_name_/translations.mjs +30 -29
  148. package/dist/astro/routes/api/menus/_name_.mjs +30 -29
  149. package/dist/astro/routes/api/menus/index.mjs +30 -29
  150. package/dist/astro/routes/api/oauth/authorize.mjs +7 -7
  151. package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
  152. package/dist/astro/routes/api/oauth/device/code.mjs +9 -9
  153. package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
  154. package/dist/astro/routes/api/oauth/register.mjs +4 -4
  155. package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
  156. package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
  157. package/dist/astro/routes/api/oauth/token.mjs +6 -6
  158. package/dist/astro/routes/api/openapi.json.mjs +4 -4
  159. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +20 -5
  160. package/dist/astro/routes/api/redirects/404s/index.mjs +10 -10
  161. package/dist/astro/routes/api/redirects/404s/summary.mjs +10 -10
  162. package/dist/astro/routes/api/redirects/_id_.mjs +31 -30
  163. package/dist/astro/routes/api/redirects/index.mjs +31 -30
  164. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
  165. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +2 -2
  166. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +46 -45
  167. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +46 -45
  168. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +46 -45
  169. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +46 -45
  170. package/dist/astro/routes/api/schema/collections/index.mjs +46 -45
  171. package/dist/astro/routes/api/schema/index.mjs +8 -8
  172. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +44 -44
  173. package/dist/astro/routes/api/schema/orphans/index.mjs +44 -44
  174. package/dist/astro/routes/api/search/enable.mjs +11 -11
  175. package/dist/astro/routes/api/search/index.mjs +10 -10
  176. package/dist/astro/routes/api/search/rebuild.mjs +11 -11
  177. package/dist/astro/routes/api/search/stats.mjs +6 -6
  178. package/dist/astro/routes/api/search/suggest.mjs +10 -10
  179. package/dist/astro/routes/api/sections/_slug_.mjs +30 -29
  180. package/dist/astro/routes/api/sections/index.mjs +30 -29
  181. package/dist/astro/routes/api/settings/email.mjs +3 -3
  182. package/dist/astro/routes/api/settings.mjs +30 -29
  183. package/dist/astro/routes/api/setup/admin-verify.mjs +12 -12
  184. package/dist/astro/routes/api/setup/admin.mjs +10 -10
  185. package/dist/astro/routes/api/setup/dev-bypass.mjs +25 -25
  186. package/dist/astro/routes/api/setup/dev-reset.mjs +1 -1
  187. package/dist/astro/routes/api/setup/index.mjs +27 -27
  188. package/dist/astro/routes/api/setup/status.mjs +3 -3
  189. package/dist/astro/routes/api/snapshot.mjs +6 -6
  190. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +33 -32
  191. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +33 -32
  192. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +33 -32
  193. package/dist/astro/routes/api/taxonomies/index.mjs +33 -32
  194. package/dist/astro/routes/api/themes/preview.mjs +4 -4
  195. package/dist/astro/routes/api/typegen.mjs +4 -4
  196. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  197. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +3 -3
  198. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +3 -3
  199. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +29 -28
  200. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +30 -29
  201. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +30 -29
  202. package/dist/astro/routes/api/widget-areas/_name_.mjs +27 -26
  203. package/dist/astro/routes/api/widget-areas/index.mjs +30 -29
  204. package/dist/astro/routes/api/widget-components.mjs +2 -2
  205. package/dist/astro/routes/robots.txt.mjs +6 -6
  206. package/dist/astro/routes/sitemap-_collection_.xml.mjs +9 -9
  207. package/dist/astro/routes/sitemap.xml.mjs +7 -7
  208. package/dist/astro/session-driver.d.mts +1 -1
  209. package/dist/astro/session-driver.mjs +2 -2
  210. package/dist/astro/types.d.mts +13 -12
  211. package/dist/{audit-ByVKIwWF.mjs → audit-B2pRxZ20.mjs} +1 -1
  212. package/dist/auth/providers/github.d.mts +1 -1
  213. package/dist/auth/providers/google.d.mts +1 -1
  214. package/dist/{auth-control-guard-CqeUmQsv.mjs → auth-control-guard-D71jourk.mjs} +1 -1
  215. package/dist/{authorize-CcBxcHtz.mjs → authorize-ZXPGPwKn.mjs} +1 -1
  216. package/dist/{briefing-CawZ59CY.mjs → briefing-0rf-su3-.mjs} +7 -7
  217. package/dist/{briefing-BJaJI8dm.mjs → briefing-kRhegBoF.mjs} +1 -1
  218. package/dist/{bundle-7C_DpkEq.mjs → bundle-CEcdUa1s.mjs} +2 -2
  219. package/dist/{byline-e51-fWRs.mjs → byline-7xR185Ly.mjs} +6 -6
  220. package/dist/{byline-fields-D-N5qVIG.mjs → byline-fields-BKzA0fJU.mjs} +1 -1
  221. package/dist/{byline-fields-CUvbslGc.d.mts → byline-fields-Ckeqd1OY.d.mts} +57 -47
  222. package/dist/{byline-fields-DOJO16Gm.mjs → byline-fields-WN7RQT4q.mjs} +10 -4
  223. package/dist/{byline-registry-87nNnYl4.mjs → byline-registry-Ba54M74c.mjs} +2 -2
  224. package/dist/{bylines-CMQNCNFh.mjs → bylines-C-EL6dh3.mjs} +3 -3
  225. package/dist/{bylines-y4LO-dTj.mjs → bylines-CmZFCMr0.mjs} +5 -5
  226. package/dist/{cache-C-FNTFYK.mjs → cache-DkVCKGio.mjs} +2 -2
  227. package/dist/{chunks-CxBt-GxR.mjs → chunks-ChGNDY8r.mjs} +1 -1
  228. package/dist/cli/index.d.mts +29 -1
  229. package/dist/cli/index.mjs +1391 -1123
  230. package/dist/cli/plugin-kit.d.mts +75 -0
  231. package/dist/cli/plugin-kit.mjs +4 -0
  232. package/dist/client/external-auth-headers.d.mts +1 -1
  233. package/dist/client/index.d.mts +27 -52
  234. package/dist/client/index.mjs +34 -7
  235. package/dist/client-factory-BDD9azPJ.mjs +233 -0
  236. package/dist/{comment-DlA4iChg.mjs → comment-DFALgsJD.mjs} +1 -1
  237. package/dist/{comment-reaction-DfFao1ST.mjs → comment-reaction-C37bf99u.mjs} +1 -1
  238. package/dist/{comments-DApFGMsP.mjs → comments-B3NwDBxl.mjs} +1 -1
  239. package/dist/{content-Bh4BDjv8.mjs → content-DJcAYFhz.mjs} +2 -2
  240. package/dist/{content-refresh-CgutSUfO.mjs → content-refresh-B4ZCmxgd.mjs} +5 -5
  241. package/dist/{context-CK4ym2Sf.mjs → context-DnpXcp6Y.mjs} +2 -2
  242. package/dist/{context-C5W3G18y.mjs → context-fSCYLL3c.mjs} +24 -13
  243. package/dist/{context-route-helpers-MASyGgWf.mjs → context-route-helpers-CI8XzcsK.mjs} +2 -2
  244. package/dist/{dashboard-DQkVT7Ei.mjs → dashboard-D0nj5oWg.mjs} +4 -4
  245. package/dist/db/index.d.mts +3 -3
  246. package/dist/db/index.mjs +1 -1
  247. package/dist/db/libsql.d.mts +1 -1
  248. package/dist/db/postgres.d.mts +1 -1
  249. package/dist/db/sqlite.d.mts +1 -1
  250. package/dist/{device-flow-BnqRH-T9.mjs → device-flow-BJe6VEhP.mjs} +5 -5
  251. package/dist/{enrich-DIFIFA8O.mjs → enrich-Ck9tfMqe.mjs} +1 -1
  252. package/dist/{experimental-workflows-BMsUnIAw.mjs → experimental-workflows-GVhUgzEs.mjs} +1 -1
  253. package/dist/{field-defs-cache-DRNdhBJv.mjs → field-defs-cache-DL-T2XVS.mjs} +4 -4
  254. package/dist/forgeway-seo-client-G5ddhVHd.mjs +456 -0
  255. package/dist/{fts-manager-Crzypjqu.mjs → fts-manager-ewTGohf4.mjs} +1 -1
  256. package/dist/{hitl-requests-B4Vbcmaq.mjs → hitl-requests-DEFj8e6u.mjs} +2 -2
  257. package/dist/{hitl-route-helpers-hEVzvP8l.mjs → hitl-route-helpers-C23JHaWz.mjs} +5 -5
  258. package/dist/{import-Bh9biJfx.mjs → import-CXXL5pip.mjs} +7 -7
  259. package/dist/{index-D4y6NGiy.d.mts → index-DcnOCD1e.d.mts} +79 -3
  260. package/dist/index.d.mts +15 -17
  261. package/dist/index.mjs +81 -78
  262. package/dist/{jsonld-Dq0gsnOH.d.mts → jsonld-DzKbhRZ4.d.mts} +2 -2
  263. package/dist/{load-DmYeniXl.mjs → load-CFtJRT3c.mjs} +1 -1
  264. package/dist/{loader-CjWqjweO.mjs → loader-aWkkc1_o.mjs} +2 -2
  265. package/dist/{manifest-schema-BxVPYMJR.mjs → manifest-schema-uA6pRFXi.mjs} +6 -2
  266. package/dist/{mcp-reviewed-executor-TwVbMzG6.mjs → mcp-reviewed-executor-Dn52L_yY.mjs} +6 -3
  267. package/dist/media/image-endpoint.mjs +1 -1
  268. package/dist/media/index.d.mts +1 -1
  269. package/dist/media/index.mjs +2 -2
  270. package/dist/media/local-runtime.d.mts +10 -11
  271. package/dist/media/local-runtime.mjs +2 -2
  272. package/dist/{media-DMj0grzC.mjs → media-CVMOoywq.mjs} +1 -1
  273. package/dist/{media-allowlist-BlVCSzHx.mjs → media-allowlist-zDNJmXCo.mjs} +1 -1
  274. package/dist/{menus-xvsnVZXw.mjs → menus-CCG2z5um.mjs} +6 -6
  275. package/dist/{menus-TaW1gKnk.mjs → menus-CmyIkRG5.mjs} +2 -2
  276. package/dist/{oauth-authorization-DiaGvRVz.mjs → oauth-authorization-B9H7X85L.mjs} +5 -5
  277. package/dist/{oauth-clients-Dl9E8dEM.mjs → oauth-clients-Dwl32-Uj.mjs} +2 -2
  278. package/dist/object-cache/memory.d.mts +1 -1
  279. package/dist/output-DhBe5_Pi.mjs +68 -0
  280. package/dist/page/index.d.mts +9 -3
  281. package/dist/page/index.mjs +1 -1
  282. package/dist/{parse-CbymENaR.mjs → parse-i15EFSAE.mjs} +1 -1
  283. package/dist/plugin-utils.d.mts +8 -9
  284. package/dist/plugins/adapt-sandbox-entry.d.mts +8 -9
  285. package/dist/plugins/adapt-sandbox-entry.mjs +3 -1
  286. package/dist/{plugins-BolVa-tl.mjs → plugins-CPA2PtfY.mjs} +95 -10
  287. package/dist/portable-text-E79LyjGo.d.mts +45 -0
  288. package/dist/{preview-vSVg6fwG.mjs → preview-BjedZm8G.mjs} +2 -2
  289. package/dist/project-env-DKibmuP8.mjs +95 -0
  290. package/dist/{provider-loader-MD8zAbnH.d.mts → provider-loader-DC2JdWeO.d.mts} +1 -1
  291. package/dist/{public-url-gsp3oUbu.d.mts → public-url-DQJCnYi9.d.mts} +1 -1
  292. package/dist/publication-authorization-RKiHXKtq.mjs +985 -0
  293. package/dist/{query-jaAmw8E1.mjs → query-C2U5u5_p.mjs} +3 -3
  294. package/dist/{query-CfRCdaL3.mjs → query-DEv5x6yB.mjs} +13 -13
  295. package/dist/{rate-limit-ZLdyWoZF.mjs → rate-limit-DglsAJSw.mjs} +2 -2
  296. package/dist/{redirect-BAjt4Z6b.mjs → redirect-ubhEqQCQ.mjs} +2 -2
  297. package/dist/{redirects-CA-xylPG.mjs → redirects-Bhpm8bRe.mjs} +18 -10
  298. package/dist/{redirects-BTdwpQtE.mjs → redirects-xs2yahLq.mjs} +4 -4
  299. package/dist/{registry-CtyVedcN.mjs → registry-BYYyb2tF.mjs} +24 -10
  300. package/dist/{request-meta-CUypToDJ.mjs → request-meta-CFe1tW2M.mjs} +1 -1
  301. package/dist/review-payload-BysRWpxS.mjs +111 -0
  302. package/dist/{review-requests-DUyEmtmG.mjs → review-requests-CTWGnWUt.mjs} +1 -1
  303. package/dist/review-requests-D42VEP1l.mjs +142 -0
  304. package/dist/review-route-helpers-C31vO6SI.mjs +41 -0
  305. package/dist/{runner-DB7CRfOF.mjs → runner-BgmttQmD.mjs} +1151 -447
  306. package/dist/{runner-1tC2tIRV.d.mts → runner-D6hzs8L9.d.mts} +1 -1
  307. package/dist/runtime.d.mts +9 -10
  308. package/dist/runtime.mjs +3 -3
  309. package/dist/schema-DUtnZxjZ.mjs +8 -0
  310. package/dist/{search-DSaLgEFI.mjs → search-DUKgZUlm.mjs} +3 -3
  311. package/dist/{secrets-CDZo2k8L.mjs → secrets-ZV5rd8yV.mjs} +1 -1
  312. package/dist/{sections-PVVXHGfp.mjs → sections-DvFgsnda.mjs} +2 -2
  313. package/dist/seed/index.d.mts +2 -2
  314. package/dist/seed/index.mjs +19 -19
  315. package/dist/seo/index.d.mts +1 -1
  316. package/dist/seo/index.mjs +1 -1
  317. package/dist/{seo-CQG3GCHQ.mjs → seo-BU8MLCS9.mjs} +1 -1
  318. package/dist/{service-DiN4ZuPp.mjs → service-FL3fB0Rx.mjs} +2 -2
  319. package/dist/{settings-BbqgHWpU.mjs → settings-BECcAIRH.mjs} +5 -5
  320. package/dist/{settings-i4VPP8bT.mjs → settings-EzADu-dd.mjs} +2 -2
  321. package/dist/{site-activity-Dy3FROs0.mjs → site-activity-h8smOlYI.mjs} +1 -1
  322. package/dist/{site-context-DQiL8Fl5.mjs → site-context-B8T1kpUP.mjs} +565 -129
  323. package/dist/stable-hash-DshcXvRE.mjs +19 -0
  324. package/dist/storage/local.d.mts +1 -1
  325. package/dist/storage/local.mjs +1 -1
  326. package/dist/storage/s3.d.mts +1 -1
  327. package/dist/storage/s3.mjs +1 -1
  328. package/dist/{taxonomies-9s-v03H-.mjs → taxonomies-BiM9NtgW.mjs} +3 -3
  329. package/dist/{taxonomies-DRiixp_E.mjs → taxonomies-DSnY5nOa.mjs} +6 -6
  330. package/dist/{taxonomy-Dvsw6BOs.mjs → taxonomy-0k_vRVsD.mjs} +2 -2
  331. package/dist/{types-4garmdWa.mjs → types-A8fPOJnl.mjs} +5 -2
  332. package/dist/{types-DW_-sFyz.d.mts → types-B_FlxIIl.d.mts} +2 -0
  333. package/dist/{types-Dq1qT19x.mjs → types-ChxYDRvx.mjs} +1 -1
  334. package/dist/{types-DU0bU2Wu.d.mts → types-DuQn1G7-.d.mts} +583 -2
  335. package/dist/{runtime-BOeiQsCv.d.mts → types-DwoUifGE.d.mts} +3560 -1355
  336. package/dist/{types-C_ly5eTp.d.mts → types-opjX_kz6.d.mts} +1 -1
  337. package/dist/ui/server-runtime.d.mts +12 -14
  338. package/dist/ui/server-runtime.mjs +21 -21
  339. package/dist/{user-BIZPzCb9.mjs → user-BH-n01y3.mjs} +2 -2
  340. package/dist/{utils-CMDhr5Xd.mjs → utils-D1SrKGtP.mjs} +1 -1
  341. package/dist/{validate-CZvb-BKB.d.mts → validate-CbVFoJgc.d.mts} +59 -5
  342. package/dist/{validate-DNs0pTSc.mjs → validate-D1HL-oLR.mjs} +2 -2
  343. package/dist/version-C3iVRuaF.mjs +6 -0
  344. package/dist/{widgets-f0ibZUIq.mjs → widgets-BZrLrNgS.mjs} +3 -3
  345. package/dist/{wordpress-slugs-BhHJX8XE.mjs → wordpress-slugs-C3PWcowq.mjs} +1 -1
  346. package/dist/{zod-generator-B7TMjyFJ.mjs → zod-generator-C7AsLpSB.mjs} +2 -2
  347. package/package.json +13 -5
  348. package/dist/hitl-Hw1RmWOq.mjs +0 -677
  349. package/dist/review-requests-C9Ff11Vq.mjs +0 -79
  350. package/dist/schema-DXdYj4R3.mjs +0 -8
  351. package/dist/types-C46EvNdc.d.mts +0 -56
  352. package/dist/types-C8XW6L19.d.mts +0 -1468
  353. package/dist/version-BTvfRGc6.mjs +0 -6
  354. /package/dist/{adapters-NVSs4UH7.d.mts → adapters-BiRymx02.d.mts} +0 -0
  355. /package/dist/{api-tokens-2r7OWi_I.mjs → api-tokens-Ypaly90d.mjs} +0 -0
  356. /package/dist/{challenge-store-Bjlepa2E.mjs → challenge-store-D5pSY2S1.mjs} +0 -0
  357. /package/dist/{components-Bclf98Be.mjs → components-DImzlLhb.mjs} +0 -0
  358. /package/dist/{context-types-DqTo6C79.mjs → context-types-eBuMDAg-.mjs} +0 -0
  359. /package/dist/{cron-cKcNTix_.mjs → cron-B1SL_lsb.mjs} +0 -0
  360. /package/dist/{default--K40jqm0.mjs → default-D4Vygcy_.mjs} +0 -0
  361. /package/dist/{email-console-ChgKFO8-.mjs → email-console-Bp0r68iF.mjs} +0 -0
  362. /package/dist/{error-CytFlgcd.mjs → error-CSVaQNZy.mjs} +0 -0
  363. /package/dist/{escape-Bd4P_xKg.mjs → escape-DN4Qmjsl.mjs} +0 -0
  364. /package/dist/{hash-BPmX6P8I.mjs → hash-mxxx2oPW.mjs} +0 -0
  365. /package/dist/{media-url-lEKSrpr9.mjs → media-url-BdoLPvg8.mjs} +0 -0
  366. /package/dist/{mime-CA7Lgovc.mjs → mime-C0vOHFm7.mjs} +0 -0
  367. /package/dist/{mode-C4CIyjtR.mjs → mode-C8yZnIlR.mjs} +0 -0
  368. /package/dist/{normalize-5ek_z7de.mjs → normalize-Cyt7JdF7.mjs} +0 -0
  369. /package/dist/{oauth-state-store-CNoXafF7.mjs → oauth-state-store-CndT35iw.mjs} +0 -0
  370. /package/dist/{oauth-user-lookup-Ygw4bNyt.mjs → oauth-user-lookup-CMehAaKs.mjs} +0 -0
  371. /package/dist/{parser-C1nYd-81.mjs → parser-06LLLL4E.mjs} +0 -0
  372. /package/dist/{passkey-config-BUwu8BA3.mjs → passkey-config-NYukYGed.mjs} +0 -0
  373. /package/dist/{patterns-BhAvqO0S.mjs → patterns-CoqlXUvk.mjs} +0 -0
  374. /package/dist/{placeholder-uvNRZBX9.d.mts → placeholder-DkWkXfDI.d.mts} +0 -0
  375. /package/dist/{portable-text-dA_UlJwW.mjs → portable-text-BASHceim.mjs} +0 -0
  376. /package/dist/{process-cache-7xgAO9JY.mjs → process-cache-CCDgti7j.mjs} +0 -0
  377. /package/dist/{provider-loader-D0w9dqjm.mjs → provider-loader-BtfrrzfD.mjs} +0 -0
  378. /package/dist/{public-url-BEz_sM9n.mjs → public-url-Cp6hyjVR.mjs} +0 -0
  379. /package/dist/{redirect-wphRBv4y.mjs → redirect-DrbEmU9a.mjs} +0 -0
  380. /package/dist/{request-cache-D9GQBxMP.mjs → request-cache-CBFjCwSt.mjs} +0 -0
  381. /package/dist/{resolve-Cb6Dbz-C.mjs → resolve-DE71WcL9.mjs} +0 -0
  382. /package/dist/{seo-qa7Dn0jK.mjs → seo-BLszyJxd.mjs} +0 -0
  383. /package/dist/{seo-contributions-C1EnikHB.mjs → seo-contributions-GnCuL1ML.mjs} +0 -0
  384. /package/dist/{session-user-CAKImna3.mjs → session-user-Ct6GBLvn.mjs} +0 -0
  385. /package/dist/{setup-complete-B8fl-mjV.mjs → setup-complete-Da0axZEE.mjs} +0 -0
  386. /package/dist/{setup-nonce-CoyjILvW.mjs → setup-nonce-BtkKnvxQ.mjs} +0 -0
  387. /package/dist/{sidecar-client-C3mqXg4C.mjs → sidecar-client-Ctzfyyp-.mjs} +0 -0
  388. /package/dist/{site-url-BML3GQYm.mjs → site-url-DJEid0ml.mjs} +0 -0
  389. /package/dist/{ssrf-DWe8jwAp.mjs → ssrf-FJqwQzj4.mjs} +0 -0
  390. /package/dist/{status-DANd8ERo.mjs → status-NqWOSFAD.mjs} +0 -0
  391. /package/dist/{tokens-1RK1n6ED.mjs → tokens-BDQU29Vd.mjs} +0 -0
  392. /package/dist/{transaction-C2EOMTJA.mjs → transaction-0LEAAy8R.mjs} +0 -0
  393. /package/dist/{transport-BNmCqLu7.mjs → transport-C46KHLMP.mjs} +0 -0
  394. /package/dist/{transport-CcJu3Co8.d.mts → transport-D9pKAzFP.d.mts} +0 -0
  395. /package/dist/{trusted-proxy-DTXQ2IMv.mjs → trusted-proxy-Dm7Mxvt6.mjs} +0 -0
  396. /package/dist/{types-B3t5cxhU.mjs → types-B7TDDFMG.mjs} +0 -0
  397. /package/dist/{types-DF7ZuGGk.d.mts → types-BLQCz46p.d.mts} +0 -0
  398. /package/dist/{types-CDpH6MA4.d.mts → types-Cw0PeCT_.d.mts} +0 -0
  399. /package/dist/{types-Rc3gQqqw.d.mts → types-g0zMtraY.d.mts} +0 -0
  400. /package/dist/{types-C5CVgwsV.mjs → types-mVVgRPaU.mjs} +0 -0
  401. /package/dist/{types-POH8PQZh.d.mts → types-wFu80FrO.d.mts} +0 -0
  402. /package/dist/{url-CpyFDFpO.mjs → url-EMAguGVk.mjs} +0 -0
  403. /package/dist/{url-Cac7a9QZ.mjs → url-guoXQQcV.mjs} +0 -0
@@ -1,150 +1,74 @@
1
1
  #!/usr/bin/env node
2
- import { a as __exportAll, i as runMigrations, n as getMigrationStatus, t as getMigrationFingerprint } from "../runner-DB7CRfOF.mjs";
2
+ import { i as runMigrations, n as getMigrationStatus, t as getMigrationFingerprint } from "../runner-BgmttQmD.mjs";
3
3
  import { t as createDatabase } from "../connection-CiJWiWya.mjs";
4
4
  import { t as validateIdentifier } from "../validate-VPnKoIzW.mjs";
5
5
  import { l as listTablesLike } from "../dialect-helpers-fk43GbDb.mjs";
6
6
  import { n as getI18nConfig, r as isI18nEnabled } from "../config-CVssduLe.mjs";
7
7
  import { i as encodeBase64url } from "../base64-F8-DUraK.mjs";
8
- import "../types-Dq1qT19x.mjs";
9
- import { o as isMissingTableError } from "../loader-CjWqjweO.mjs";
8
+ import "../types-ChxYDRvx.mjs";
9
+ import { o as isMissingTableError } from "../loader-aWkkc1_o.mjs";
10
10
  import "../object-cache-D51qOseS.mjs";
11
11
  import { n as slugify } from "../slugify-KSsVD_D3.mjs";
12
- import { t as ContentRepository } from "../content-Bh4BDjv8.mjs";
13
- import { t as MediaRepository } from "../media-DMj0grzC.mjs";
14
- import { t as TaxonomyRepository } from "../taxonomy-Dvsw6BOs.mjs";
12
+ import { t as ContentRepository } from "../content-DJcAYFhz.mjs";
13
+ import { t as MediaRepository } from "../media-CVMOoywq.mjs";
14
+ import { t as TaxonomyRepository } from "../taxonomy-0k_vRVsD.mjs";
15
15
  import { t as OptionsRepository } from "../options-DQPTH5n0.mjs";
16
- import "../redirect-BAjt4Z6b.mjs";
17
- import "../request-cache-D9GQBxMP.mjs";
18
- import "../byline-registry-87nNnYl4.mjs";
19
- import "../field-defs-cache-DRNdhBJv.mjs";
20
- import { t as BylineRepository } from "../byline-e51-fWRs.mjs";
21
- import "../content-refresh-CgutSUfO.mjs";
22
- import "../fts-manager-Crzypjqu.mjs";
23
- import { n as SchemaRegistry } from "../registry-CtyVedcN.mjs";
24
- import "../settings-BbqgHWpU.mjs";
25
- import { n as VERSION } from "../version-BTvfRGc6.mjs";
26
- import { a as reconcileManifestAccess, i as pluginManifestSchema } from "../manifest-schema-BxVPYMJR.mjs";
27
- import "../ssrf-DWe8jwAp.mjs";
28
- import { n as fingerprintKey, r as generateEncryptionKey, t as DinewaySecretsError } from "../secrets-CDZo2k8L.mjs";
29
- import { t as validateSeed } from "../validate-DNs0pTSc.mjs";
30
- import { t as applySeed } from "../apply-bZswmZPm.mjs";
31
- import { createHeaderAwareFetch, customHeadersInterceptor, isRedirectResponse, resolveCustomHeaders } from "../client/external-auth-headers.mjs";
32
- import { t as convertDataForRead } from "../portable-text-dA_UlJwW.mjs";
33
- import { DinewayClient } from "../client/index.mjs";
16
+ import "../redirect-ubhEqQCQ.mjs";
17
+ import "../request-cache-CBFjCwSt.mjs";
18
+ import "../byline-registry-Ba54M74c.mjs";
19
+ import "../field-defs-cache-DL-T2XVS.mjs";
20
+ import { t as BylineRepository } from "../byline-7xR185Ly.mjs";
21
+ import "../content-refresh-B4ZCmxgd.mjs";
22
+ import "../fts-manager-ewTGohf4.mjs";
23
+ import { n as SchemaRegistry } from "../registry-BYYyb2tF.mjs";
24
+ import "../settings-BECcAIRH.mjs";
25
+ import { n as VERSION } from "../version-C3iVRuaF.mjs";
26
+ import { a as reconcileManifestAccess, i as pluginManifestSchema } from "../manifest-schema-uA6pRFXi.mjs";
27
+ import { n as resolveAndValidateExternalUrl, t as SsrfError } from "../ssrf-FJqwQzj4.mjs";
28
+ import { n as fingerprintKey, r as generateEncryptionKey, t as DinewaySecretsError } from "../secrets-ZV5rd8yV.mjs";
29
+ import { t as validateSeed } from "../validate-D1HL-oLR.mjs";
30
+ import { t as applySeed } from "../apply-DjTqq6Ae.mjs";
31
+ import { a as readSavedDeployTarget, c as listForgewayCredentials, i as readForgewayDeployMetadata, l as readForgewayCredentials, n as SeoProviderEvidenceService, o as writeDeployTarget, r as readDeployPackageJson, s as writeForgewayDeployMetadata, t as ForgewaySeoGatewayClient, u as writeForgewayCredentials } from "../forgeway-seo-client-G5ddhVHd.mjs";
32
+ import { n as output, t as configureOutputMode } from "../output-DhBe5_Pi.mjs";
33
+ import { createHeaderAwareFetch, isRedirectResponse, resolveCustomHeaders } from "../client/external-auth-headers.mjs";
34
+ import { t as convertDataForRead } from "../portable-text-BASHceim.mjs";
35
+ import { a as getMarketplaceCredential, c as resolveCredentialKey, i as getCredentials, l as saveCredentials, n as connectionArgs, o as removeCredentials, r as createClientFromArgs, s as removeMarketplaceCredential, u as saveMarketplaceCredential } from "../client-factory-BDD9azPJ.mjs";
36
+ import { a as resolveDinewayBaseUrl, n as ensureProjectGitignoreDinewayLocalState, o as resolveDinewayToken, r as ensureProjectGitignoreEntry, s as upsertProjectEnv, t as DEFAULT_DINEWAY_URL } from "../project-env-DKibmuP8.mjs";
34
37
  import { LocalStorage } from "../storage/local.mjs";
35
- import { r as fileExists$11, t as bundleCommand } from "../bundle-7C_DpkEq.mjs";
36
- import { createHash, randomUUID } from "node:crypto";
38
+ import { r as fileExists$11, t as bundleCommand } from "../bundle-CEcdUa1s.mjs";
39
+ import { createRequire } from "node:module";
40
+ import { createHash } from "node:crypto";
37
41
  import { sql } from "kysely";
38
42
  import { createGzipDecoder, unpackTar } from "modern-tar";
39
- import { createReadStream, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
43
+ import { createReadStream, existsSync, readFileSync, writeFileSync } from "node:fs";
40
44
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
45
+ import sax from "sax";
46
+ import { pathToFileURL } from "node:url";
41
47
  import { defineCommand, runCommand, runMain } from "citty";
42
48
  import { createInterface } from "node:readline/promises";
43
49
  import consola, { consola as consola$1 } from "consola";
44
50
  import pc from "picocolors";
45
51
  import { ForgewayClient } from "@forgeway/sdk";
46
- import { access, chmod, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
47
- import { homedir } from "node:os";
52
+ import { access, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
48
53
  import { spawn } from "node:child_process";
54
+ import { parse } from "parse5";
49
55
 
50
- //#region src/cli/commands/deploy/utils/forgeway-secrets.ts
51
- const DINEWAY_USER_CONFIG_DIR_NAME = "dineway";
52
- const TRAILING_SLASH_PATTERN$3 = /\/$/;
53
- function getDinewayUserConfigDir() {
54
- return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), DINEWAY_USER_CONFIG_DIR_NAME);
55
- }
56
- function getForgewayAccountSecretPath() {
57
- return join(getDinewayUserConfigDir(), "forgeway.json");
58
- }
59
- function normalizePlatformApiUrl$1(platformApiUrl) {
60
- return platformApiUrl.replace(TRAILING_SLASH_PATTERN$3, "");
61
- }
62
- function normalizeEmail$2(email) {
63
- return email.trim().toLowerCase();
64
- }
65
- function accountKey(platformApiUrl, email) {
66
- return `${normalizePlatformApiUrl$1(platformApiUrl)}#${normalizeEmail$2(email)}`;
67
- }
68
- function normalizeLookup(lookup) {
69
- if (typeof lookup === "string") return { cwd: lookup };
70
- return lookup ?? {};
71
- }
72
- async function readAccountStore() {
73
- try {
74
- const raw = await readFile(getForgewayAccountSecretPath(), "utf-8");
75
- return JSON.parse(raw);
76
- } catch {
77
- return { version: 1 };
78
- }
79
- }
80
- async function writeAccountStore(store) {
81
- await mkdir(getDinewayUserConfigDir(), {
82
- recursive: true,
83
- mode: 448
84
- });
85
- const file = getForgewayAccountSecretPath();
86
- await writeFile(file, JSON.stringify({
87
- ...store,
88
- version: 1
89
- }, null, " ") + "\n", {
90
- encoding: "utf-8",
91
- mode: 384
92
- });
93
- await chmod(file, 384);
94
- }
95
- function accountMatches(credentials, lookup) {
96
- if (lookup.platformApiUrl && normalizePlatformApiUrl$1(credentials.platformApiUrl) !== normalizePlatformApiUrl$1(lookup.platformApiUrl)) return false;
97
- if (lookup.email && normalizeEmail$2(credentials.user?.email ?? "") !== normalizeEmail$2(lookup.email)) return false;
98
- return true;
99
- }
100
- async function readForgewayCredentials(lookup) {
101
- const normalizedLookup = normalizeLookup(lookup);
102
- const store = await readAccountStore();
103
- const accounts = Object.values(store.accounts ?? {}).filter((credentials) => accountMatches(credentials, normalizedLookup));
104
- if (normalizedLookup.platformApiUrl && normalizedLookup.email) return store.accounts?.[accountKey(normalizedLookup.platformApiUrl, normalizedLookup.email)] ?? null;
105
- if (accounts.length === 1) return accounts[0] ?? null;
106
- return null;
107
- }
108
- async function writeForgewayCredentials(credentials, lookup) {
109
- const normalizedLookup = normalizeLookup(lookup);
110
- const platformApiUrl = normalizePlatformApiUrl$1(normalizedLookup.platformApiUrl ?? credentials.platformApiUrl);
111
- const email = normalizeEmail$2(normalizedLookup.email ?? credentials.user?.email ?? "");
112
- if (!email) throw new Error("Forgeway credentials require a user email before they can be stored.");
113
- const key = accountKey(platformApiUrl, email);
114
- const store = await readAccountStore();
115
- store.accounts = {
116
- ...store.accounts,
117
- [key]: {
118
- ...credentials,
119
- platformApiUrl
120
- }
121
- };
122
- store.lastAccountKey = key;
123
- await writeAccountStore(store);
124
- }
125
- async function listForgewayCredentials(platformApiUrl) {
126
- const lookup = platformApiUrl ? { platformApiUrl } : {};
127
- const store = await readAccountStore();
128
- return Object.values(store.accounts ?? {}).filter((credentials) => accountMatches(credentials, lookup));
129
- }
130
-
131
- //#endregion
132
56
  //#region src/cli/forgeway-account.ts
133
- const TRAILING_SLASH_PATTERN$2 = /\/$/;
134
- const SHADOW_EMAIL_PATTERN$2 = /^shadow_[a-f0-9]{16}@dineway\.ai$/i;
57
+ const TRAILING_SLASH_PATTERN$1 = /\/$/;
58
+ const SHADOW_EMAIL_PATTERN$1 = /^shadow_[a-f0-9]{16}@dineway\.ai$/i;
135
59
  const DEFAULT_FORGEWAY_API_BASE_URL = "https://api.dineway.ai";
136
60
  const DEFAULT_DINEWAY_WEB_URL = "https://dineway.ai";
137
61
  function normalizeForgewayApiUrl(value) {
138
- return (value || DEFAULT_FORGEWAY_API_BASE_URL).replace(TRAILING_SLASH_PATTERN$2, "");
62
+ return (value || DEFAULT_FORGEWAY_API_BASE_URL).replace(TRAILING_SLASH_PATTERN$1, "");
139
63
  }
140
64
  function normalizeDinewayWebUrl(value) {
141
- return (value || DEFAULT_DINEWAY_WEB_URL).replace(TRAILING_SLASH_PATTERN$2, "");
65
+ return (value || DEFAULT_DINEWAY_WEB_URL).replace(TRAILING_SLASH_PATTERN$1, "");
142
66
  }
143
67
  function normalizeForgewayEmail(value) {
144
68
  return value?.trim().toLowerCase() ?? "";
145
69
  }
146
70
  function isShadowForgewayEmail(email) {
147
- return Boolean(email && SHADOW_EMAIL_PATTERN$2.test(email));
71
+ return Boolean(email && SHADOW_EMAIL_PATTERN$1.test(email));
148
72
  }
149
73
  function createForgewayAccountClient(platformApiUrl, accessToken) {
150
74
  return new ForgewayClient({
@@ -154,7 +78,7 @@ function createForgewayAccountClient(platformApiUrl, accessToken) {
154
78
  auth: { detectOAuthCallback: false }
155
79
  });
156
80
  }
157
- function normalizeForgewayUser$2(user) {
81
+ function normalizeForgewayUser$1(user) {
158
82
  if (!user?.id || !user.email) throw new Error("Forgeway session response did not include a user email.");
159
83
  return {
160
84
  id: user.id,
@@ -192,7 +116,7 @@ async function refreshForgewayAccountCredentials(credentials) {
192
116
  if (!credentials.refreshToken) throw new Error("Stored Forgeway account is missing a refresh token. Run `dineway account login`.");
193
117
  const { data, error } = await createForgewayAccountClient(credentials.platformApiUrl).auth.refreshSession({ refreshToken: credentials.refreshToken });
194
118
  if (error || !data?.accessToken) throw new Error(error?.message || "Failed to refresh Forgeway account. Run `dineway account login`.");
195
- const refreshedUser = normalizeForgewayUser$2(data.user);
119
+ const refreshedUser = normalizeForgewayUser$1(data.user);
196
120
  if (normalizeForgewayEmail(refreshedUser.email) !== normalizeForgewayEmail(user.email)) throw new Error("Forgeway refresh returned credentials for a different account.");
197
121
  const updated = {
198
122
  ...credentials,
@@ -224,7 +148,7 @@ async function completeForgewayAccountLogin(input) {
224
148
  const platformApiUrl = normalizeForgewayApiUrl(input.platformApiUrl);
225
149
  const { data, error } = await createForgewayAccountClient(platformApiUrl).auth.signInWithPasswordlessToken({ token: input.token });
226
150
  if (error || !data?.accessToken || !data.refreshToken) throw new Error(error?.message || "Forgeway sign-in token is invalid or expired.");
227
- const user = normalizeForgewayUser$2(data.user);
151
+ const user = normalizeForgewayUser$1(data.user);
228
152
  if (input.email && normalizeForgewayEmail(user.email) !== normalizeForgewayEmail(input.email)) throw new Error("Forgeway sign-in returned credentials for a different email.");
229
153
  const credentials = {
230
154
  platformApiUrl,
@@ -251,71 +175,6 @@ async function getForgewayDinewayAccount(credentials) {
251
175
  };
252
176
  }
253
177
 
254
- //#endregion
255
- //#region src/cli/output.ts
256
- /**
257
- * Redirect consola output to stderr so it doesn't pollute JSON on stdout.
258
- *
259
- * Call this early in any command that uses `output()` with `--json`.
260
- * Safe to call multiple times — only applies the redirect once.
261
- */
262
- function configureOutputMode(args) {
263
- if (args.json || !process.stdout.isTTY) {
264
- consola$1.options.stdout = process.stderr;
265
- consola$1.options.stderr = process.stderr;
266
- }
267
- }
268
- /**
269
- * Output data as JSON or pretty-printed.
270
- *
271
- * If stdout is not a TTY or --json is set, outputs JSON.
272
- * Otherwise, outputs a formatted representation.
273
- */
274
- function output(data, args) {
275
- if (args.json || !process.stdout.isTTY) process.stdout.write(JSON.stringify(data, null, 2) + "\n");
276
- else prettyPrint(data);
277
- }
278
- function prettyPrint(data, indent = 0) {
279
- if (data === null || data === void 0) {
280
- consola$1.log("(empty)");
281
- return;
282
- }
283
- if (Array.isArray(data)) {
284
- if (data.length === 0) {
285
- consola$1.log("(no items)");
286
- return;
287
- }
288
- for (const item of data) {
289
- prettyPrint(item, indent);
290
- if (indent === 0) consola$1.log("---");
291
- }
292
- return;
293
- }
294
- if (typeof data === "object") {
295
- const obj = Object(data);
296
- if ("items" in obj && Array.isArray(obj.items)) {
297
- prettyPrint(obj.items, indent);
298
- if (typeof obj.nextCursor === "string") consola$1.log(`\nNext cursor: ${obj.nextCursor}`);
299
- return;
300
- }
301
- const prefix = " ".repeat(indent);
302
- for (const [key, value] of Object.entries(obj)) {
303
- if (value === null || value === void 0) continue;
304
- if (typeof value === "object" && !Array.isArray(value)) {
305
- consola$1.log(`${prefix}${key}:`);
306
- prettyPrint(value, indent + 1);
307
- } else if (Array.isArray(value)) consola$1.log(`${prefix}${key}: [${value.length} items]`);
308
- else {
309
- const str = typeof value === "string" ? value : JSON.stringify(value);
310
- const display = str.length > 80 ? str.slice(0, 77) + "..." : str;
311
- consola$1.log(`${prefix}${key}: ${display}`);
312
- }
313
- }
314
- return;
315
- }
316
- consola$1.log(typeof data === "string" ? data : JSON.stringify(data));
317
- }
318
-
319
178
  //#endregion
320
179
  //#region src/cli/commands/account.ts
321
180
  async function promptLine(question) {
@@ -504,320 +363,6 @@ const accountCommand = defineCommand({
504
363
  }
505
364
  });
506
365
 
507
- //#endregion
508
- //#region src/cli/credentials.ts
509
- /**
510
- * Credential storage for CLI auth tokens.
511
- *
512
- * Stores OAuth tokens in ~/.config/dineway/auth.json.
513
- * Remote URLs are keyed by origin, local dev by project path.
514
- */
515
- function getConfigDir() {
516
- const xdg = process.env["XDG_CONFIG_HOME"];
517
- if (xdg) return join(xdg, "dineway");
518
- return join(homedir(), ".config", "dineway");
519
- }
520
- function getCredentialPath() {
521
- return join(getConfigDir(), "auth.json");
522
- }
523
- /**
524
- * Resolve the credential key for a given URL.
525
- *
526
- * Remote URLs are keyed by origin (e.g. "https://my-site.pages.dev").
527
- * Local dev instances are keyed by project path (e.g. "path:/Users/matt/sites/blog").
528
- */
529
- function resolveCredentialKey(baseUrl) {
530
- try {
531
- const url = new URL(baseUrl);
532
- if (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]") {
533
- const projectPath = findProjectRoot(process.cwd());
534
- if (projectPath) return `path:${projectPath}`;
535
- return url.origin;
536
- }
537
- return url.origin;
538
- } catch {
539
- return baseUrl;
540
- }
541
- }
542
- /**
543
- * Walk up from cwd to find the project root (directory containing astro.config.*).
544
- */
545
- function findProjectRoot(from) {
546
- let dir = resolve(from);
547
- const root = resolve("/");
548
- while (dir !== root) {
549
- for (const name of [
550
- "astro.config.ts",
551
- "astro.config.mts",
552
- "astro.config.js",
553
- "astro.config.mjs"
554
- ]) if (existsSync(join(dir, name))) return dir;
555
- const parent = resolve(dir, "..");
556
- if (parent === dir) break;
557
- dir = parent;
558
- }
559
- return null;
560
- }
561
- function readStore() {
562
- const path = getCredentialPath();
563
- try {
564
- if (existsSync(path)) {
565
- const content = readFileSync(path, "utf-8");
566
- return JSON.parse(content);
567
- }
568
- } catch {}
569
- return {};
570
- }
571
- function writeStore(store) {
572
- mkdirSync(getConfigDir(), {
573
- recursive: true,
574
- mode: 448
575
- });
576
- writeFileSync(getCredentialPath(), JSON.stringify(store, null, " "), {
577
- encoding: "utf-8",
578
- mode: 384
579
- });
580
- }
581
- /**
582
- * Get stored credentials for a URL.
583
- */
584
- function getCredentials(baseUrl) {
585
- const key = resolveCredentialKey(baseUrl);
586
- const cred = readStore()[key];
587
- if (!cred || !("accessToken" in cred)) return null;
588
- return cred;
589
- }
590
- /**
591
- * Save credentials for a URL.
592
- */
593
- function saveCredentials(baseUrl, cred) {
594
- const key = resolveCredentialKey(baseUrl);
595
- const store = readStore();
596
- store[key] = cred;
597
- writeStore(store);
598
- }
599
- /**
600
- * Remove credentials for a URL.
601
- */
602
- function removeCredentials(baseUrl) {
603
- const key = resolveCredentialKey(baseUrl);
604
- const store = readStore();
605
- if (key in store) {
606
- delete store[key];
607
- writeStore(store);
608
- return true;
609
- }
610
- return false;
611
- }
612
- function marketplaceKey(registryUrl) {
613
- try {
614
- return `marketplace:${new URL(registryUrl).origin}`;
615
- } catch {
616
- return `marketplace:${registryUrl}`;
617
- }
618
- }
619
- /**
620
- * Get stored marketplace credential for a registry URL.
621
- */
622
- function getMarketplaceCredential(registryUrl) {
623
- const key = marketplaceKey(registryUrl);
624
- const cred = readStore()[key];
625
- if (!cred || !("token" in cred)) return null;
626
- if (new Date(cred.expiresAt) < /* @__PURE__ */ new Date()) return null;
627
- return cred;
628
- }
629
- /**
630
- * Save marketplace credential for a registry URL.
631
- */
632
- function saveMarketplaceCredential(registryUrl, cred) {
633
- const key = marketplaceKey(registryUrl);
634
- const store = readStore();
635
- store[key] = cred;
636
- writeStore(store);
637
- }
638
- /**
639
- * Remove marketplace credential for a registry URL.
640
- */
641
- function removeMarketplaceCredential(registryUrl) {
642
- const key = marketplaceKey(registryUrl);
643
- const store = readStore();
644
- if (key in store) {
645
- delete store[key];
646
- writeStore(store);
647
- return true;
648
- }
649
- return false;
650
- }
651
-
652
- //#endregion
653
- //#region src/cli/project-env.ts
654
- const DEFAULT_DINEWAY_URL = "http://localhost:4321";
655
- const ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
656
- const LINE_SPLIT_PATTERN = /\r?\n/;
657
- const DINEWAY_LOCAL_STATE_GITIGNORE_COMMENT = "# Dineway local state";
658
- const DINEWAY_LOCAL_STATE_GITIGNORE_ENTRIES = [
659
- ".dineway/deploy.json",
660
- ".dineway/forgeway.json",
661
- ".dineway/types.ts",
662
- ".dineway/schema.json",
663
- ".dineway/uploads/"
664
- ];
665
- const NEWLINE_PATTERN = /\r?\n/g;
666
- function parseDotenvValue(value) {
667
- const trimmed = value.trim();
668
- if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1);
669
- return trimmed;
670
- }
671
- function parseDotenv(text) {
672
- const env = {};
673
- for (const line of text.split(LINE_SPLIT_PATTERN)) {
674
- const trimmed = line.trim();
675
- if (!trimmed || trimmed.startsWith("#")) continue;
676
- const normalized = trimmed.startsWith("export ") ? trimmed.slice(7).trim() : trimmed;
677
- const equalsIndex = normalized.indexOf("=");
678
- if (equalsIndex <= 0) continue;
679
- const key = normalized.slice(0, equalsIndex).trim();
680
- if (!ENV_KEY_PATTERN.test(key)) continue;
681
- env[key] = parseDotenvValue(normalized.slice(equalsIndex + 1));
682
- }
683
- return env;
684
- }
685
- function readProjectEnv(cwd = process.cwd()) {
686
- const envPath = join(cwd, ".env");
687
- if (!existsSync(envPath)) return {};
688
- return parseDotenv(readFileSync(envPath, "utf-8"));
689
- }
690
- function getProjectEnvValue(name, cwd = process.cwd()) {
691
- return process.env[name] ?? readProjectEnv(cwd)[name];
692
- }
693
- function resolveDinewayBaseUrl(url, cwd = process.cwd()) {
694
- if (url && url !== DEFAULT_DINEWAY_URL) return url;
695
- const projectEnv = readProjectEnv(cwd);
696
- return process.env["DINEWAY_URL"] ?? process.env["DINEWAY_SITE_URL"] ?? projectEnv["DINEWAY_SITE_URL"] ?? projectEnv["DINEWAY_URL"] ?? url ?? DEFAULT_DINEWAY_URL;
697
- }
698
- function resolveDinewayToken(token, cwd = process.cwd()) {
699
- return token ?? getProjectEnvValue("DINEWAY_TOKEN", cwd);
700
- }
701
- function formatEnvValue(value) {
702
- return value.replace(NEWLINE_PATTERN, "");
703
- }
704
- async function upsertProjectEnv(cwd, values) {
705
- const envPath = join(cwd, ".env");
706
- const existing = await readFile(envPath, "utf-8").catch(() => "");
707
- const lines = existing ? existing.split(LINE_SPLIT_PATTERN) : [];
708
- const seen = /* @__PURE__ */ new Set();
709
- const nextLines = lines.map((line) => {
710
- const trimmed = line.trim();
711
- const normalized = trimmed.startsWith("export ") ? trimmed.slice(7).trim() : trimmed;
712
- const equalsIndex = normalized.indexOf("=");
713
- if (equalsIndex <= 0) return line;
714
- const key = normalized.slice(0, equalsIndex).trim();
715
- const value = values[key];
716
- if (!(key in values) || value === void 0) return line;
717
- seen.add(key);
718
- return `${key}=${formatEnvValue(value)}`;
719
- });
720
- for (const [key, value] of Object.entries(values)) {
721
- if (value === void 0 || seen.has(key)) continue;
722
- nextLines.push(`${key}=${formatEnvValue(value)}`);
723
- }
724
- await writeFile(envPath, `${nextLines.filter((line, index) => line || index < nextLines.length - 1).join("\n")}\n`, "utf-8");
725
- }
726
- async function ensureProjectGitignoreEntry(cwd, entry) {
727
- const gitignorePath = join(cwd, ".gitignore");
728
- const existing = await readFile(gitignorePath, "utf-8").catch(() => "");
729
- if (existing.split(LINE_SPLIT_PATTERN).map((line) => line.trim()).includes(entry)) return;
730
- await writeFile(gitignorePath, `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}${entry}\n`, "utf-8");
731
- }
732
- async function ensureProjectGitignoreDinewayLocalState(cwd) {
733
- const gitignorePath = join(cwd, ".gitignore");
734
- const existing = await readFile(gitignorePath, "utf-8").catch(() => "");
735
- const lines = new Set(existing.split(LINE_SPLIT_PATTERN).map((line) => line.trim()));
736
- const missingEntries = DINEWAY_LOCAL_STATE_GITIGNORE_ENTRIES.filter((entry) => !lines.has(entry));
737
- if (missingEntries.length === 0) return;
738
- const block = [DINEWAY_LOCAL_STATE_GITIGNORE_COMMENT, ...missingEntries].join("\n");
739
- await writeFile(gitignorePath, `${existing}${existing.trim() ? existing.endsWith("\n") ? "\n" : "\n\n" : ""}${block}\n`, "utf-8");
740
- }
741
-
742
- //#endregion
743
- //#region src/cli/client-factory.ts
744
- var client_factory_exports = /* @__PURE__ */ __exportAll({
745
- connectionArgs: () => connectionArgs,
746
- createClientFromArgs: () => createClientFromArgs
747
- });
748
- /**
749
- * Shared connection args for all CLI commands that talk to a Dineway instance.
750
- * Spread into each command's `args` definition.
751
- */
752
- const connectionArgs = {
753
- url: {
754
- type: "string",
755
- alias: "u",
756
- description: "Dineway instance URL",
757
- default: DEFAULT_DINEWAY_URL
758
- },
759
- token: {
760
- type: "string",
761
- alias: "t",
762
- description: "Auth token"
763
- },
764
- header: {
765
- type: "string",
766
- alias: "H",
767
- description: "Custom header \"Name: Value\" (repeatable, or use DINEWAY_HEADERS env)"
768
- },
769
- json: {
770
- type: "boolean",
771
- description: "Output as JSON"
772
- }
773
- };
774
- /**
775
- * Create a DinewayClient from CLI args, env vars, and stored credentials.
776
- *
777
- * Auth resolution order:
778
- * 1. --token flag
779
- * 2. DINEWAY_TOKEN env var
780
- * 3. Stored credentials (~/.config/dineway/auth.json)
781
- * 4. Dev bypass (if URL is localhost)
782
- *
783
- * Custom headers are merged from (in priority order):
784
- * 1. Stored credentials (persisted during `dineway login --header`)
785
- * 2. DINEWAY_HEADERS env var
786
- * 3. --header CLI flags
787
- */
788
- function createClientFromArgs(args) {
789
- const baseUrl = resolveDinewayBaseUrl(args.url);
790
- let token = resolveDinewayToken(args.token);
791
- const isLocal = baseUrl.includes("localhost") || baseUrl.includes("127.0.0.1");
792
- const cred = !token ? getCredentials(baseUrl) : null;
793
- const customHeaders = {
794
- ...cred?.customHeaders,
795
- ...resolveCustomHeaders()
796
- };
797
- const extraInterceptors = [];
798
- if (Object.keys(customHeaders).length > 0) extraInterceptors.push(customHeadersInterceptor(customHeaders));
799
- if (!token && cred) if (new Date(cred.expiresAt) > /* @__PURE__ */ new Date()) token = cred.accessToken;
800
- else return new DinewayClient({
801
- baseUrl,
802
- token: cred.accessToken,
803
- refreshToken: cred.refreshToken,
804
- onTokenRefresh: (newAccessToken, expiresIn) => {
805
- saveCredentials(baseUrl, {
806
- ...cred,
807
- accessToken: newAccessToken,
808
- expiresAt: new Date(Date.now() + expiresIn * 1e3).toISOString()
809
- });
810
- },
811
- interceptors: extraInterceptors
812
- });
813
- return new DinewayClient({
814
- baseUrl,
815
- token,
816
- devBypass: !token && isLocal,
817
- interceptors: extraInterceptors
818
- });
819
- }
820
-
821
366
  //#endregion
822
367
  //#region src/cli/commands/auth.ts
823
368
  /**
@@ -1064,7 +609,7 @@ const createCommand$1 = defineCommand({
1064
609
  },
1065
610
  draft: {
1066
611
  type: "boolean",
1067
- description: "Keep as draft instead of auto-publishing"
612
+ description: "Explicitly create as Draft (the only supported creation mode)"
1068
613
  },
1069
614
  ...connectionArgs
1070
615
  },
@@ -1079,7 +624,6 @@ const createCommand$1 = defineCommand({
1079
624
  locale: args.locale,
1080
625
  translationOf: args["translation-of"]
1081
626
  });
1082
- if (!args.draft) await client.publish(args.collection, item.id);
1083
627
  output(await client.get(args.collection, item.id), args);
1084
628
  } catch (error) {
1085
629
  consola$1.error(error instanceof Error ? error.message : "Unknown error");
@@ -1122,7 +666,7 @@ const updateCommand = defineCommand({
1122
666
  },
1123
667
  draft: {
1124
668
  type: "boolean",
1125
- description: "Keep as draft instead of auto-publishing"
669
+ description: "Explicitly update the Draft (the only supported update mode)"
1126
670
  },
1127
671
  ...connectionArgs
1128
672
  },
@@ -1136,7 +680,6 @@ const updateCommand = defineCommand({
1136
680
  _rev: args.rev,
1137
681
  locale: args.locale
1138
682
  });
1139
- if (!args.draft && updated.draftRevisionId) await client.publish(args.collection, updated.id);
1140
683
  output(await client.get(args.collection, updated.id), args);
1141
684
  } catch (error) {
1142
685
  consola$1.error(error instanceof Error ? error.message : "Unknown error");
@@ -1190,12 +733,25 @@ const publishCommand$1 = defineCommand({
1190
733
  description: "Content item ID or slug",
1191
734
  required: true
1192
735
  },
736
+ "review-request": {
737
+ type: "string",
738
+ description: "Approved editorial Review Request ID for the exact Draft",
739
+ required: true
740
+ },
741
+ "release-authorization": {
742
+ type: "string",
743
+ description: "Approved release authorization ID for this token action",
744
+ required: true
745
+ },
1193
746
  ...connectionArgs
1194
747
  },
1195
748
  async run({ args }) {
1196
749
  configureOutputMode(args);
1197
750
  try {
1198
- await createClientFromArgs(args).publish(args.collection, args.id);
751
+ await createClientFromArgs(args).publish(args.collection, args.id, {
752
+ reviewRequestId: args["review-request"],
753
+ releaseAuthorizationId: args["release-authorization"]
754
+ });
1199
755
  output({ success: true }, args);
1200
756
  consola$1.success(`Published ${args.collection}/${args.id}`);
1201
757
  } catch (error) {
@@ -1255,12 +811,26 @@ const scheduleCommand = defineCommand({
1255
811
  description: "ISO 8601 datetime to publish at",
1256
812
  required: true
1257
813
  },
814
+ "review-request": {
815
+ type: "string",
816
+ description: "Approved editorial Review Request ID for the exact Draft",
817
+ required: true
818
+ },
819
+ "release-authorization": {
820
+ type: "string",
821
+ description: "Approved release authorization ID for this token action",
822
+ required: true
823
+ },
1258
824
  ...connectionArgs
1259
825
  },
1260
826
  async run({ args }) {
1261
827
  configureOutputMode(args);
1262
828
  try {
1263
- await createClientFromArgs(args).schedule(args.collection, args.id, { at: args.at });
829
+ await createClientFromArgs(args).schedule(args.collection, args.id, {
830
+ at: args.at,
831
+ reviewRequestId: args["review-request"],
832
+ releaseAuthorizationId: args["release-authorization"]
833
+ });
1264
834
  output({ success: true }, args);
1265
835
  consola$1.success(`Scheduled ${args.collection}/${args.id} for ${args.at}`);
1266
836
  } catch (error) {
@@ -1349,7 +919,7 @@ const contentCommand = defineCommand({
1349
919
  //#endregion
1350
920
  //#region src/cli/paywall.ts
1351
921
  const PAYWALL_MESSAGE_PATTERN = /\b(quota|credit|credits|upgrade|plan|billing|subscription|slot)\b/i;
1352
- function isRecord$1(value) {
922
+ function isRecord$2(value) {
1353
923
  return typeof value === "object" && value !== null;
1354
924
  }
1355
925
  function stringField(record, key) {
@@ -1357,9 +927,9 @@ function stringField(record, key) {
1357
927
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
1358
928
  }
1359
929
  function getNestedError(value) {
1360
- if (!isRecord$1(value)) return null;
930
+ if (!isRecord$2(value)) return null;
1361
931
  const nested = value.error;
1362
- return isRecord$1(nested) ? nested : value;
932
+ return isRecord$2(nested) ? nested : value;
1363
933
  }
1364
934
  function isHostedSitesResource(resource) {
1365
935
  const normalized = resource.toLowerCase();
@@ -1927,7 +1497,7 @@ const FLY_HOST_RE = /\b[a-z0-9][a-z0-9-]*\.fly\.dev\b/;
1927
1497
  const INVALID_FLY_APP_CHARS_RE = /[^a-z0-9-]+/g;
1928
1498
  const REPEATED_HYPHENS_RE$2 = /-+/g;
1929
1499
  const EDGE_HYPHENS_RE$2 = /^-|-$/g;
1930
- const LINE_SPLIT_RE = /\r?\n/;
1500
+ const LINE_SPLIT_RE$1 = /\r?\n/;
1931
1501
  const DEFAULT_REGION$1 = "iad";
1932
1502
  const FLY_VOLUME_NAME = "dineway_data";
1933
1503
  async function fileExists$8(path) {
@@ -2019,7 +1589,7 @@ primary_region = "${region}"
2019
1589
  function parseFlyUrl(stdout) {
2020
1590
  const direct = stdout.match(FLY_URL_RE);
2021
1591
  if (direct) return direct[0];
2022
- for (const line of stdout.split(LINE_SPLIT_RE).filter(Boolean)) try {
1592
+ for (const line of stdout.split(LINE_SPLIT_RE$1).filter(Boolean)) try {
2023
1593
  const url = findFlyUrl(JSON.parse(line));
2024
1594
  if (url) return url;
2025
1595
  } catch {
@@ -2129,69 +1699,9 @@ const flyTarget = {
2129
1699
  }
2130
1700
  };
2131
1701
 
2132
- //#endregion
2133
- //#region src/cli/commands/deploy/utils/config.ts
2134
- const DINEWAY_DIR_NAME = ".dineway";
2135
- const DEPLOY_STATE_FILE_NAME = "deploy.json";
2136
- function deployStatePath(cwd) {
2137
- return join(cwd, DINEWAY_DIR_NAME, DEPLOY_STATE_FILE_NAME);
2138
- }
2139
- function emptyDeployState() {
2140
- return { version: 1 };
2141
- }
2142
- async function readDeployPackageJson(cwd) {
2143
- const content = await readFile(join(cwd, "package.json"), "utf-8");
2144
- return JSON.parse(content);
2145
- }
2146
- async function readDeployState(cwd) {
2147
- try {
2148
- const content = await readFile(deployStatePath(cwd), "utf-8");
2149
- return {
2150
- ...emptyDeployState(),
2151
- ...JSON.parse(content),
2152
- version: 1
2153
- };
2154
- } catch (error) {
2155
- if (error && typeof error === "object" && error.code === "ENOENT") return emptyDeployState();
2156
- throw error;
2157
- }
2158
- }
2159
- async function writeDeployState(cwd, state) {
2160
- await mkdir(join(cwd, DINEWAY_DIR_NAME), { recursive: true });
2161
- await writeFile(deployStatePath(cwd), JSON.stringify({
2162
- ...state,
2163
- version: 1
2164
- }, null, " ") + "\n", "utf-8");
2165
- }
2166
- async function readSavedDeployTarget(cwd) {
2167
- return (await readDeployState(cwd)).target;
2168
- }
2169
- async function writeDeployTarget(cwd, target) {
2170
- await writeDeployState(cwd, {
2171
- ...await readDeployState(cwd),
2172
- target
2173
- });
2174
- }
2175
- async function readForgewayDeployMetadata(cwd) {
2176
- return (await readDeployState(cwd)).targets?.forgeway ?? {};
2177
- }
2178
- async function writeForgewayDeployMetadata(cwd, metadata) {
2179
- const state = await readDeployState(cwd);
2180
- await writeDeployState(cwd, {
2181
- ...state,
2182
- targets: {
2183
- ...state.targets,
2184
- forgeway: {
2185
- ...state.targets?.forgeway,
2186
- ...metadata
2187
- }
2188
- }
2189
- });
2190
- }
2191
-
2192
1702
  //#endregion
2193
1703
  //#region src/cli/commands/deploy/targets/forgeway.ts
2194
- const DEFAULT_DINEWAY_API_BASE_URL$1 = "https://api.dineway.ai";
1704
+ const DEFAULT_DINEWAY_API_BASE_URL = "https://api.dineway.ai";
2195
1705
  const DATABASE_ENV_VAR_NAMES = ["DINEWAY_DATABASE_URL", "DINEWAY_DATABASE_AUTH_TOKEN"];
2196
1706
  const POLL_INTERVAL_MS$1 = 5e3;
2197
1707
  const POLL_TIMEOUT_MS$1 = 3e5;
@@ -2206,12 +1716,12 @@ const NON_COMPARE_PATTERN = /[^\p{Letter}\p{Number}]+/gu;
2206
1716
  const LOCATION_PARTS_PATTERN = /[,;|/]+|\s+-\s+/u;
2207
1717
  const JWT_PATTERN = /[A-Za-z0-9_-]{32,}\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g;
2208
1718
  const TOKEN_ASSIGNMENT_PATTERN = /(DINEWAY_DATABASE_AUTH_TOKEN|DATABASE_AUTH_TOKEN|AUTH_TOKEN)=\S+/gi;
2209
- const TRAILING_SLASH_PATTERN$1 = /\/$/;
1719
+ const TRAILING_SLASH_PATTERN = /\/$/;
2210
1720
  const BACKSLASH_PATTERN = /\\/g;
2211
1721
  const DINEWAY_PACKAGE_NAME = "dineway";
2212
1722
  const SEMVER_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:[-+].*)?$/;
2213
1723
  const LOCAL_PACKAGE_SPEC_PATTERN = /^(?:workspace|file|link|portal):/;
2214
- const SHADOW_EMAIL_PATTERN$1 = /^shadow_[a-f0-9]{16}@dineway\.ai$/i;
1724
+ const SHADOW_EMAIL_PATTERN = /^shadow_[a-f0-9]{16}@dineway\.ai$/i;
2215
1725
  const FOOD_TYPES = new Set([
2216
1726
  "restaurant",
2217
1727
  "cafe",
@@ -2259,7 +1769,7 @@ var ForgewayApiError = class extends Error {
2259
1769
  this.name = "ForgewayApiError";
2260
1770
  }
2261
1771
  };
2262
- function normalizeForgewayUser$1(user) {
1772
+ function normalizeForgewayUser(user) {
2263
1773
  return {
2264
1774
  id: user.id,
2265
1775
  name: user.profile?.name ?? user.name ?? user.email,
@@ -2280,7 +1790,7 @@ async function fileExists$7(path) {
2280
1790
  }
2281
1791
  }
2282
1792
  function normalizePlatformApiUrl(value) {
2283
- return (value || DEFAULT_DINEWAY_API_BASE_URL$1).replace(TRAILING_SLASH_PATTERN$1, "");
1793
+ return (value || DEFAULT_DINEWAY_API_BASE_URL).replace(TRAILING_SLASH_PATTERN, "");
2284
1794
  }
2285
1795
  function resolvePlatformApiUrl(savedPlatformApiUrl) {
2286
1796
  return normalizePlatformApiUrl(getEnv("DINEWAY_API_BASE_URL") ?? (typeof savedPlatformApiUrl === "string" ? savedPlatformApiUrl : void 0));
@@ -2343,11 +1853,11 @@ async function parseErrorResponse(response) {
2343
1853
  }
2344
1854
  return `Forgeway request failed: ${response.status}`;
2345
1855
  }
2346
- function normalizeEmail$1(email) {
1856
+ function normalizeEmail(email) {
2347
1857
  return email.trim().toLowerCase();
2348
1858
  }
2349
- function isShadowEmail$1(email) {
2350
- return Boolean(email && SHADOW_EMAIL_PATTERN$1.test(email));
1859
+ function isShadowEmail(email) {
1860
+ return Boolean(email && SHADOW_EMAIL_PATTERN.test(email));
2351
1861
  }
2352
1862
  async function refreshFormalAccessToken(context, deps) {
2353
1863
  if (!context.refreshToken) throw new Error("Forgeway formal account refresh token is missing. Run dineway deploy again.");
@@ -2359,7 +1869,7 @@ async function refreshFormalAccessToken(context, deps) {
2359
1869
  if (!response.ok) throw new Error("Failed to refresh Forgeway formal account credentials. Run dineway deploy again.");
2360
1870
  const payload = await response.json();
2361
1871
  if (!payload.accessToken) throw new Error("Forgeway refresh response was missing accessToken.");
2362
- const user = assertVerifiedFormalAccountUser(payload.user ? normalizeForgewayUser$1(payload.user) : context.user, context.user?.email);
1872
+ const user = assertVerifiedFormalAccountUser(payload.user ? normalizeForgewayUser(payload.user) : context.user, context.user?.email);
2363
1873
  const updated = {
2364
1874
  platformApiUrl: context.platformApiUrl,
2365
1875
  accessToken: payload.accessToken,
@@ -2406,7 +1916,7 @@ function getDependencySpec(pkg, name) {
2406
1916
  if (typeof value === "string" && value.trim()) return value.trim();
2407
1917
  }
2408
1918
  }
2409
- async function readProjectPackage(cwd) {
1919
+ async function readProjectPackage$1(cwd) {
2410
1920
  try {
2411
1921
  return await readDeployPackageJson(cwd);
2412
1922
  } catch {
@@ -2420,7 +1930,7 @@ function isDinewayDependencySpecCompatible(dependencySpec, cliVersion) {
2420
1930
  }
2421
1931
  async function warnProjectDinewayRuntimeMismatch(cwd, cliVersion = VERSION) {
2422
1932
  if (!SEMVER_VERSION_PATTERN.test(cliVersion)) return;
2423
- const pkg = await readProjectPackage(cwd);
1933
+ const pkg = await readProjectPackage$1(cwd);
2424
1934
  if (!pkg) return;
2425
1935
  const dependencySpec = getDependencySpec(pkg, DINEWAY_PACKAGE_NAME);
2426
1936
  if (!dependencySpec) return;
@@ -2434,20 +1944,20 @@ function savedPublisherEmail(saved) {
2434
1944
  return typeof saved.publisherEmail === "string" && saved.publisherEmail.trim() ? saved.publisherEmail : void 0;
2435
1945
  }
2436
1946
  function validatePublisherEmail(rawEmail) {
2437
- const email = normalizeEmail$1(rawEmail);
2438
- if (!email || isShadowEmail$1(email)) throw new Error("Dineway account email must be a real owner email, not a shadow user email.");
1947
+ const email = normalizeEmail(rawEmail);
1948
+ if (!email || isShadowEmail(email)) throw new Error("Dineway account email must be a real owner email, not a shadow user email.");
2439
1949
  return email;
2440
1950
  }
2441
1951
  function isStoredFormalCredentialForEmail(stored, email) {
2442
1952
  if (!stored?.accessToken || !stored.user) return false;
2443
- if (email && normalizeEmail$1(stored.user.email) !== email) return false;
1953
+ if (email && normalizeEmail(stored.user.email) !== email) return false;
2444
1954
  if (stored.user.emailVerified !== true) return false;
2445
- if (isShadowEmail$1(stored.user.email)) return false;
1955
+ if (isShadowEmail(stored.user.email)) return false;
2446
1956
  return true;
2447
1957
  }
2448
1958
  function assertVerifiedFormalAccountUser(user, expectedEmail) {
2449
- if (!user || user.emailVerified !== true || isShadowEmail$1(user.email)) throw new Error("Forgeway formal account credentials require a verified formal account.");
2450
- if (expectedEmail && normalizeEmail$1(user.email) !== normalizeEmail$1(expectedEmail)) throw new Error("Forgeway refresh returned credentials for a different email.");
1959
+ if (!user || user.emailVerified !== true || isShadowEmail(user.email)) throw new Error("Forgeway formal account credentials require a verified formal account.");
1960
+ if (expectedEmail && normalizeEmail(user.email) !== normalizeEmail(expectedEmail)) throw new Error("Forgeway refresh returned credentials for a different email.");
2451
1961
  return user;
2452
1962
  }
2453
1963
  async function readCachedPublisherCredentials(cwd, platformApiUrl, email, deps) {
@@ -2480,14 +1990,14 @@ async function resolveCachedPublisherAccount(cwd, options, saved, platformApiUrl
2480
1990
  platformApiUrl
2481
1991
  });
2482
1992
  if (isStoredFormalCredentialForEmail(singleCredentials)) return {
2483
- email: normalizeEmail$1(singleCredentials.user.email),
1993
+ email: normalizeEmail(singleCredentials.user.email),
2484
1994
  credentials: singleCredentials
2485
1995
  };
2486
1996
  const accounts = (await (deps.listCredentials ?? listForgewayCredentials)(platformApiUrl)).filter((credentials) => isStoredFormalCredentialForEmail(credentials));
2487
1997
  if (accounts.length === 1) {
2488
1998
  const credentials = accounts[0];
2489
1999
  return {
2490
- email: normalizeEmail$1(credentials.user.email),
2000
+ email: normalizeEmail(credentials.user.email),
2491
2001
  credentials
2492
2002
  };
2493
2003
  }
@@ -2549,7 +2059,7 @@ async function resolveProjectContext(cwd, options, deps) {
2549
2059
  if (formal.email) throw new Error(`No verified Dineway account credentials were found for ${formal.email}. Run: dineway account login --email ${formal.email}`);
2550
2060
  throw new Error("No verified Dineway account credentials were found. Run: dineway account login");
2551
2061
  }
2552
- function isRecord(value) {
2062
+ function isRecord$1(value) {
2553
2063
  return typeof value === "object" && value !== null && !Array.isArray(value);
2554
2064
  }
2555
2065
  function scalarText(value) {
@@ -2562,7 +2072,7 @@ function uniqueStrings(values) {
2562
2072
  function displayText(value) {
2563
2073
  if (typeof value === "string" || typeof value === "number") return String(value);
2564
2074
  if (Array.isArray(value)) return value.map(displayText).filter(Boolean).join(" ");
2565
- if (isRecord(value)) return displayText(value.text) || displayText(value.name) || displayText(value.displayName) || displayText(value.longText) || displayText(value.shortText) || displayText(value.long_name) || displayText(value.short_name) || displayText(value.value);
2075
+ if (isRecord$1(value)) return displayText(value.text) || displayText(value.name) || displayText(value.displayName) || displayText(value.longText) || displayText(value.shortText) || displayText(value.long_name) || displayText(value.short_name) || displayText(value.value);
2566
2076
  return "";
2567
2077
  }
2568
2078
  function normalizeCompare(value) {
@@ -2580,7 +2090,7 @@ function candidateNameTexts(candidate) {
2580
2090
  ]);
2581
2091
  }
2582
2092
  function candidateAddressTexts(candidate) {
2583
- const addressComponents = Array.isArray(candidate.addressComponents) ? candidate.addressComponents.flatMap((component) => isRecord(component) ? [
2093
+ const addressComponents = Array.isArray(candidate.addressComponents) ? candidate.addressComponents.flatMap((component) => isRecord$1(component) ? [
2584
2094
  displayText(component.longText),
2585
2095
  displayText(component.shortText),
2586
2096
  displayText(component.long_name),
@@ -2722,11 +2232,11 @@ function selectPlaceCandidate(candidates, context) {
2722
2232
  } : null;
2723
2233
  }
2724
2234
  function extractPlaceCandidates(payload) {
2725
- const root = isRecord(payload) ? payload : {};
2726
- const nestedData = isRecord(root.data) ? root.data : {};
2235
+ const root = isRecord$1(payload) ? payload : {};
2236
+ const nestedData = isRecord$1(root.data) ? root.data : {};
2727
2237
  const places = root.places ?? root.results ?? nestedData.places ?? nestedData.results;
2728
2238
  if (!Array.isArray(places)) throw new Error("Dineway place search response did not include places/results.");
2729
- return places.filter(isRecord);
2239
+ return places.filter(isRecord$1);
2730
2240
  }
2731
2241
  async function resolvePlaceIdWithFormalSearch(options) {
2732
2242
  const candidates = extractPlaceCandidates(await ossFetch(options.context, "/api/places/search", options.deps, {
@@ -2743,7 +2253,7 @@ async function resolvePlaceIdWithFormalSearch(options) {
2743
2253
  return finalPlaceId;
2744
2254
  }
2745
2255
  async function ossFetch(context, path, deps, options = {}) {
2746
- const request = async (token) => await forgewayRequest(`${context.ossHost.replace(TRAILING_SLASH_PATTERN$1, "")}${path}`, {
2256
+ const request = async (token) => await forgewayRequest(`${context.ossHost.replace(TRAILING_SLASH_PATTERN, "")}${path}`, {
2747
2257
  ...options,
2748
2258
  headers: {
2749
2259
  "Content-Type": "application/json",
@@ -3258,7 +2768,7 @@ async function deployForgeway(cwd, options, deps = {}) {
3258
2768
  deps,
3259
2769
  verbose
3260
2770
  });
3261
- if (context.user?.email) await writeForgewayDeployMetadata(cwd, { publisherEmail: normalizeEmail$1(context.user.email) });
2771
+ if (context.user?.email) await writeForgewayDeployMetadata(cwd, { publisherEmail: normalizeEmail(context.user.email) });
3262
2772
  const url = deployment.liveUrl ?? `https://${site.domain}`;
3263
2773
  await (deps.writeProjectEnv ?? persistForgewayCliEnv)(cwd, {
3264
2774
  siteUrl: url,
@@ -4040,7 +3550,7 @@ const devCommand = defineCommand({
4040
3550
  const remoteUrl = pkg.dineway?.url || process.env.DINEWAY_URL;
4041
3551
  if (!remoteUrl) consola.warn("No remote URL configured. Set DINEWAY_URL or dineway.url in package.json");
4042
3552
  else try {
4043
- const { createClientFromArgs } = await Promise.resolve().then(() => client_factory_exports);
3553
+ const { createClientFromArgs } = await import("../client-factory-BDD9azPJ.mjs").then((n) => n.t);
4044
3554
  const client = createClientFromArgs({ url: remoteUrl });
4045
3555
  const schema = await client.schemaExport();
4046
3556
  const types = await client.schemaTypes();
@@ -5953,7 +5463,7 @@ const publishCommand = defineCommand({
5953
5463
  const pluginDir = resolve(args.dir);
5954
5464
  try {
5955
5465
  const { runCommand } = await import("citty");
5956
- const { bundleCommand } = await import("../bundle-7C_DpkEq.mjs").then((n) => n.n);
5466
+ const { bundleCommand } = await import("../bundle-CEcdUa1s.mjs").then((n) => n.n);
5957
5467
  await runCommand(bundleCommand, { rawArgs: ["--dir", pluginDir] });
5958
5468
  } catch {
5959
5469
  consola.error("Build failed");
@@ -6723,141 +6233,714 @@ const seedCommand = defineCommand({
6723
6233
  });
6724
6234
 
6725
6235
  //#endregion
6726
- //#region src/cli/forgeway-seo-client.ts
6727
- const DEFAULT_DINEWAY_API_BASE_URL = "https://api.dineway.ai";
6728
- const TRAILING_SLASH_PATTERN = /\/$/;
6729
- const SHADOW_EMAIL_PATTERN = /^shadow_[a-f0-9]{16}@dineway\.ai$/i;
6730
- var ForgewaySeoApiError = class extends Error {
6731
- constructor(message, status, body) {
6732
- super(message);
6733
- this.status = status;
6734
- this.body = body;
6735
- this.name = "ForgewaySeoApiError";
6736
- }
6737
- };
6738
- function normalizeApiUrl(value) {
6739
- return (value || DEFAULT_DINEWAY_API_BASE_URL).replace(TRAILING_SLASH_PATTERN, "");
6236
+ //#region src/seo/local-crawl.ts
6237
+ const CONTRACT_VERSION = 1;
6238
+ const USER_AGENT = "DinewayContentCrawler/1.0";
6239
+ const DEFAULT_PAGE_LIMIT = 100;
6240
+ const MAX_PAGE_LIMIT = 500;
6241
+ const DEFAULT_MAX_DEPTH = 3;
6242
+ const MAX_DEPTH = 10;
6243
+ const DEFAULT_CONCURRENCY = 4;
6244
+ const MAX_CONCURRENCY = 10;
6245
+ const DEFAULT_TIMEOUT_MS = 1e4;
6246
+ const MAX_TIMEOUT_MS = 6e4;
6247
+ const DEFAULT_MAX_BYTES = 1e6;
6248
+ const MAX_RESOURCE_BYTES = 1e7;
6249
+ const MAX_SITEMAPS = 20;
6250
+ const MAX_REDIRECTS = 5;
6251
+ const MAX_TEXT_EXCERPT = 2e4;
6252
+ const LINE_SPLIT_RE = /\r?\n/;
6253
+ const COMMENT_RE = /#.*$/;
6254
+ const WHITESPACE_RE = /\s+/g;
6255
+ const WORD_RE = /[\p{L}\p{N}]+(?:['’-][\p{L}\p{N}]+)*/gu;
6256
+ const REGEXP_SPECIAL_RE = /[.*+?^${}()|[\]\\]/g;
6257
+ async function crawlLocalSite(options) {
6258
+ const normalized = normalizeOptions(options);
6259
+ if (!normalized.allowPrivateNetwork) await resolveAndValidateExternalUrl(normalized.startUrl.href, { resolver: normalized.dnsResolver });
6260
+ const origin = normalized.startUrl.origin;
6261
+ const skipped = /* @__PURE__ */ new Map();
6262
+ const robots = await loadRobots(normalized);
6263
+ const sitemapResult = normalized.sitemap === "skip" ? {
6264
+ sources: [],
6265
+ urls: []
6266
+ } : await loadSitemaps(normalized, robots.sitemaps);
6267
+ const queue = [];
6268
+ const queued = /* @__PURE__ */ new Set();
6269
+ const enqueue = (candidate, depth, discoveredFrom) => {
6270
+ const url = normalizeUrl(candidate, normalized.startUrl, normalized.keepQuery);
6271
+ if (!url || url.origin !== origin || queued.has(url.href)) return;
6272
+ const pathDecision = pathEligibility(url, normalized);
6273
+ if (pathDecision) {
6274
+ skipped.set(url.href, {
6275
+ url: url.href,
6276
+ reason: pathDecision
6277
+ });
6278
+ return;
6279
+ }
6280
+ if (!robotsAllows(url, robots.rules)) {
6281
+ skipped.set(url.href, {
6282
+ url: url.href,
6283
+ reason: "robots_disallowed"
6284
+ });
6285
+ return;
6286
+ }
6287
+ queued.add(url.href);
6288
+ queue.push({
6289
+ url: url.href,
6290
+ depth,
6291
+ discoveredFrom
6292
+ });
6293
+ };
6294
+ if (normalized.sitemap !== "only") enqueue(normalized.startUrl.href, 0, "start");
6295
+ for (const url of sitemapResult.urls) enqueue(url, 0, "sitemap");
6296
+ const pages = [];
6297
+ let cursor = 0;
6298
+ while (cursor < queue.length && pages.length < normalized.pageLimit) {
6299
+ const remaining = normalized.pageLimit - pages.length;
6300
+ const batch = queue.slice(cursor, cursor + Math.min(normalized.concurrency, remaining));
6301
+ cursor += batch.length;
6302
+ const resources = await Promise.all(batch.map(async (item) => ({
6303
+ item,
6304
+ resource: await fetchResource(item.url, normalized)
6305
+ })));
6306
+ for (const { item, resource } of resources) {
6307
+ const page = pageFromResource(item, resource, normalized);
6308
+ pages.push(page);
6309
+ if (normalized.sitemap !== "only" && !page.error && page.contentType?.includes("text/html") && item.depth < normalized.maxDepth) for (const link of page.internalLinks) enqueue(link.url, item.depth + 1, item.url);
6310
+ }
6311
+ }
6312
+ const edges = pages.flatMap((page) => page.internalLinks.map((link) => ({
6313
+ from: page.url,
6314
+ ...link
6315
+ })));
6316
+ const brokenInternalUrls = uniqueSorted(pages.filter((page) => page.httpStatus !== null && page.httpStatus >= 400).filter((page) => edges.some((edge) => edge.url === page.url)).map((page) => page.url));
6317
+ const incoming = new Set(edges.map((edge) => edge.url));
6318
+ const orphanUrls = uniqueSorted(pages.filter((page) => page.url !== normalized.startUrl.href && page.httpStatus !== null && page.httpStatus < 400 && page.contentType?.includes("text/html") && !incoming.has(page.url)).map((page) => page.url));
6319
+ const successfulHtml = pages.filter((page) => page.httpStatus !== null && page.httpStatus < 400 && page.contentType?.includes("text/html"));
6320
+ return {
6321
+ contractVersion: CONTRACT_VERSION,
6322
+ startUrl: normalized.startUrl.href,
6323
+ origin,
6324
+ crawledAt: normalized.now().toISOString(),
6325
+ options: {
6326
+ pageLimit: normalized.pageLimit,
6327
+ maxDepth: normalized.maxDepth,
6328
+ concurrency: normalized.concurrency,
6329
+ include: normalized.includeValues,
6330
+ exclude: normalized.excludeValues,
6331
+ timeoutMs: normalized.timeoutMs,
6332
+ maxBytesPerResource: normalized.maxBytesPerResource,
6333
+ sitemap: normalized.sitemap,
6334
+ keepQuery: normalized.keepQuery,
6335
+ allowPrivateNetwork: normalized.allowPrivateNetwork
6336
+ },
6337
+ sources: {
6338
+ robots: robots.source,
6339
+ sitemaps: sitemapResult.sources
6340
+ },
6341
+ pages,
6342
+ skipped: [...skipped.values()].toSorted((left, right) => left.url.localeCompare(right.url)),
6343
+ linkGraph: {
6344
+ edges,
6345
+ orphanUrls,
6346
+ brokenInternalUrls,
6347
+ duplicateTitles: duplicateGroups(successfulHtml, "title"),
6348
+ duplicateDescriptions: duplicateGroups(successfulHtml, "description"),
6349
+ duplicateContent: duplicateGroups(successfulHtml, "contentSha256")
6350
+ },
6351
+ summary: {
6352
+ fetchedPages: pages.length,
6353
+ htmlPages: pages.filter((page) => page.contentType?.includes("text/html")).length,
6354
+ brokenPages: pages.filter((page) => page.httpStatus !== null && page.httpStatus >= 400).length,
6355
+ orphanPages: orphanUrls.length,
6356
+ skippedUrls: skipped.size,
6357
+ errorPages: pages.filter((page) => page.error).length,
6358
+ pageLimitReached: pages.length === normalized.pageLimit && cursor < queue.length
6359
+ }
6360
+ };
6740
6361
  }
6741
- function resolveForgewaySeoApiUrl(savedPlatformApiUrl, cwd = process.cwd()) {
6742
- return normalizeApiUrl(getProjectEnvValue("DINEWAY_API_BASE_URL", cwd) ?? (typeof savedPlatformApiUrl === "string" ? savedPlatformApiUrl : void 0));
6362
+ function normalizeOptions(options) {
6363
+ const startUrl = normalizeUrl(options.startUrl, void 0, options.keepQuery === true);
6364
+ if (!startUrl) throw new Error("startUrl must be an absolute HTTP or HTTPS URL without credentials");
6365
+ const pageLimit = boundedInteger(options.pageLimit, DEFAULT_PAGE_LIMIT, 1, MAX_PAGE_LIMIT, "pageLimit");
6366
+ const maxDepth = boundedInteger(options.maxDepth, DEFAULT_MAX_DEPTH, 0, MAX_DEPTH, "maxDepth");
6367
+ const concurrency = boundedInteger(options.concurrency, DEFAULT_CONCURRENCY, 1, MAX_CONCURRENCY, "concurrency");
6368
+ const timeoutMs = boundedInteger(options.timeoutMs, DEFAULT_TIMEOUT_MS, 100, MAX_TIMEOUT_MS, "timeoutMs");
6369
+ const maxBytesPerResource = boundedInteger(options.maxBytesPerResource, DEFAULT_MAX_BYTES, 1024, MAX_RESOURCE_BYTES, "maxBytesPerResource");
6370
+ const sitemap = options.sitemap ?? "include";
6371
+ if (![
6372
+ "include",
6373
+ "skip",
6374
+ "only"
6375
+ ].includes(sitemap)) throw new Error("sitemap must be include, skip, or only");
6376
+ const includeValues = normalizePatterns(options.include ?? []);
6377
+ const excludeValues = normalizePatterns(options.exclude ?? []);
6378
+ return {
6379
+ startUrl,
6380
+ pageLimit,
6381
+ maxDepth,
6382
+ concurrency,
6383
+ include: includeValues.map(pathMatcher),
6384
+ exclude: excludeValues.map(pathMatcher),
6385
+ includeValues,
6386
+ excludeValues,
6387
+ timeoutMs,
6388
+ maxBytesPerResource,
6389
+ sitemap,
6390
+ keepQuery: options.keepQuery === true,
6391
+ allowPrivateNetwork: options.allowPrivateNetwork === true,
6392
+ dnsResolver: options.dnsResolver,
6393
+ fetch: options.fetch ?? globalThis.fetch,
6394
+ now: options.now ?? (() => /* @__PURE__ */ new Date())
6395
+ };
6743
6396
  }
6744
- function normalizeEmail(value) {
6745
- return value?.trim().toLowerCase() ?? "";
6397
+ function boundedInteger(value, fallback, minimum, maximum, name) {
6398
+ const normalized = value ?? fallback;
6399
+ if (!Number.isInteger(normalized) || normalized < minimum || normalized > maximum) throw new Error(`${name} must be an integer from ${minimum} to ${maximum}`);
6400
+ return normalized;
6746
6401
  }
6747
- function isShadowEmail(email) {
6748
- return Boolean(email && SHADOW_EMAIL_PATTERN.test(email));
6402
+ function normalizePatterns(values) {
6403
+ return uniqueSorted(values.map((value) => value.trim()).filter((value) => {
6404
+ if (!value.startsWith("/") || value.length > 255) throw new Error("include and exclude patterns must be path globs starting with /");
6405
+ return true;
6406
+ }));
6749
6407
  }
6750
- function assertFormalUser(user) {
6751
- if (!user || user.emailVerified !== true || isShadowEmail(user.email)) throw new Error("Forgeway SEO provider access requires verified formal Forgeway credentials. Run `dineway deploy forgeway` and complete email verification first.");
6752
- return user;
6408
+ function pathMatcher(value) {
6409
+ let source = "^";
6410
+ for (let index = 0; index < value.length; index += 1) {
6411
+ const character = value[index];
6412
+ if (character === "*" && value[index + 1] === "*") {
6413
+ source += ".*";
6414
+ index += 1;
6415
+ } else if (character === "*") source += "[^/]*";
6416
+ else source += escapeRegExp(character ?? "");
6417
+ }
6418
+ return {
6419
+ value,
6420
+ pattern: new RegExp(`${source}$`)
6421
+ };
6422
+ }
6423
+ function pathEligibility(url, options) {
6424
+ const path = url.pathname;
6425
+ if (options.include.length > 0 && !options.include.some((item) => item.pattern.test(path))) return "not_included";
6426
+ if (options.exclude.some((item) => item.pattern.test(path))) return "excluded";
6427
+ return null;
6753
6428
  }
6754
- function normalizeForgewayUser(user, fallback) {
6755
- if (!user?.id || !user.email) return fallback;
6429
+ async function loadRobots(options) {
6430
+ const url = new URL("/robots.txt", options.startUrl.origin).href;
6431
+ const resource = await fetchResource(url, options);
6432
+ if (!resource.response) return {
6433
+ source: {
6434
+ url,
6435
+ status: "temporarily_unavailable",
6436
+ httpStatus: null,
6437
+ error: resource.error ?? "fetch_failed"
6438
+ },
6439
+ rules: [],
6440
+ sitemaps: []
6441
+ };
6442
+ if (resource.response.status === 404) return {
6443
+ source: {
6444
+ url,
6445
+ status: "not_found",
6446
+ httpStatus: 404
6447
+ },
6448
+ rules: [],
6449
+ sitemaps: []
6450
+ };
6451
+ if (!resource.response.ok || resource.error) return {
6452
+ source: {
6453
+ url,
6454
+ status: "temporarily_unavailable",
6455
+ httpStatus: resource.response.status,
6456
+ error: resource.error ?? `http_${resource.response.status}`
6457
+ },
6458
+ rules: [],
6459
+ sitemaps: []
6460
+ };
6461
+ const parsed = parseRobots(resource.body);
6756
6462
  return {
6757
- id: user.id,
6758
- name: user.profile?.name ?? user.name ?? user.email,
6759
- email: user.email,
6760
- avatarUrl: user.profile?.avatar_url ?? user.avatarUrl ?? user.avatar_url ?? null,
6761
- emailVerified: user.emailVerified ?? user.email_verified ?? false
6463
+ source: {
6464
+ url,
6465
+ status: "available",
6466
+ httpStatus: resource.response.status
6467
+ },
6468
+ rules: parsed.rules,
6469
+ sitemaps: parsed.sitemaps
6762
6470
  };
6763
6471
  }
6764
- async function parseJsonResponse(response) {
6765
- const text = await response.text();
6766
- if (!text) return {};
6767
- try {
6768
- return JSON.parse(text);
6769
- } catch {
6770
- return { message: text };
6771
- }
6772
- }
6773
- function errorMessage(body, fallback) {
6774
- if (typeof body !== "object" || body === null) return fallback;
6775
- const record = body;
6776
- if (typeof record.message === "string") return record.message;
6777
- if (typeof record.error === "string") return record.error;
6778
- const nestedError = record.error;
6779
- if (typeof nestedError === "object" && nestedError !== null) {
6780
- const message = nestedError.message;
6781
- if (typeof message === "string") return message;
6782
- }
6783
- return fallback;
6784
- }
6785
- var ForgewaySeoGatewayClient = class {
6786
- fetchImpl;
6787
- readCredentials;
6788
- writeCredentials;
6789
- cwd;
6790
- constructor(deps = {}) {
6791
- this.fetchImpl = deps.fetch ?? fetch;
6792
- this.readCredentials = deps.readCredentials ?? readForgewayCredentials;
6793
- this.writeCredentials = deps.writeCredentials ?? writeForgewayCredentials;
6794
- this.cwd = deps.cwd ?? process.cwd();
6795
- }
6796
- async loadCredentials() {
6797
- const metadata = await readForgewayDeployMetadata(this.cwd).catch(() => ({}));
6798
- const platformApiUrl = resolveForgewaySeoApiUrl(metadata.platformApiUrl, this.cwd);
6799
- const credentials = await this.readCredentials({
6800
- cwd: this.cwd,
6801
- platformApiUrl,
6802
- email: typeof metadata.publisherEmail === "string" ? metadata.publisherEmail : void 0
6472
+ function parseRobots(body) {
6473
+ const groups = [];
6474
+ const sitemaps = [];
6475
+ let current = {
6476
+ agents: [],
6477
+ rules: []
6478
+ };
6479
+ for (const rawLine of body.split(LINE_SPLIT_RE)) {
6480
+ const line = rawLine.replace(COMMENT_RE, "").trim();
6481
+ if (!line) continue;
6482
+ const separator = line.indexOf(":");
6483
+ if (separator < 0) continue;
6484
+ const field = line.slice(0, separator).trim().toLowerCase();
6485
+ const value = line.slice(separator + 1).trim();
6486
+ if (field === "sitemap" && value) {
6487
+ sitemaps.push(value);
6488
+ continue;
6489
+ }
6490
+ if (field === "user-agent") {
6491
+ if (current.rules.length > 0) {
6492
+ groups.push(current);
6493
+ current = {
6494
+ agents: [],
6495
+ rules: []
6496
+ };
6497
+ }
6498
+ current.agents.push(value.toLowerCase());
6499
+ continue;
6500
+ }
6501
+ if ((field === "allow" || field === "disallow") && current.agents.length > 0 && value) current.rules.push({
6502
+ type: field,
6503
+ path: value
6803
6504
  });
6804
- if (!credentials?.accessToken) throw new Error("Forgeway SEO provider access requires stored Forgeway credentials. Run `dineway deploy forgeway` first.");
6805
- assertFormalUser(credentials.user);
6806
- if (!credentials.refreshToken) throw new Error("Forgeway SEO provider access requires a stored formal-account refresh token. Run `dineway deploy forgeway` again.");
6807
- return {
6808
- ...credentials,
6809
- platformApiUrl: resolveForgewaySeoApiUrl(credentials.platformApiUrl, this.cwd)
6810
- };
6811
6505
  }
6812
- async refreshAccessToken(credentials) {
6813
- const response = await this.fetchImpl(`${credentials.platformApiUrl}/api/auth/refresh?client_type=server`, {
6814
- method: "POST",
6815
- headers: { "Content-Type": "application/json" },
6816
- body: JSON.stringify({ refreshToken: credentials.refreshToken })
6817
- });
6818
- const body = await parseJsonResponse(response);
6819
- if (!response.ok) throw new ForgewaySeoApiError(errorMessage(body, "Failed to refresh Forgeway formal account credentials. Run `dineway deploy forgeway` again."), response.status, body);
6820
- if (!body.accessToken) throw new Error("Forgeway refresh response was missing accessToken.");
6821
- const user = assertFormalUser(normalizeForgewayUser(body.user, credentials.user));
6822
- if (credentials.user?.email && normalizeEmail(user.email) !== normalizeEmail(credentials.user.email)) throw new Error("Forgeway refresh returned credentials for a different email.");
6823
- const updated = {
6824
- ...credentials,
6825
- accessToken: body.accessToken,
6826
- refreshToken: body.refreshToken ?? credentials.refreshToken,
6827
- user
6506
+ if (current.agents.length > 0 || current.rules.length > 0) groups.push(current);
6507
+ const crawlerName = USER_AGENT.split("/")[0]?.toLowerCase() ?? "dinewaycontentcrawler";
6508
+ const exact = groups.filter((group) => group.agents.includes(crawlerName));
6509
+ return {
6510
+ rules: (exact.length > 0 ? exact : groups.filter((group) => group.agents.includes("*"))).flatMap((group) => group.rules),
6511
+ sitemaps: uniqueSorted(sitemaps)
6512
+ };
6513
+ }
6514
+ function robotsAllows(url, rules) {
6515
+ const path = `${url.pathname}${url.search}`;
6516
+ const matching = rules.filter((rule) => robotsRuleMatches(path, rule.path)).toSorted((left, right) => right.path.length - left.path.length);
6517
+ if (matching.length === 0) return true;
6518
+ const longest = matching[0]?.path.length ?? 0;
6519
+ return matching.some((rule) => rule.path.length === longest && rule.type === "allow");
6520
+ }
6521
+ function robotsRuleMatches(path, rulePath) {
6522
+ const anchored = rulePath.endsWith("$");
6523
+ const pattern = escapeRegExp(anchored ? rulePath.slice(0, -1) : rulePath).replaceAll("\\*", ".*");
6524
+ return new RegExp(`^${pattern}${anchored ? "$" : ""}`).test(path);
6525
+ }
6526
+ async function loadSitemaps(options, robotsSitemaps) {
6527
+ const queue = (robotsSitemaps.length > 0 ? robotsSitemaps : [new URL("/sitemap.xml", options.startUrl.origin).href]).map((value) => normalizeUrl(value, options.startUrl, options.keepQuery)).filter((value) => value !== null && value.origin === options.startUrl.origin).map((value) => value.href);
6528
+ const seen = /* @__PURE__ */ new Set();
6529
+ const sources = [];
6530
+ const urls = [];
6531
+ while (queue.length > 0 && seen.size < MAX_SITEMAPS) {
6532
+ const url = queue.shift();
6533
+ if (!url || seen.has(url)) continue;
6534
+ seen.add(url);
6535
+ const resource = await fetchResource(url, options);
6536
+ if (!resource.response) {
6537
+ sources.push({
6538
+ url,
6539
+ status: "temporarily_unavailable",
6540
+ httpStatus: null,
6541
+ error: resource.error ?? "fetch_failed"
6542
+ });
6543
+ continue;
6544
+ }
6545
+ if (resource.response.status === 404) {
6546
+ sources.push({
6547
+ url,
6548
+ status: "not_found",
6549
+ httpStatus: 404
6550
+ });
6551
+ continue;
6552
+ }
6553
+ if (!resource.response.ok || resource.error) {
6554
+ sources.push({
6555
+ url,
6556
+ status: "temporarily_unavailable",
6557
+ httpStatus: resource.response.status,
6558
+ error: resource.error ?? `http_${resource.response.status}`
6559
+ });
6560
+ continue;
6561
+ }
6562
+ try {
6563
+ const parsed = parseSitemap(resource.body);
6564
+ sources.push({
6565
+ url,
6566
+ status: "available",
6567
+ httpStatus: resource.response.status
6568
+ });
6569
+ for (const location of parsed.locations) {
6570
+ const normalized = normalizeUrl(location, options.startUrl, options.keepQuery);
6571
+ if (!normalized || normalized.origin !== options.startUrl.origin) continue;
6572
+ if (parsed.kind === "index") queue.push(normalized.href);
6573
+ else urls.push(normalized.href);
6574
+ }
6575
+ } catch {
6576
+ sources.push({
6577
+ url,
6578
+ status: "invalid",
6579
+ httpStatus: resource.response.status,
6580
+ error: "invalid_sitemap_xml"
6581
+ });
6582
+ }
6583
+ }
6584
+ return {
6585
+ sources,
6586
+ urls: uniqueSorted(urls)
6587
+ };
6588
+ }
6589
+ function parseSitemap(body) {
6590
+ const parser = sax.parser(true, {
6591
+ trim: true,
6592
+ normalize: true
6593
+ });
6594
+ let root = null;
6595
+ let inLocation = false;
6596
+ let location = "";
6597
+ const locations = [];
6598
+ parser.onopentag = (node) => {
6599
+ const name = node.name.toLowerCase();
6600
+ root ??= name;
6601
+ if (name === "loc") {
6602
+ inLocation = true;
6603
+ location = "";
6604
+ }
6605
+ };
6606
+ parser.ontext = (text) => {
6607
+ if (inLocation) location += text;
6608
+ };
6609
+ parser.oncdata = (text) => {
6610
+ if (inLocation) location += text;
6611
+ };
6612
+ parser.onclosetag = (name) => {
6613
+ if (name.toLowerCase() !== "loc") return;
6614
+ inLocation = false;
6615
+ if (location.trim()) locations.push(location.trim());
6616
+ };
6617
+ parser.write(body).close();
6618
+ if (root !== "urlset" && root !== "sitemapindex") throw new Error("Unsupported sitemap root");
6619
+ return {
6620
+ kind: root === "sitemapindex" ? "index" : "urls",
6621
+ locations
6622
+ };
6623
+ }
6624
+ async function fetchResource(url, options) {
6625
+ const controller = new AbortController();
6626
+ const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
6627
+ try {
6628
+ let currentUrl = url;
6629
+ let response = null;
6630
+ for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects += 1) {
6631
+ if (!options.allowPrivateNetwork) await resolveAndValidateExternalUrl(currentUrl, { resolver: options.dnsResolver });
6632
+ response = await options.fetch(currentUrl, {
6633
+ headers: {
6634
+ accept: "text/html,application/xml,text/xml,text/plain;q=0.8",
6635
+ "user-agent": USER_AGENT
6636
+ },
6637
+ redirect: "manual",
6638
+ signal: controller.signal
6639
+ });
6640
+ const location = response.headers.get("location");
6641
+ if (response.status < 300 || response.status >= 400 || !location) break;
6642
+ const next = normalizeUrl(location, new URL(currentUrl), options.keepQuery);
6643
+ if (!next) return {
6644
+ response,
6645
+ finalUrl: currentUrl,
6646
+ contentType: response.headers.get("content-type"),
6647
+ body: "",
6648
+ bytes: 0,
6649
+ error: "invalid_redirect"
6650
+ };
6651
+ if (next.origin !== options.startUrl.origin) return {
6652
+ response,
6653
+ finalUrl: next.href,
6654
+ contentType: response.headers.get("content-type"),
6655
+ body: "",
6656
+ bytes: 0,
6657
+ error: "cross_origin_redirect"
6658
+ };
6659
+ if (redirects === MAX_REDIRECTS) return {
6660
+ response,
6661
+ finalUrl: next.href,
6662
+ contentType: response.headers.get("content-type"),
6663
+ body: "",
6664
+ bytes: 0,
6665
+ error: "too_many_redirects"
6666
+ };
6667
+ currentUrl = next.href;
6668
+ }
6669
+ if (!response) throw new Error("No response");
6670
+ const finalUrl = response.url || currentUrl;
6671
+ const contentType = response.headers.get("content-type");
6672
+ const declaredLength = Number(response.headers.get("content-length"));
6673
+ if (Number.isFinite(declaredLength) && declaredLength > options.maxBytesPerResource) return {
6674
+ response,
6675
+ finalUrl,
6676
+ contentType,
6677
+ body: "",
6678
+ bytes: 0,
6679
+ error: "resource_too_large"
6828
6680
  };
6829
- await this.writeCredentials(updated, {
6830
- cwd: this.cwd,
6831
- platformApiUrl: updated.platformApiUrl,
6832
- email: user.email
6833
- });
6834
- return updated;
6681
+ const body = await readBoundedBody(response, options.maxBytesPerResource);
6682
+ return {
6683
+ response,
6684
+ finalUrl,
6685
+ contentType,
6686
+ body: body.text,
6687
+ bytes: body.bytes
6688
+ };
6689
+ } catch (error) {
6690
+ return {
6691
+ response: null,
6692
+ finalUrl: url,
6693
+ contentType: null,
6694
+ body: "",
6695
+ bytes: 0,
6696
+ error: error instanceof ResourceTooLargeError ? "resource_too_large" : fetchError(error)
6697
+ };
6698
+ } finally {
6699
+ clearTimeout(timeout);
6835
6700
  }
6836
- async request(path, body, init) {
6837
- const credentials = await this.refreshAccessToken(await this.loadCredentials());
6838
- const method = init?.method ?? "POST";
6839
- const response = await this.fetchImpl(`${credentials.platformApiUrl}${path}`, {
6840
- method,
6841
- headers: {
6842
- Authorization: `Bearer ${credentials.accessToken}`,
6843
- "Content-Type": "application/json",
6844
- "Idempotency-Key": init?.idempotencyKey ?? randomUUID()
6845
- },
6846
- body: method === "GET" ? void 0 : JSON.stringify(body ?? {})
6847
- });
6848
- const payload = await parseJsonResponse(response);
6849
- if (!response.ok) throw new ForgewaySeoApiError(errorMessage(payload, `Forgeway SEO request failed with status ${response.status}.`), response.status, payload);
6850
- return payload;
6701
+ }
6702
+ async function readBoundedBody(response, limit) {
6703
+ if (!response.body) return {
6704
+ text: "",
6705
+ bytes: 0
6706
+ };
6707
+ const reader = response.body.getReader();
6708
+ const chunks = [];
6709
+ let bytes = 0;
6710
+ while (true) {
6711
+ const result = await reader.read();
6712
+ if (result.done) break;
6713
+ bytes += result.value.byteLength;
6714
+ if (bytes > limit) {
6715
+ await reader.cancel();
6716
+ throw new ResourceTooLargeError();
6717
+ }
6718
+ chunks.push(result.value);
6719
+ }
6720
+ const combined = new Uint8Array(bytes);
6721
+ let offset = 0;
6722
+ for (const chunk of chunks) {
6723
+ combined.set(chunk, offset);
6724
+ offset += chunk.byteLength;
6851
6725
  }
6852
- };
6726
+ return {
6727
+ text: new TextDecoder().decode(combined),
6728
+ bytes
6729
+ };
6730
+ }
6731
+ var ResourceTooLargeError = class extends Error {};
6732
+ function fetchError(error) {
6733
+ if (error instanceof SsrfError) return "private_network_blocked";
6734
+ if (error instanceof Error && error.name === "AbortError") return "timeout";
6735
+ return "fetch_failed";
6736
+ }
6737
+ function pageFromResource(item, resource, options) {
6738
+ const base = {
6739
+ url: item.url,
6740
+ finalUrl: resource.finalUrl,
6741
+ depth: item.depth,
6742
+ discoveredFrom: item.discoveredFrom,
6743
+ httpStatus: resource.response?.status ?? null,
6744
+ contentType: resource.contentType,
6745
+ bytes: resource.bytes
6746
+ };
6747
+ const empty = {
6748
+ title: null,
6749
+ description: null,
6750
+ canonicalUrl: null,
6751
+ robots: null,
6752
+ language: null,
6753
+ headings: {
6754
+ h1: [],
6755
+ h2: [],
6756
+ h3: []
6757
+ },
6758
+ wordCount: 0,
6759
+ contentSha256: null,
6760
+ textExcerpt: "",
6761
+ textTruncated: false,
6762
+ internalLinks: [],
6763
+ externalLinks: [],
6764
+ structuredDataBlocks: 0,
6765
+ renderingRecommended: false
6766
+ };
6767
+ if (resource.error) return {
6768
+ ...base,
6769
+ ...empty,
6770
+ error: resource.error
6771
+ };
6772
+ const final = normalizeUrl(resource.finalUrl, void 0, options.keepQuery);
6773
+ if (!final || final.origin !== options.startUrl.origin) return {
6774
+ ...base,
6775
+ ...empty,
6776
+ error: "cross_origin_redirect"
6777
+ };
6778
+ if (!resource.contentType?.includes("text/html")) return {
6779
+ ...base,
6780
+ ...empty
6781
+ };
6782
+ try {
6783
+ return {
6784
+ ...base,
6785
+ ...parseHtml(resource.body, final, options.keepQuery)
6786
+ };
6787
+ } catch {
6788
+ return {
6789
+ ...base,
6790
+ ...empty,
6791
+ error: "invalid_html"
6792
+ };
6793
+ }
6794
+ }
6795
+ function parseHtml(body, pageUrl, keepQuery) {
6796
+ const document = parse(body);
6797
+ let title = null;
6798
+ let description = null;
6799
+ let canonicalUrl = null;
6800
+ let robots = null;
6801
+ let language = null;
6802
+ let structuredDataBlocks = 0;
6803
+ let scriptBlocks = 0;
6804
+ const headings = {
6805
+ h1: [],
6806
+ h2: [],
6807
+ h3: []
6808
+ };
6809
+ const internalLinks = [];
6810
+ const externalLinks = [];
6811
+ walk(document, (element) => {
6812
+ const attributes = new Map(element.attrs.map((attribute) => [attribute.name, attribute.value]));
6813
+ if (element.tagName === "html") language = cleanText(attributes.get("lang")) || language;
6814
+ if (element.tagName === "title" && title === null) title = cleanText(textContent(element));
6815
+ if (element.tagName === "meta") {
6816
+ const name = attributes.get("name")?.toLowerCase();
6817
+ if (name === "description" && description === null) description = cleanText(attributes.get("content"));
6818
+ if ((name === "robots" || name === "googlebot") && robots === null) robots = cleanText(attributes.get("content"));
6819
+ }
6820
+ if (element.tagName === "link") {
6821
+ if ((attributes.get("rel")?.toLowerCase().split(WHITESPACE_RE) ?? []).includes("canonical") && canonicalUrl === null) canonicalUrl = normalizeUrl(attributes.get("href") ?? "", pageUrl, keepQuery)?.href ?? null;
6822
+ }
6823
+ if (element.tagName === "h1" || element.tagName === "h2" || element.tagName === "h3") {
6824
+ const value = cleanText(textContent(element));
6825
+ if (value) headings[element.tagName].push(value);
6826
+ }
6827
+ if (element.tagName === "script") {
6828
+ scriptBlocks += 1;
6829
+ if (attributes.get("type")?.toLowerCase() === "application/ld+json") structuredDataBlocks += 1;
6830
+ }
6831
+ if (element.tagName === "a") {
6832
+ const link = normalizeUrl(attributes.get("href") ?? "", pageUrl, keepQuery);
6833
+ if (!link) return;
6834
+ const item = {
6835
+ url: link.href,
6836
+ anchor: cleanText(textContent(element)) ?? ""
6837
+ };
6838
+ if (link.origin === pageUrl.origin) internalLinks.push(item);
6839
+ else externalLinks.push(item);
6840
+ }
6841
+ });
6842
+ const visibleText = cleanText(textContent(document)) ?? "";
6843
+ const wordCount = countWords(visibleText);
6844
+ const textExcerpt = visibleText.slice(0, MAX_TEXT_EXCERPT);
6845
+ return {
6846
+ title,
6847
+ description,
6848
+ canonicalUrl,
6849
+ robots,
6850
+ language,
6851
+ headings,
6852
+ wordCount,
6853
+ contentSha256: createHash("sha256").update(visibleText).digest("hex"),
6854
+ textExcerpt,
6855
+ textTruncated: textExcerpt.length < visibleText.length,
6856
+ internalLinks: uniqueLinks(internalLinks),
6857
+ externalLinks: uniqueLinks(externalLinks),
6858
+ structuredDataBlocks,
6859
+ renderingRecommended: wordCount < 20 && scriptBlocks > 0
6860
+ };
6861
+ }
6862
+ function walk(node, visit) {
6863
+ if (isElement(node)) visit(node);
6864
+ if (!("childNodes" in node)) return;
6865
+ for (const child of node.childNodes) walk(child, visit);
6866
+ }
6867
+ function textContent(node, hidden = false) {
6868
+ if ("value" in node) return hidden ? "" : node.value;
6869
+ const nextHidden = hidden || isElement(node) && [
6870
+ "script",
6871
+ "style",
6872
+ "noscript",
6873
+ "template",
6874
+ "svg",
6875
+ "head"
6876
+ ].includes(node.tagName);
6877
+ if (!("childNodes" in node)) return "";
6878
+ return node.childNodes.map((child) => textContent(child, nextHidden)).join(" ");
6879
+ }
6880
+ function isElement(node) {
6881
+ return "tagName" in node;
6882
+ }
6883
+ function normalizeUrl(value, base, keepQuery = false) {
6884
+ try {
6885
+ const url = base ? new URL(value, base) : new URL(value);
6886
+ if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password) return null;
6887
+ url.hash = "";
6888
+ if (!keepQuery) url.search = "";
6889
+ if (!url.pathname) url.pathname = "/";
6890
+ return url;
6891
+ } catch {
6892
+ return null;
6893
+ }
6894
+ }
6895
+ function cleanText(value) {
6896
+ if (!value) return null;
6897
+ return value.replaceAll(WHITESPACE_RE, " ").trim() || null;
6898
+ }
6899
+ function countWords(value) {
6900
+ return value.match(WORD_RE)?.length ?? 0;
6901
+ }
6902
+ function uniqueLinks(links) {
6903
+ const seen = /* @__PURE__ */ new Set();
6904
+ return links.filter((link) => {
6905
+ const key = `${link.url}\u0000${link.anchor}`;
6906
+ if (seen.has(key)) return false;
6907
+ seen.add(key);
6908
+ return true;
6909
+ });
6910
+ }
6911
+ function duplicateGroups(pages, field) {
6912
+ const groups = /* @__PURE__ */ new Map();
6913
+ for (const page of pages) {
6914
+ const value = page[field];
6915
+ if (!value) continue;
6916
+ const key = field === "contentSha256" ? value : value.toLocaleLowerCase();
6917
+ const group = groups.get(key) ?? {
6918
+ value,
6919
+ urls: []
6920
+ };
6921
+ group.urls.push(page.url);
6922
+ groups.set(key, group);
6923
+ }
6924
+ return [...groups.values()].filter((group) => group.urls.length > 1).map((group) => ({
6925
+ value: group.value,
6926
+ urls: uniqueSorted(group.urls)
6927
+ })).toSorted((left, right) => left.value.localeCompare(right.value));
6928
+ }
6929
+ function uniqueSorted(values) {
6930
+ return [...new Set(values)].toSorted((left, right) => left.localeCompare(right));
6931
+ }
6932
+ function escapeRegExp(value) {
6933
+ return value.replace(REGEXP_SPECIAL_RE, "\\$&");
6934
+ }
6853
6935
 
6854
6936
  //#endregion
6855
6937
  //#region src/cli/commands/seo.ts
6938
+ const jsonArgs = { json: {
6939
+ type: "boolean",
6940
+ description: "Output as JSON"
6941
+ } };
6856
6942
  const hostedProviderArgs = {
6857
- json: {
6858
- type: "boolean",
6859
- description: "Output as JSON"
6860
- },
6943
+ ...jsonArgs,
6861
6944
  "force-refresh": {
6862
6945
  type: "boolean",
6863
6946
  description: "Bypass Forgeway SEO provider cache"
@@ -6903,416 +6986,515 @@ function requireArg(value, name) {
6903
6986
  if (!normalized) throw new Error(`${name} is required.`);
6904
6987
  return normalized;
6905
6988
  }
6906
- async function runSeoRequest(args, path, body) {
6907
- configureOutputMode(args);
6908
- try {
6909
- output(await new ForgewaySeoGatewayClient().request(path, body), args);
6910
- } catch (error) {
6911
- if (await handleCliPaywall({
6912
- error,
6913
- resource: path,
6914
- context: { providerPath: path }
6915
- })) process.exit(2);
6916
- consola$1.error(error instanceof Error ? error.message : "Unknown error");
6917
- process.exit(1);
6918
- }
6989
+ function sitemapMode(value) {
6990
+ const mode = toStringValue(value) ?? "include";
6991
+ if (mode === "include" || mode === "skip" || mode === "only") return mode;
6992
+ throw new Error("sitemap must be include, skip, or only.");
6919
6993
  }
6920
- const firecrawlCommand = defineCommand({
6994
+ const crawlCommand = defineCommand({
6921
6995
  meta: {
6922
- name: "firecrawl",
6923
- description: "Forgeway-hosted Firecrawl workflows"
6996
+ name: "crawl",
6997
+ description: "Deterministic site crawl controls"
6924
6998
  },
6925
- subCommands: {
6926
- scrape: defineCommand({
6927
- meta: {
6928
- name: "scrape",
6929
- description: "Scrape one URL through Forgeway-hosted Firecrawl"
6999
+ subCommands: { local: defineCommand({
7000
+ meta: {
7001
+ name: "local",
7002
+ description: "Crawl one site locally and return bounded structural evidence"
7003
+ },
7004
+ args: {
7005
+ url: {
7006
+ type: "positional",
7007
+ description: "First-party site URL",
7008
+ required: true
6930
7009
  },
6931
- args: {
6932
- url: {
6933
- type: "positional",
6934
- description: "URL to scrape",
6935
- required: true
6936
- },
6937
- ...hostedProviderArgs,
6938
- formats: {
6939
- type: "string",
6940
- description: "Comma-separated Firecrawl formats",
6941
- default: "markdown"
6942
- },
6943
- "only-main-content": {
6944
- type: "boolean",
6945
- description: "Return main content only",
6946
- default: true
6947
- },
6948
- "timeout-ms": {
6949
- type: "string",
6950
- description: "Provider timeout in milliseconds",
6951
- default: "60000"
6952
- }
7010
+ ...jsonArgs,
7011
+ limit: {
7012
+ type: "string",
7013
+ description: "Maximum pages",
7014
+ default: "100"
6953
7015
  },
6954
- async run({ args }) {
6955
- await runSeoRequest(args, "/api/firecrawl/scrape", {
6956
- url: requireArg(args.url, "url"),
6957
- formats: formats(args.formats),
6958
- onlyMainContent: args["only-main-content"] !== false,
6959
- timeoutMs: toNumberValue(args["timeout-ms"], 6e4),
6960
- forceRefresh: forceRefresh(args)
6961
- });
6962
- }
6963
- }),
6964
- map: defineCommand({
6965
- meta: {
6966
- name: "map",
6967
- description: "Map URLs through Forgeway-hosted Firecrawl"
7016
+ "max-depth": {
7017
+ type: "string",
7018
+ description: "Maximum link discovery depth",
7019
+ default: "3"
6968
7020
  },
6969
- args: {
6970
- url: {
6971
- type: "positional",
6972
- description: "Base URL to map",
6973
- required: true
6974
- },
6975
- ...hostedProviderArgs,
6976
- search: {
6977
- type: "string",
6978
- description: "Optional relevance search term"
6979
- },
6980
- sitemap: {
6981
- type: "string",
6982
- description: "Sitemap mode: skip, include, or only",
6983
- default: "include"
6984
- },
6985
- limit: {
6986
- type: "string",
6987
- description: "Maximum URLs",
6988
- default: "100"
6989
- },
6990
- "include-subdomains": {
6991
- type: "boolean",
6992
- description: "Include subdomains"
6993
- },
6994
- "ignore-query-parameters": {
6995
- type: "boolean",
6996
- description: "Ignore query parameters",
6997
- default: true
6998
- }
7021
+ concurrency: {
7022
+ type: "string",
7023
+ description: "Maximum concurrent page requests",
7024
+ default: "4"
6999
7025
  },
7000
- async run({ args }) {
7001
- await runSeoRequest(args, "/api/firecrawl/map", {
7002
- url: requireArg(args.url, "url"),
7003
- search: toStringValue(args.search),
7004
- sitemap: toStringValue(args.sitemap) ?? "include",
7005
- limit: toNumberValue(args.limit, 100),
7006
- includeSubdomains: args["include-subdomains"] === true,
7007
- ignoreQueryParameters: args["ignore-query-parameters"] !== false,
7008
- forceRefresh: forceRefresh(args)
7009
- });
7010
- }
7011
- }),
7012
- crawl: defineCommand({
7013
- meta: {
7014
- name: "crawl",
7015
- description: "Start a bounded Firecrawl crawl, or check status with --id"
7026
+ include: {
7027
+ type: "string",
7028
+ description: "Comma-separated path globs to include"
7016
7029
  },
7017
- args: {
7018
- url: {
7019
- type: "positional",
7020
- description: "Base URL to crawl"
7021
- },
7022
- ...hostedProviderArgs,
7023
- id: {
7024
- type: "string",
7025
- description: "Existing Firecrawl crawl id to check"
7026
- },
7027
- limit: {
7028
- type: "string",
7029
- description: "Maximum pages",
7030
- default: "25"
7031
- },
7032
- "max-depth": {
7033
- type: "string",
7034
- description: "Maximum discovery depth",
7035
- default: "3"
7036
- },
7037
- "allow-subdomains": {
7038
- type: "boolean",
7039
- description: "Allow subdomains"
7040
- },
7041
- "allow-external-links": {
7042
- type: "boolean",
7043
- description: "Allow external links"
7044
- },
7045
- formats: {
7046
- type: "string",
7047
- description: "Comma-separated scrape formats",
7048
- default: "markdown"
7049
- }
7030
+ exclude: {
7031
+ type: "string",
7032
+ description: "Comma-separated path globs to exclude"
7050
7033
  },
7051
- async run({ args }) {
7052
- const crawlId = toStringValue(args.id);
7053
- if (crawlId) {
7054
- configureOutputMode(args);
7055
- try {
7056
- output(await new ForgewaySeoGatewayClient().request(`/api/firecrawl/crawl/${encodeURIComponent(crawlId)}`, void 0, { method: "GET" }), args);
7057
- } catch (error) {
7058
- if (await handleCliPaywall({
7059
- error,
7060
- resource: `/api/firecrawl/crawl/${crawlId}`,
7061
- context: { providerPath: `/api/firecrawl/crawl/${crawlId}` }
7062
- })) process.exit(2);
7063
- consola$1.error(error instanceof Error ? error.message : "Unknown error");
7064
- process.exit(1);
7065
- }
7066
- return;
7067
- }
7068
- await runSeoRequest(args, "/api/firecrawl/crawl", {
7069
- url: requireArg(args.url, "url"),
7070
- limit: toNumberValue(args.limit, 25),
7071
- maxDiscoveryDepth: toNumberValue(args["max-depth"], 3),
7072
- allowExternalLinks: args["allow-external-links"] === true,
7073
- allowSubdomains: args["allow-subdomains"] === true,
7074
- scrapeFormats: formats(args.formats),
7075
- forceRefresh: forceRefresh(args)
7076
- });
7077
- }
7078
- }),
7079
- search: defineCommand({
7080
- meta: {
7081
- name: "search",
7082
- description: "Search through Forgeway-hosted Firecrawl"
7034
+ "timeout-ms": {
7035
+ type: "string",
7036
+ description: "Per-resource timeout in milliseconds",
7037
+ default: "10000"
7083
7038
  },
7084
- args: {
7085
- query: {
7086
- type: "positional",
7087
- description: "Search query",
7088
- required: true
7089
- },
7090
- ...hostedProviderArgs,
7091
- limit: {
7092
- type: "string",
7093
- description: "Maximum results",
7094
- default: "10"
7095
- },
7096
- scrape: {
7097
- type: "boolean",
7098
- description: "Scrape result markdown"
7099
- }
7039
+ "max-bytes": {
7040
+ type: "string",
7041
+ description: "Maximum bytes per fetched resource",
7042
+ default: "1000000"
7100
7043
  },
7101
- async run({ args }) {
7102
- await runSeoRequest(args, "/api/firecrawl/search", {
7103
- query: requireArg(args.query, "query"),
7104
- limit: toNumberValue(args.limit, 10),
7105
- scrape: args.scrape === true,
7106
- forceRefresh: forceRefresh(args)
7107
- });
7044
+ sitemap: {
7045
+ type: "string",
7046
+ description: "Sitemap mode: include, skip, or only",
7047
+ default: "include"
7048
+ },
7049
+ "keep-query": {
7050
+ type: "boolean",
7051
+ description: "Keep query parameters in page identity"
7052
+ },
7053
+ "allow-private-network": {
7054
+ type: "boolean",
7055
+ description: "Allow an explicitly selected localhost or private origin"
7108
7056
  }
7109
- })
7110
- }
7057
+ },
7058
+ async run({ args }) {
7059
+ configureOutputMode(args);
7060
+ try {
7061
+ output(await crawlLocalSite({
7062
+ startUrl: requireArg(args.url, "url"),
7063
+ pageLimit: toNumberValue(args.limit, 100),
7064
+ maxDepth: toNumberValue(args["max-depth"], 3),
7065
+ concurrency: toNumberValue(args.concurrency, 4),
7066
+ include: formats(args.include) ?? [],
7067
+ exclude: formats(args.exclude) ?? [],
7068
+ timeoutMs: toNumberValue(args["timeout-ms"], 1e4),
7069
+ maxBytesPerResource: toNumberValue(args["max-bytes"], 1e6),
7070
+ sitemap: sitemapMode(args.sitemap),
7071
+ keepQuery: args["keep-query"] === true,
7072
+ allowPrivateNetwork: args["allow-private-network"] === true
7073
+ }), args);
7074
+ } catch (error) {
7075
+ consola$1.error(error instanceof Error ? error.message : "Local site crawl failed");
7076
+ process.exit(1);
7077
+ }
7078
+ }
7079
+ }) }
7111
7080
  });
7081
+ function dataForSeoDevice(value) {
7082
+ const normalized = toStringValue(value) ?? "desktop";
7083
+ if (normalized === "desktop" || normalized === "mobile") return normalized;
7084
+ throw new Error("device must be desktop or mobile.");
7085
+ }
7086
+ function aiScrapePlatform(value) {
7087
+ const normalized = toStringValue(value) ?? "chat_gpt";
7088
+ if (normalized === "chat_gpt" || normalized === "gemini" || normalized === "perplexity") return normalized;
7089
+ throw new Error("platform must be chat_gpt, gemini, or perplexity.");
7090
+ }
7091
+ function aiMentionPlatform(value) {
7092
+ const normalized = toStringValue(value) ?? "google";
7093
+ if (normalized === "google" || normalized === "chat_gpt") return normalized;
7094
+ throw new Error("platform must be google or chat_gpt.");
7095
+ }
7112
7096
  function dataForSeoBase(args) {
7113
7097
  return {
7114
7098
  locationCode: toNumberValue(args["location-code"], 2840),
7115
7099
  languageCode: toStringValue(args["language-code"]) ?? "en",
7116
- device: toStringValue(args.device) ?? "desktop",
7100
+ device: dataForSeoDevice(args.device),
7117
7101
  forceRefresh: forceRefresh(args)
7118
7102
  };
7119
7103
  }
7120
- const dataForSeoSerpCommand = defineCommand({
7121
- meta: {
7122
- name: "serp",
7123
- description: "Fetch Google organic SERP data"
7124
- },
7125
- args: {
7126
- keyword: {
7127
- type: "positional",
7128
- description: "Keyword",
7129
- required: true
7130
- },
7131
- ...dataForSeoCommonArgs,
7132
- depth: {
7133
- type: "string",
7134
- description: "SERP depth",
7135
- default: "10"
7104
+ function createSeoCommand(options = {}) {
7105
+ const providerService = options.providerService ?? new SeoProviderEvidenceService(new ForgewaySeoGatewayClient());
7106
+ async function runSeoRequest(args, resource, request) {
7107
+ configureOutputMode(args);
7108
+ try {
7109
+ output(await request(), args);
7110
+ } catch (error) {
7111
+ if (await handleCliPaywall({
7112
+ error,
7113
+ resource,
7114
+ context: { providerPath: resource }
7115
+ })) process.exit(2);
7116
+ consola$1.error(error instanceof Error ? error.message : "Unknown error");
7117
+ process.exit(1);
7136
7118
  }
7137
- },
7138
- async run({ args }) {
7139
- await runSeoRequest(args, "/api/dataforseo/serp", {
7140
- keyword: requireArg(args.keyword, "keyword"),
7141
- depth: toNumberValue(args.depth, 10),
7142
- ...dataForSeoBase(args)
7143
- });
7144
7119
  }
7145
- });
7146
- const dataForSeoMapsCommand = defineCommand({
7147
- meta: {
7148
- name: "maps",
7149
- description: "Fetch Google Maps SERP data"
7150
- },
7151
- args: {
7152
- keyword: {
7153
- type: "positional",
7154
- description: "Keyword",
7155
- required: true
7156
- },
7157
- ...dataForSeoCommonArgs,
7158
- depth: {
7159
- type: "string",
7160
- description: "Maps depth",
7161
- default: "10"
7120
+ const firecrawlCommand = defineCommand({
7121
+ meta: {
7122
+ name: "firecrawl",
7123
+ description: "Forgeway-hosted Firecrawl workflows"
7124
+ },
7125
+ subCommands: {
7126
+ scrape: defineCommand({
7127
+ meta: {
7128
+ name: "scrape",
7129
+ description: "Scrape one URL through Forgeway-hosted Firecrawl"
7130
+ },
7131
+ args: {
7132
+ url: {
7133
+ type: "positional",
7134
+ description: "URL to scrape",
7135
+ required: true
7136
+ },
7137
+ ...hostedProviderArgs,
7138
+ formats: {
7139
+ type: "string",
7140
+ description: "Comma-separated Firecrawl formats",
7141
+ default: "markdown"
7142
+ },
7143
+ "only-main-content": {
7144
+ type: "boolean",
7145
+ description: "Return main content only",
7146
+ default: true
7147
+ },
7148
+ "timeout-ms": {
7149
+ type: "string",
7150
+ description: "Provider timeout in milliseconds",
7151
+ default: "60000"
7152
+ }
7153
+ },
7154
+ async run({ args }) {
7155
+ await runSeoRequest(args, "/api/firecrawl/scrape", () => providerService.firecrawlScrape({
7156
+ url: requireArg(args.url, "url"),
7157
+ formats: formats(args.formats) ?? ["markdown"],
7158
+ onlyMainContent: args["only-main-content"] !== false,
7159
+ timeoutMs: toNumberValue(args["timeout-ms"], 6e4),
7160
+ forceRefresh: forceRefresh(args)
7161
+ }));
7162
+ }
7163
+ }),
7164
+ map: defineCommand({
7165
+ meta: {
7166
+ name: "map",
7167
+ description: "Map URLs through Forgeway-hosted Firecrawl"
7168
+ },
7169
+ args: {
7170
+ url: {
7171
+ type: "positional",
7172
+ description: "Base URL to map",
7173
+ required: true
7174
+ },
7175
+ ...hostedProviderArgs,
7176
+ search: {
7177
+ type: "string",
7178
+ description: "Optional relevance search term"
7179
+ },
7180
+ sitemap: {
7181
+ type: "string",
7182
+ description: "Sitemap mode: skip, include, or only",
7183
+ default: "include"
7184
+ },
7185
+ limit: {
7186
+ type: "string",
7187
+ description: "Maximum URLs",
7188
+ default: "100"
7189
+ },
7190
+ "include-subdomains": {
7191
+ type: "boolean",
7192
+ description: "Include subdomains"
7193
+ },
7194
+ "ignore-query-parameters": {
7195
+ type: "boolean",
7196
+ description: "Ignore query parameters",
7197
+ default: true
7198
+ }
7199
+ },
7200
+ async run({ args }) {
7201
+ await runSeoRequest(args, "/api/firecrawl/map", () => providerService.firecrawlMap({
7202
+ url: requireArg(args.url, "url"),
7203
+ search: toStringValue(args.search),
7204
+ sitemap: sitemapMode(args.sitemap),
7205
+ limit: toNumberValue(args.limit, 100),
7206
+ includeSubdomains: args["include-subdomains"] === true,
7207
+ ignoreQueryParameters: args["ignore-query-parameters"] !== false,
7208
+ forceRefresh: forceRefresh(args)
7209
+ }));
7210
+ }
7211
+ }),
7212
+ crawl: defineCommand({
7213
+ meta: {
7214
+ name: "crawl",
7215
+ description: "Start a bounded Firecrawl crawl, or check status with --id"
7216
+ },
7217
+ args: {
7218
+ url: {
7219
+ type: "positional",
7220
+ description: "Base URL to crawl",
7221
+ required: false
7222
+ },
7223
+ ...hostedProviderArgs,
7224
+ id: {
7225
+ type: "string",
7226
+ description: "Existing Firecrawl crawl id to check"
7227
+ },
7228
+ limit: {
7229
+ type: "string",
7230
+ description: "Maximum pages",
7231
+ default: "25"
7232
+ },
7233
+ "max-depth": {
7234
+ type: "string",
7235
+ description: "Maximum discovery depth",
7236
+ default: "3"
7237
+ },
7238
+ "allow-subdomains": {
7239
+ type: "boolean",
7240
+ description: "Allow subdomains"
7241
+ },
7242
+ "allow-external-links": {
7243
+ type: "boolean",
7244
+ description: "Allow external links"
7245
+ },
7246
+ formats: {
7247
+ type: "string",
7248
+ description: "Comma-separated scrape formats",
7249
+ default: "markdown"
7250
+ }
7251
+ },
7252
+ async run({ args }) {
7253
+ const crawlId = toStringValue(args.id);
7254
+ if (crawlId) {
7255
+ await runSeoRequest(args, `/api/firecrawl/crawl/${crawlId}`, () => providerService.firecrawlCrawlStatus({ id: crawlId }));
7256
+ return;
7257
+ }
7258
+ await runSeoRequest(args, "/api/firecrawl/crawl", () => providerService.firecrawlCrawlStart({
7259
+ url: requireArg(args.url, "url"),
7260
+ limit: toNumberValue(args.limit, 25),
7261
+ maxDiscoveryDepth: toNumberValue(args["max-depth"], 3),
7262
+ allowExternalLinks: args["allow-external-links"] === true,
7263
+ allowSubdomains: args["allow-subdomains"] === true,
7264
+ scrapeFormats: formats(args.formats) ?? ["markdown"],
7265
+ forceRefresh: forceRefresh(args)
7266
+ }));
7267
+ }
7268
+ }),
7269
+ search: defineCommand({
7270
+ meta: {
7271
+ name: "search",
7272
+ description: "Search through Forgeway-hosted Firecrawl"
7273
+ },
7274
+ args: {
7275
+ query: {
7276
+ type: "positional",
7277
+ description: "Search query",
7278
+ required: true
7279
+ },
7280
+ ...hostedProviderArgs,
7281
+ limit: {
7282
+ type: "string",
7283
+ description: "Maximum results",
7284
+ default: "10"
7285
+ },
7286
+ scrape: {
7287
+ type: "boolean",
7288
+ description: "Scrape result markdown"
7289
+ }
7290
+ },
7291
+ async run({ args }) {
7292
+ await runSeoRequest(args, "/api/firecrawl/search", () => providerService.firecrawlSearch({
7293
+ query: requireArg(args.query, "query"),
7294
+ limit: toNumberValue(args.limit, 10),
7295
+ scrape: args.scrape === true,
7296
+ forceRefresh: forceRefresh(args)
7297
+ }));
7298
+ }
7299
+ })
7162
7300
  }
7163
- },
7164
- async run({ args }) {
7165
- await runSeoRequest(args, "/api/dataforseo/maps", {
7166
- keyword: requireArg(args.keyword, "keyword"),
7167
- depth: toNumberValue(args.depth, 10),
7168
- ...dataForSeoBase(args)
7169
- });
7170
- }
7171
- });
7172
- const dataForSeoKeywordsCommand = defineCommand({
7173
- meta: {
7174
- name: "keywords",
7175
- description: "Fetch keyword suggestions"
7176
- },
7177
- args: {
7178
- keyword: {
7179
- type: "positional",
7180
- description: "Seed keyword",
7181
- required: true
7301
+ });
7302
+ const dataForSeoSerpCommand = defineCommand({
7303
+ meta: {
7304
+ name: "serp",
7305
+ description: "Fetch Google organic SERP data"
7306
+ },
7307
+ args: {
7308
+ keyword: {
7309
+ type: "positional",
7310
+ description: "Keyword",
7311
+ required: true
7312
+ },
7313
+ ...dataForSeoCommonArgs,
7314
+ depth: {
7315
+ type: "string",
7316
+ description: "SERP depth",
7317
+ default: "10"
7318
+ }
7182
7319
  },
7183
- ...dataForSeoCommonArgs,
7184
- limit: {
7185
- type: "string",
7186
- description: "Maximum suggestions",
7187
- default: "20"
7320
+ async run({ args }) {
7321
+ await runSeoRequest(args, "/api/dataforseo/serp", () => providerService.dataForSeoSerp({
7322
+ keyword: requireArg(args.keyword, "keyword"),
7323
+ depth: toNumberValue(args.depth, 10),
7324
+ ...dataForSeoBase(args)
7325
+ }));
7188
7326
  }
7189
- },
7190
- async run({ args }) {
7191
- await runSeoRequest(args, "/api/dataforseo/keywords", {
7192
- keyword: requireArg(args.keyword, "keyword"),
7193
- limit: toNumberValue(args.limit, 20),
7194
- includeSerpInfo: true,
7195
- includeSeedKeyword: true,
7196
- ...dataForSeoBase(args)
7197
- });
7198
- }
7199
- });
7200
- const dataForSeoCompetitorsCommand = defineCommand({
7201
- meta: {
7202
- name: "competitors",
7203
- description: "Fetch competitor-domain data"
7204
- },
7205
- args: {
7206
- domain: {
7207
- type: "positional",
7208
- description: "Target domain",
7209
- required: true
7327
+ });
7328
+ const dataForSeoMapsCommand = defineCommand({
7329
+ meta: {
7330
+ name: "maps",
7331
+ description: "Fetch Google Maps SERP data"
7332
+ },
7333
+ args: {
7334
+ keyword: {
7335
+ type: "positional",
7336
+ description: "Keyword",
7337
+ required: true
7338
+ },
7339
+ ...dataForSeoCommonArgs,
7340
+ depth: {
7341
+ type: "string",
7342
+ description: "Maps depth",
7343
+ default: "10"
7344
+ }
7210
7345
  },
7211
- ...dataForSeoCommonArgs,
7212
- limit: {
7213
- type: "string",
7214
- description: "Maximum competitors",
7215
- default: "20"
7346
+ async run({ args }) {
7347
+ await runSeoRequest(args, "/api/dataforseo/maps", () => providerService.dataForSeoMaps({
7348
+ keyword: requireArg(args.keyword, "keyword"),
7349
+ depth: toNumberValue(args.depth, 10),
7350
+ ...dataForSeoBase(args)
7351
+ }));
7216
7352
  }
7217
- },
7218
- async run({ args }) {
7219
- await runSeoRequest(args, "/api/dataforseo/competitors", {
7220
- targetDomain: requireArg(args.domain, "domain"),
7221
- limit: toNumberValue(args.limit, 20),
7222
- ...dataForSeoBase(args)
7223
- });
7224
- }
7225
- });
7226
- const dataForSeoAiScrapeCommand = defineCommand({
7227
- meta: {
7228
- name: "ai-scrape",
7229
- description: "Fetch DataForSEO AI scraper response data"
7230
- },
7231
- args: {
7232
- keyword: {
7233
- type: "positional",
7234
- description: "AI search keyword",
7235
- required: true
7353
+ });
7354
+ const dataForSeoKeywordsCommand = defineCommand({
7355
+ meta: {
7356
+ name: "keywords",
7357
+ description: "Fetch keyword suggestions"
7358
+ },
7359
+ args: {
7360
+ keyword: {
7361
+ type: "positional",
7362
+ description: "Seed keyword",
7363
+ required: true
7364
+ },
7365
+ ...dataForSeoCommonArgs,
7366
+ limit: {
7367
+ type: "string",
7368
+ description: "Maximum suggestions",
7369
+ default: "20"
7370
+ }
7236
7371
  },
7237
- ...dataForSeoCommonArgs,
7238
- platform: {
7239
- type: "string",
7240
- description: "AI platform: chat_gpt, gemini, or perplexity",
7241
- default: "chat_gpt"
7372
+ async run({ args }) {
7373
+ await runSeoRequest(args, "/api/dataforseo/keywords", () => providerService.dataForSeoKeywords({
7374
+ keyword: requireArg(args.keyword, "keyword"),
7375
+ limit: toNumberValue(args.limit, 20),
7376
+ ...dataForSeoBase(args)
7377
+ }));
7242
7378
  }
7243
- },
7244
- async run({ args }) {
7245
- await runSeoRequest(args, "/api/dataforseo/ai-scrape", {
7246
- keyword: requireArg(args.keyword, "keyword"),
7247
- platform: toStringValue(args.platform) ?? "chat_gpt",
7248
- ...dataForSeoBase(args)
7249
- });
7250
- }
7251
- });
7252
- const dataForSeoAiMentionsCommand = defineCommand({
7253
- meta: {
7254
- name: "ai-mentions",
7255
- description: "Fetch DataForSEO LLM mention data"
7256
- },
7257
- args: {
7258
- ...dataForSeoCommonArgs,
7259
- domain: {
7260
- type: "string",
7261
- description: "Target domain"
7379
+ });
7380
+ const dataForSeoCompetitorsCommand = defineCommand({
7381
+ meta: {
7382
+ name: "competitors",
7383
+ description: "Fetch competitor-domain data"
7384
+ },
7385
+ args: {
7386
+ domain: {
7387
+ type: "positional",
7388
+ description: "Target domain",
7389
+ required: true
7390
+ },
7391
+ ...dataForSeoCommonArgs,
7392
+ limit: {
7393
+ type: "string",
7394
+ description: "Maximum competitors",
7395
+ default: "20"
7396
+ }
7262
7397
  },
7263
- keyword: {
7264
- type: "string",
7265
- description: "Target keyword"
7398
+ async run({ args }) {
7399
+ await runSeoRequest(args, "/api/dataforseo/competitors", () => providerService.dataForSeoCompetitors({
7400
+ domain: requireArg(args.domain, "domain"),
7401
+ limit: toNumberValue(args.limit, 20),
7402
+ ...dataForSeoBase(args)
7403
+ }));
7404
+ }
7405
+ });
7406
+ const dataForSeoAiScrapeCommand = defineCommand({
7407
+ meta: {
7408
+ name: "ai-scrape",
7409
+ description: "Fetch DataForSEO AI scraper response data"
7410
+ },
7411
+ args: {
7412
+ keyword: {
7413
+ type: "positional",
7414
+ description: "AI search keyword",
7415
+ required: true
7416
+ },
7417
+ ...dataForSeoCommonArgs,
7418
+ platform: {
7419
+ type: "string",
7420
+ description: "AI platform: chat_gpt, gemini, or perplexity",
7421
+ default: "chat_gpt"
7422
+ }
7266
7423
  },
7267
- platform: {
7268
- type: "string",
7269
- description: "Mention platform: google or chat_gpt",
7270
- default: "google"
7424
+ async run({ args }) {
7425
+ await runSeoRequest(args, "/api/dataforseo/ai-scrape", () => providerService.dataForSeoAiScrape({
7426
+ keyword: requireArg(args.keyword, "keyword"),
7427
+ platform: aiScrapePlatform(args.platform),
7428
+ ...dataForSeoBase(args)
7429
+ }));
7430
+ }
7431
+ });
7432
+ const dataForSeoAiMentionsCommand = defineCommand({
7433
+ meta: {
7434
+ name: "ai-mentions",
7435
+ description: "Fetch DataForSEO LLM mention data"
7436
+ },
7437
+ args: {
7438
+ ...dataForSeoCommonArgs,
7439
+ domain: {
7440
+ type: "string",
7441
+ description: "Target domain"
7442
+ },
7443
+ keyword: {
7444
+ type: "string",
7445
+ description: "Target keyword"
7446
+ },
7447
+ platform: {
7448
+ type: "string",
7449
+ description: "Mention platform: google or chat_gpt",
7450
+ default: "google"
7451
+ },
7452
+ limit: {
7453
+ type: "string",
7454
+ description: "Maximum mentions",
7455
+ default: "20"
7456
+ }
7271
7457
  },
7272
- limit: {
7273
- type: "string",
7274
- description: "Maximum mentions",
7275
- default: "20"
7458
+ async run({ args }) {
7459
+ if (!toStringValue(args.domain) && !toStringValue(args.keyword)) {
7460
+ consola$1.error("domain or keyword is required.");
7461
+ process.exit(1);
7462
+ }
7463
+ await runSeoRequest(args, "/api/dataforseo/ai-mentions", () => providerService.dataForSeoAiMentions({
7464
+ domain: toStringValue(args.domain),
7465
+ keyword: toStringValue(args.keyword),
7466
+ platform: aiMentionPlatform(args.platform),
7467
+ limit: toNumberValue(args.limit, 20),
7468
+ ...dataForSeoBase(args)
7469
+ }));
7276
7470
  }
7277
- },
7278
- async run({ args }) {
7279
- if (!toStringValue(args.domain) && !toStringValue(args.keyword)) {
7280
- consola$1.error("domain or keyword is required.");
7281
- process.exit(1);
7471
+ });
7472
+ return defineCommand({
7473
+ meta: {
7474
+ name: "seo",
7475
+ description: "Local site evidence and hosted SEO provider workflows"
7476
+ },
7477
+ subCommands: {
7478
+ crawl: crawlCommand,
7479
+ firecrawl: firecrawlCommand,
7480
+ dataforseo: defineCommand({
7481
+ meta: {
7482
+ name: "dataforseo",
7483
+ description: "Forgeway-hosted DataForSEO workflows"
7484
+ },
7485
+ subCommands: {
7486
+ serp: dataForSeoSerpCommand,
7487
+ maps: dataForSeoMapsCommand,
7488
+ keywords: dataForSeoKeywordsCommand,
7489
+ competitors: dataForSeoCompetitorsCommand,
7490
+ "ai-scrape": dataForSeoAiScrapeCommand,
7491
+ "ai-mentions": dataForSeoAiMentionsCommand
7492
+ }
7493
+ })
7282
7494
  }
7283
- await runSeoRequest(args, "/api/dataforseo/ai-mentions", {
7284
- targetDomain: toStringValue(args.domain),
7285
- keyword: toStringValue(args.keyword),
7286
- platform: toStringValue(args.platform) ?? "google",
7287
- limit: toNumberValue(args.limit, 20),
7288
- ...dataForSeoBase(args)
7289
- });
7290
- }
7291
- });
7292
- const dataForSeoCommand = defineCommand({
7293
- meta: {
7294
- name: "dataforseo",
7295
- description: "Forgeway-hosted DataForSEO workflows"
7296
- },
7297
- subCommands: {
7298
- serp: dataForSeoSerpCommand,
7299
- maps: dataForSeoMapsCommand,
7300
- keywords: dataForSeoKeywordsCommand,
7301
- competitors: dataForSeoCompetitorsCommand,
7302
- "ai-scrape": dataForSeoAiScrapeCommand,
7303
- "ai-mentions": dataForSeoAiMentionsCommand
7304
- }
7305
- });
7306
- const seoCommand = defineCommand({
7307
- meta: {
7308
- name: "seo",
7309
- description: "Hosted SEO provider workflows through Forgeway"
7310
- },
7311
- subCommands: {
7312
- firecrawl: firecrawlCommand,
7313
- dataforseo: dataForSeoCommand
7314
- }
7315
- });
7495
+ });
7496
+ }
7497
+ const seoCommand = createSeoCommand();
7316
7498
 
7317
7499
  //#endregion
7318
7500
  //#region src/cli/commands/taxonomy.ts
@@ -7482,6 +7664,88 @@ const typesCommand = defineCommand({
7482
7664
  }
7483
7665
  });
7484
7666
 
7667
+ //#endregion
7668
+ //#region src/cli/contributions.ts
7669
+ const DINEWAY_PLUGIN_PACKAGE = /^(?:@dineway-ai\/plugin-|dineway-plugin-)/;
7670
+ const CLI_COMMAND_NAME = /^[a-z][a-z0-9-]*$/;
7671
+ async function discoverDinewayCliContributions(cwd = process.cwd()) {
7672
+ const projectPackage = await findProjectPackage(cwd);
7673
+ if (!projectPackage) return [];
7674
+ const { packagePath, project } = projectPackage;
7675
+ const requireFromProject = createRequire(packagePath);
7676
+ const packageNames = new Set([
7677
+ ...Object.keys(project.dependencies ?? {}),
7678
+ ...Object.keys(project.devDependencies ?? {}),
7679
+ ...Object.keys(project.optionalDependencies ?? {})
7680
+ ]);
7681
+ const contributions = [];
7682
+ for (const packageName of [...packageNames].filter(isDinewayPluginPackage).toSorted()) {
7683
+ const specifier = `${packageName}/cli`;
7684
+ let modulePath;
7685
+ try {
7686
+ modulePath = requireFromProject.resolve(specifier);
7687
+ } catch (error) {
7688
+ if (isMissingContribution(error)) continue;
7689
+ throw error;
7690
+ }
7691
+ const loaded = await import(pathToFileURL(modulePath).href);
7692
+ const value = loaded.dinewayCliContribution ?? loaded.default;
7693
+ if (!isDinewayCliContribution(value)) throw new Error(`${specifier} does not export a valid Dineway CLI contribution`);
7694
+ contributions.push({
7695
+ packageName,
7696
+ commands: value.commands
7697
+ });
7698
+ }
7699
+ return contributions;
7700
+ }
7701
+ async function findProjectPackage(from) {
7702
+ let directory = resolve(from);
7703
+ while (true) {
7704
+ const packagePath = join(directory, "package.json");
7705
+ const project = await readProjectPackage(packagePath);
7706
+ if (project) return {
7707
+ packagePath,
7708
+ project
7709
+ };
7710
+ const parent = dirname(directory);
7711
+ if (parent === directory) return null;
7712
+ directory = parent;
7713
+ }
7714
+ }
7715
+ function mergeDinewayCliContributions(builtIns, contributions) {
7716
+ const commands = { ...builtIns };
7717
+ for (const contribution of contributions) for (const [name, command] of Object.entries(contribution.commands).toSorted(([left], [right]) => left.localeCompare(right))) {
7718
+ if (commands[name]) throw new Error(`Dineway CLI command "${name}" from ${contribution.packageName} conflicts with an existing command`);
7719
+ commands[name] = command;
7720
+ }
7721
+ return commands;
7722
+ }
7723
+ async function readProjectPackage(path) {
7724
+ try {
7725
+ const parsed = JSON.parse(await readFile(path, "utf8"));
7726
+ return isRecord(parsed) ? parsed : null;
7727
+ } catch (error) {
7728
+ if (isFileNotFound(error)) return null;
7729
+ throw error;
7730
+ }
7731
+ }
7732
+ function isDinewayPluginPackage(packageName) {
7733
+ return DINEWAY_PLUGIN_PACKAGE.test(packageName);
7734
+ }
7735
+ function isDinewayCliContribution(value) {
7736
+ if (!isRecord(value) || !isRecord(value.commands)) return false;
7737
+ return Object.entries(value.commands).every(([name, command]) => CLI_COMMAND_NAME.test(name) && isRecord(command));
7738
+ }
7739
+ function isRecord(value) {
7740
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7741
+ }
7742
+ function isMissingContribution(error) {
7743
+ return error instanceof Error && "code" in error && (error.code === "MODULE_NOT_FOUND" || error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED");
7744
+ }
7745
+ function isFileNotFound(error) {
7746
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
7747
+ }
7748
+
7485
7749
  //#endregion
7486
7750
  //#region src/cli/index.ts
7487
7751
  /**
@@ -7507,36 +7771,40 @@ const typesCommand = defineCommand({
7507
7771
  * - deploy: Deploy a standard Dineway Node server through a provider CLI
7508
7772
  * - plugin: Plugin management (init, bundle, validate, publish, login, logout)
7509
7773
  */
7510
- runMain(defineCommand({
7511
- meta: {
7512
- name: "dineway",
7513
- version: "0.0.0",
7514
- description: "CLI for Dineway Agentic Web builder"
7515
- },
7516
- subCommands: {
7517
- init: initCommand,
7518
- account: accountCommand,
7519
- types: typesCommand,
7520
- dev: devCommand,
7521
- deploy: deployCommand,
7522
- doctor: doctorCommand,
7523
- seed: seedCommand,
7524
- "export-seed": exportSeedCommand,
7525
- secrets: secretsCommand,
7526
- auth: authCommand,
7527
- login: loginCommand,
7528
- logout: logoutCommand,
7529
- whoami: whoamiCommand,
7530
- content: contentCommand,
7531
- schema: schemaCommand,
7532
- media: mediaCommand,
7533
- search: searchCommand,
7534
- seo: seoCommand,
7535
- taxonomy: taxonomyCommand,
7536
- menu: menuCommand,
7537
- plugin: pluginCommand
7538
- }
7539
- }));
7774
+ const builtInCommands = {
7775
+ init: initCommand,
7776
+ account: accountCommand,
7777
+ types: typesCommand,
7778
+ dev: devCommand,
7779
+ deploy: deployCommand,
7780
+ doctor: doctorCommand,
7781
+ seed: seedCommand,
7782
+ "export-seed": exportSeedCommand,
7783
+ secrets: secretsCommand,
7784
+ auth: authCommand,
7785
+ login: loginCommand,
7786
+ logout: logoutCommand,
7787
+ whoami: whoamiCommand,
7788
+ content: contentCommand,
7789
+ schema: schemaCommand,
7790
+ media: mediaCommand,
7791
+ search: searchCommand,
7792
+ seo: seoCommand,
7793
+ taxonomy: taxonomyCommand,
7794
+ menu: menuCommand,
7795
+ plugin: pluginCommand
7796
+ };
7797
+ async function createDinewayCliCommand(cwd = process.cwd()) {
7798
+ return defineCommand({
7799
+ meta: {
7800
+ name: "dineway",
7801
+ version: "0.0.0",
7802
+ description: "CLI for Dineway Agentic Web builder"
7803
+ },
7804
+ subCommands: mergeDinewayCliContributions(builtInCommands, await discoverDinewayCliContributions(cwd))
7805
+ });
7806
+ }
7807
+ createDinewayCliCommand().then((main) => runMain(main));
7540
7808
 
7541
7809
  //#endregion
7542
- export { };
7810
+ export { createDinewayCliCommand };