@wootsup/yt-builder-mcp 1.12.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (869) hide show
  1. package/README.md +139 -40
  2. package/bin/node-floor.d.ts +28 -0
  3. package/bin/node-floor.js +150 -0
  4. package/bin/yt-builder-mcp.js +51 -10
  5. package/dist/auth.d.ts +3 -121
  6. package/dist/auth.d.ts.map +1 -1
  7. package/dist/auth.js +44 -174
  8. package/dist/auth.js.map +1 -1
  9. package/dist/catalog/build-catalog.d.ts +4 -49
  10. package/dist/catalog/build-catalog.d.ts.map +1 -1
  11. package/dist/catalog/build-catalog.js +15 -126
  12. package/dist/catalog/build-catalog.js.map +1 -1
  13. package/dist/catalog/json-schema.d.ts +3 -0
  14. package/dist/catalog/json-schema.d.ts.map +1 -0
  15. package/dist/catalog/json-schema.js +68 -0
  16. package/dist/catalog/json-schema.js.map +1 -0
  17. package/dist/catalog/output-shapes.d.ts +6 -0
  18. package/dist/catalog/output-shapes.d.ts.map +1 -0
  19. package/dist/catalog/output-shapes.js +89 -0
  20. package/dist/catalog/output-shapes.js.map +1 -0
  21. package/dist/catalog/tool-catalog-markdown.d.ts +1 -37
  22. package/dist/catalog/tool-catalog-markdown.d.ts.map +1 -1
  23. package/dist/catalog/tool-catalog-markdown.js +8 -52
  24. package/dist/catalog/tool-catalog-markdown.js.map +1 -1
  25. package/dist/cli/doctor-command.d.ts +2 -7
  26. package/dist/cli/doctor-command.d.ts.map +1 -1
  27. package/dist/cli/doctor-command.js +21 -62
  28. package/dist/cli/doctor-command.js.map +1 -1
  29. package/dist/cli/invocation-decision.d.ts +4 -52
  30. package/dist/cli/invocation-decision.d.ts.map +1 -1
  31. package/dist/cli/invocation-decision.js +46 -55
  32. package/dist/cli/invocation-decision.js.map +1 -1
  33. package/dist/client.d.ts +3 -107
  34. package/dist/client.d.ts.map +1 -1
  35. package/dist/client.js +34 -115
  36. package/dist/client.js.map +1 -1
  37. package/dist/clients/claude-code.d.ts +0 -15
  38. package/dist/clients/claude-code.d.ts.map +1 -1
  39. package/dist/clients/claude-code.js +1 -23
  40. package/dist/clients/claude-code.js.map +1 -1
  41. package/dist/clients/claude-desktop.d.ts +0 -16
  42. package/dist/clients/claude-desktop.d.ts.map +1 -1
  43. package/dist/clients/claude-desktop.js +1 -19
  44. package/dist/clients/claude-desktop.js.map +1 -1
  45. package/dist/clients/cline.d.ts +0 -23
  46. package/dist/clients/cline.d.ts.map +1 -1
  47. package/dist/clients/cline.js +1 -34
  48. package/dist/clients/cline.js.map +1 -1
  49. package/dist/clients/codex-cli.d.ts +0 -39
  50. package/dist/clients/codex-cli.d.ts.map +1 -1
  51. package/dist/clients/codex-cli.js +12 -147
  52. package/dist/clients/codex-cli.js.map +1 -1
  53. package/dist/clients/config-file.d.ts +15 -0
  54. package/dist/clients/config-file.d.ts.map +1 -0
  55. package/dist/clients/config-file.js +86 -0
  56. package/dist/clients/config-file.js.map +1 -0
  57. package/dist/clients/continue.d.ts +0 -10
  58. package/dist/clients/continue.d.ts.map +1 -1
  59. package/dist/clients/continue.js +3 -30
  60. package/dist/clients/continue.js.map +1 -1
  61. package/dist/clients/cursor.d.ts +0 -9
  62. package/dist/clients/cursor.d.ts.map +1 -1
  63. package/dist/clients/cursor.js +1 -12
  64. package/dist/clients/cursor.js.map +1 -1
  65. package/dist/clients/gemini-cli.d.ts +0 -15
  66. package/dist/clients/gemini-cli.d.ts.map +1 -1
  67. package/dist/clients/gemini-cli.js +1 -18
  68. package/dist/clients/gemini-cli.js.map +1 -1
  69. package/dist/clients/home.d.ts +0 -12
  70. package/dist/clients/home.d.ts.map +1 -1
  71. package/dist/clients/home.js +0 -12
  72. package/dist/clients/home.js.map +1 -1
  73. package/dist/clients/index.d.ts +2 -153
  74. package/dist/clients/index.d.ts.map +1 -1
  75. package/dist/clients/index.js +3 -153
  76. package/dist/clients/index.js.map +1 -1
  77. package/dist/clients/roo-code.d.ts +0 -20
  78. package/dist/clients/roo-code.d.ts.map +1 -1
  79. package/dist/clients/roo-code.js +1 -23
  80. package/dist/clients/roo-code.js.map +1 -1
  81. package/dist/clients/zed.d.ts +0 -9
  82. package/dist/clients/zed.d.ts.map +1 -1
  83. package/dist/clients/zed.js +1 -19
  84. package/dist/clients/zed.js.map +1 -1
  85. package/dist/compat-floor.d.ts +35 -0
  86. package/dist/compat-floor.d.ts.map +1 -0
  87. package/dist/compat-floor.js +84 -0
  88. package/dist/compat-floor.js.map +1 -0
  89. package/dist/compat-probe.d.ts +6 -0
  90. package/dist/compat-probe.d.ts.map +1 -0
  91. package/dist/compat-probe.js +20 -0
  92. package/dist/compat-probe.js.map +1 -0
  93. package/dist/diagnostics/ca-reexec.d.ts +0 -30
  94. package/dist/diagnostics/ca-reexec.d.ts.map +1 -1
  95. package/dist/diagnostics/ca-reexec.js +0 -62
  96. package/dist/diagnostics/ca-reexec.js.map +1 -1
  97. package/dist/diagnostics/ca-trust.d.ts +0 -22
  98. package/dist/diagnostics/ca-trust.d.ts.map +1 -1
  99. package/dist/diagnostics/ca-trust.js +3 -42
  100. package/dist/diagnostics/ca-trust.js.map +1 -1
  101. package/dist/diagnostics/diagnose-network-error.d.ts +1 -38
  102. package/dist/diagnostics/diagnose-network-error.d.ts.map +1 -1
  103. package/dist/diagnostics/diagnose-network-error.js +27 -38
  104. package/dist/diagnostics/diagnose-network-error.js.map +1 -1
  105. package/dist/diagnostics/doctor.d.ts +1 -85
  106. package/dist/diagnostics/doctor.d.ts.map +1 -1
  107. package/dist/diagnostics/doctor.js +7 -92
  108. package/dist/diagnostics/doctor.js.map +1 -1
  109. package/dist/diagnostics/startup-self-probe.d.ts +3 -25
  110. package/dist/diagnostics/startup-self-probe.d.ts.map +1 -1
  111. package/dist/diagnostics/startup-self-probe.js +11 -48
  112. package/dist/diagnostics/startup-self-probe.js.map +1 -1
  113. package/dist/errors/hints.d.ts +0 -44
  114. package/dist/errors/hints.d.ts.map +1 -1
  115. package/dist/errors/hints.js +1 -33
  116. package/dist/errors/hints.js.map +1 -1
  117. package/dist/errors/mask.d.ts +0 -31
  118. package/dist/errors/mask.d.ts.map +1 -1
  119. package/dist/errors/mask.js +0 -32
  120. package/dist/errors/mask.js.map +1 -1
  121. package/dist/errors/redact-credentials.d.ts +2 -0
  122. package/dist/errors/redact-credentials.d.ts.map +1 -0
  123. package/dist/errors/redact-credentials.js +13 -0
  124. package/dist/errors/redact-credentials.js.map +1 -0
  125. package/dist/errors/sanitize.d.ts +0 -29
  126. package/dist/errors/sanitize.d.ts.map +1 -1
  127. package/dist/errors/sanitize.js +0 -40
  128. package/dist/errors/sanitize.js.map +1 -1
  129. package/dist/errors.d.ts +6 -17
  130. package/dist/errors.d.ts.map +1 -1
  131. package/dist/errors.js +16 -21
  132. package/dist/errors.js.map +1 -1
  133. package/dist/gateway/advanced-read-tool.d.ts +0 -32
  134. package/dist/gateway/advanced-read-tool.d.ts.map +1 -1
  135. package/dist/gateway/advanced-read-tool.js +43 -111
  136. package/dist/gateway/advanced-read-tool.js.map +1 -1
  137. package/dist/gateway/advanced-tool/discovery.d.ts +1 -12
  138. package/dist/gateway/advanced-tool/discovery.d.ts.map +1 -1
  139. package/dist/gateway/advanced-tool/discovery.js +15 -24
  140. package/dist/gateway/advanced-tool/discovery.js.map +1 -1
  141. package/dist/gateway/advanced-tool/domains.d.ts +7 -66
  142. package/dist/gateway/advanced-tool/domains.d.ts.map +1 -1
  143. package/dist/gateway/advanced-tool/domains.js +36 -80
  144. package/dist/gateway/advanced-tool/domains.js.map +1 -1
  145. package/dist/gateway/advanced-tool/execute.d.ts +0 -25
  146. package/dist/gateway/advanced-tool/execute.d.ts.map +1 -1
  147. package/dist/gateway/advanced-tool/execute.js +13 -49
  148. package/dist/gateway/advanced-tool/execute.js.map +1 -1
  149. package/dist/gateway/advanced-tool/index.d.ts +1 -32
  150. package/dist/gateway/advanced-tool/index.d.ts.map +1 -1
  151. package/dist/gateway/advanced-tool/index.js +1 -35
  152. package/dist/gateway/advanced-tool/index.js.map +1 -1
  153. package/dist/gateway/advanced-tool/register.d.ts +0 -14
  154. package/dist/gateway/advanced-tool/register.d.ts.map +1 -1
  155. package/dist/gateway/advanced-tool/register.js +22 -58
  156. package/dist/gateway/advanced-tool/register.js.map +1 -1
  157. package/dist/gateway/advanced-tool.d.ts +0 -11
  158. package/dist/gateway/advanced-tool.d.ts.map +1 -1
  159. package/dist/gateway/advanced-tool.js +0 -11
  160. package/dist/gateway/advanced-tool.js.map +1 -1
  161. package/dist/gateway/capturing-server.d.ts +3 -88
  162. package/dist/gateway/capturing-server.d.ts.map +1 -1
  163. package/dist/gateway/capturing-server.js +0 -77
  164. package/dist/gateway/capturing-server.js.map +1 -1
  165. package/dist/gateway/essentials.d.ts +1 -63
  166. package/dist/gateway/essentials.d.ts.map +1 -1
  167. package/dist/gateway/essentials.js +9 -178
  168. package/dist/gateway/essentials.js.map +1 -1
  169. package/dist/gateway/test-support.d.ts +0 -28
  170. package/dist/gateway/test-support.d.ts.map +1 -1
  171. package/dist/gateway/test-support.js +0 -29
  172. package/dist/gateway/test-support.js.map +1 -1
  173. package/dist/health-response.d.ts +2 -0
  174. package/dist/health-response.d.ts.map +1 -0
  175. package/dist/health-response.js +7 -0
  176. package/dist/health-response.js.map +1 -0
  177. package/dist/icon-data.js +0 -3
  178. package/dist/icon-data.js.map +1 -1
  179. package/dist/index.d.ts +7 -23
  180. package/dist/index.d.ts.map +1 -1
  181. package/dist/index.js +46 -130
  182. package/dist/index.js.map +1 -1
  183. package/dist/install-skill.d.ts +0 -44
  184. package/dist/install-skill.d.ts.map +1 -1
  185. package/dist/install-skill.js +8 -48
  186. package/dist/install-skill.js.map +1 -1
  187. package/dist/net/extra-headers.d.ts +0 -58
  188. package/dist/net/extra-headers.d.ts.map +1 -1
  189. package/dist/net/extra-headers.js +0 -67
  190. package/dist/net/extra-headers.js.map +1 -1
  191. package/dist/net/internal-host.d.ts +0 -31
  192. package/dist/net/internal-host.d.ts.map +1 -1
  193. package/dist/net/internal-host.js +7 -65
  194. package/dist/net/internal-host.js.map +1 -1
  195. package/dist/net/proxy-dispatcher.d.ts +0 -37
  196. package/dist/net/proxy-dispatcher.d.ts.map +1 -1
  197. package/dist/net/proxy-dispatcher.js +0 -40
  198. package/dist/net/proxy-dispatcher.js.map +1 -1
  199. package/dist/net/public-fetch.d.ts +53 -0
  200. package/dist/net/public-fetch.d.ts.map +1 -0
  201. package/dist/net/public-fetch.js +143 -0
  202. package/dist/net/public-fetch.js.map +1 -0
  203. package/dist/net/public-page-report.d.ts +26 -0
  204. package/dist/net/public-page-report.d.ts.map +1 -0
  205. package/dist/net/public-page-report.js +145 -0
  206. package/dist/net/public-page-report.js.map +1 -0
  207. package/dist/net/redirect-safe-fetch.d.ts +14 -0
  208. package/dist/net/redirect-safe-fetch.d.ts.map +1 -0
  209. package/dist/net/redirect-safe-fetch.js +61 -0
  210. package/dist/net/redirect-safe-fetch.js.map +1 -0
  211. package/dist/net/session-recovery-fetch.d.ts +7 -0
  212. package/dist/net/session-recovery-fetch.d.ts.map +1 -0
  213. package/dist/net/session-recovery-fetch.js +139 -0
  214. package/dist/net/session-recovery-fetch.js.map +1 -0
  215. package/dist/net/site-basic-auth.d.ts +1 -59
  216. package/dist/net/site-basic-auth.d.ts.map +1 -1
  217. package/dist/net/site-basic-auth.js +5 -94
  218. package/dist/net/site-basic-auth.js.map +1 -1
  219. package/dist/package-version.d.ts +2 -0
  220. package/dist/package-version.d.ts.map +1 -0
  221. package/dist/package-version.js +15 -0
  222. package/dist/package-version.js.map +1 -0
  223. package/dist/platform/detect.d.ts +0 -54
  224. package/dist/platform/detect.d.ts.map +1 -1
  225. package/dist/platform/detect.js +0 -53
  226. package/dist/platform/detect.js.map +1 -1
  227. package/dist/platform/index.d.ts +0 -100
  228. package/dist/platform/index.d.ts.map +1 -1
  229. package/dist/platform/index.js +0 -106
  230. package/dist/platform/index.js.map +1 -1
  231. package/dist/platform/joomla.d.ts +0 -35
  232. package/dist/platform/joomla.d.ts.map +1 -1
  233. package/dist/platform/joomla.js +0 -35
  234. package/dist/platform/joomla.js.map +1 -1
  235. package/dist/product-name.d.ts +2 -0
  236. package/dist/product-name.d.ts.map +1 -0
  237. package/dist/product-name.js +2 -0
  238. package/dist/product-name.js.map +1 -0
  239. package/dist/proxy/bridge.d.ts +36 -76
  240. package/dist/proxy/bridge.d.ts.map +1 -1
  241. package/dist/proxy/bridge.js +443 -136
  242. package/dist/proxy/bridge.js.map +1 -1
  243. package/dist/proxy/confirm-elicitation.d.ts +22 -0
  244. package/dist/proxy/confirm-elicitation.d.ts.map +1 -0
  245. package/dist/proxy/confirm-elicitation.js +170 -0
  246. package/dist/proxy/confirm-elicitation.js.map +1 -0
  247. package/dist/proxy/confirm-question-contract.d.ts +6 -0
  248. package/dist/proxy/confirm-question-contract.d.ts.map +1 -0
  249. package/dist/proxy/confirm-question-contract.js +9 -0
  250. package/dist/proxy/confirm-question-contract.js.map +1 -0
  251. package/dist/proxy/confirm-question.d.ts +2 -0
  252. package/dist/proxy/confirm-question.d.ts.map +1 -0
  253. package/dist/proxy/confirm-question.js +125 -0
  254. package/dist/proxy/confirm-question.js.map +1 -0
  255. package/dist/proxy/instructions.d.ts +4 -0
  256. package/dist/proxy/instructions.d.ts.map +1 -0
  257. package/dist/proxy/instructions.js +12 -0
  258. package/dist/proxy/instructions.js.map +1 -0
  259. package/dist/proxy/mode.d.ts +14 -9
  260. package/dist/proxy/mode.d.ts.map +1 -1
  261. package/dist/proxy/mode.js +58 -34
  262. package/dist/proxy/mode.js.map +1 -1
  263. package/dist/proxy/multi-site.d.ts +42 -0
  264. package/dist/proxy/multi-site.d.ts.map +1 -0
  265. package/dist/proxy/multi-site.js +134 -0
  266. package/dist/proxy/multi-site.js.map +1 -0
  267. package/dist/proxy/request-headers.d.ts +9 -0
  268. package/dist/proxy/request-headers.d.ts.map +1 -0
  269. package/dist/proxy/request-headers.js +17 -0
  270. package/dist/proxy/request-headers.js.map +1 -0
  271. package/dist/proxy/shadow-tools.d.ts +9 -0
  272. package/dist/proxy/shadow-tools.d.ts.map +1 -0
  273. package/dist/proxy/shadow-tools.js +42 -0
  274. package/dist/proxy/shadow-tools.js.map +1 -0
  275. package/dist/proxy/tools-list-cap.d.ts +7 -0
  276. package/dist/proxy/tools-list-cap.d.ts.map +1 -0
  277. package/dist/proxy/tools-list-cap.js +14 -0
  278. package/dist/proxy/tools-list-cap.js.map +1 -0
  279. package/dist/proxy/transport-uncertainty.d.ts +2 -0
  280. package/dist/proxy/transport-uncertainty.d.ts.map +1 -0
  281. package/dist/proxy/transport-uncertainty.js +8 -0
  282. package/dist/proxy/transport-uncertainty.js.map +1 -0
  283. package/dist/proxy/upstream-error.d.ts +7 -0
  284. package/dist/proxy/upstream-error.d.ts.map +1 -0
  285. package/dist/proxy/upstream-error.js +35 -0
  286. package/dist/proxy/upstream-error.js.map +1 -0
  287. package/dist/proxy/wire-dialect.d.ts +5 -0
  288. package/dist/proxy/wire-dialect.d.ts.map +1 -0
  289. package/dist/proxy/wire-dialect.js +33 -0
  290. package/dist/proxy/wire-dialect.js.map +1 -0
  291. package/dist/server.d.ts +14 -82
  292. package/dist/server.d.ts.map +1 -1
  293. package/dist/server.js +109 -204
  294. package/dist/server.js.map +1 -1
  295. package/dist/setup/banner-color.d.ts +13 -0
  296. package/dist/setup/banner-color.d.ts.map +1 -0
  297. package/dist/setup/banner-color.js +93 -0
  298. package/dist/setup/banner-color.js.map +1 -0
  299. package/dist/setup/banner.d.ts +22 -0
  300. package/dist/setup/banner.d.ts.map +1 -0
  301. package/dist/setup/banner.js +120 -0
  302. package/dist/setup/banner.js.map +1 -0
  303. package/dist/setup/ca-recovery.d.ts +20 -0
  304. package/dist/setup/ca-recovery.d.ts.map +1 -0
  305. package/dist/setup/ca-recovery.js +92 -0
  306. package/dist/setup/ca-recovery.js.map +1 -0
  307. package/dist/setup/closing.d.ts +15 -0
  308. package/dist/setup/closing.d.ts.map +1 -0
  309. package/dist/setup/closing.js +43 -0
  310. package/dist/setup/closing.js.map +1 -0
  311. package/dist/setup/corporate-ca.d.ts +16 -0
  312. package/dist/setup/corporate-ca.d.ts.map +1 -0
  313. package/dist/setup/corporate-ca.js +32 -0
  314. package/dist/setup/corporate-ca.js.map +1 -0
  315. package/dist/setup/host-platform.d.ts +8 -0
  316. package/dist/setup/host-platform.d.ts.map +1 -0
  317. package/dist/setup/host-platform.js +21 -0
  318. package/dist/setup/host-platform.js.map +1 -0
  319. package/dist/setup/install-configs.d.ts +18 -0
  320. package/dist/setup/install-configs.d.ts.map +1 -0
  321. package/dist/setup/install-configs.js +95 -0
  322. package/dist/setup/install-configs.js.map +1 -0
  323. package/dist/setup/multi-site-loop.d.ts +14 -0
  324. package/dist/setup/multi-site-loop.d.ts.map +1 -0
  325. package/dist/setup/multi-site-loop.js +20 -0
  326. package/dist/setup/multi-site-loop.js.map +1 -0
  327. package/dist/setup/phases.d.ts +18 -0
  328. package/dist/setup/phases.d.ts.map +1 -0
  329. package/dist/setup/phases.js +55 -0
  330. package/dist/setup/phases.js.map +1 -0
  331. package/dist/setup/registry-remedy.d.ts +12 -0
  332. package/dist/setup/registry-remedy.d.ts.map +1 -0
  333. package/dist/setup/registry-remedy.js +37 -0
  334. package/dist/setup/registry-remedy.js.map +1 -0
  335. package/dist/setup/resume.d.ts +28 -0
  336. package/dist/setup/resume.d.ts.map +1 -0
  337. package/dist/setup/resume.js +99 -0
  338. package/dist/setup/resume.js.map +1 -0
  339. package/dist/setup-ca-env.d.ts +0 -30
  340. package/dist/setup-ca-env.d.ts.map +1 -1
  341. package/dist/setup-ca-env.js +0 -28
  342. package/dist/setup-ca-env.js.map +1 -1
  343. package/dist/setup-ca-reprobe.d.ts +0 -25
  344. package/dist/setup-ca-reprobe.d.ts.map +1 -1
  345. package/dist/setup-ca-reprobe.js +0 -34
  346. package/dist/setup-ca-reprobe.js.map +1 -1
  347. package/dist/setup-cli-confirm.d.ts +0 -34
  348. package/dist/setup-cli-confirm.d.ts.map +1 -1
  349. package/dist/setup-cli-confirm.js +0 -36
  350. package/dist/setup-cli-confirm.js.map +1 -1
  351. package/dist/setup-cli.d.ts +6 -167
  352. package/dist/setup-cli.d.ts.map +1 -1
  353. package/dist/setup-cli.js +165 -177
  354. package/dist/setup-cli.js.map +1 -1
  355. package/dist/setup-npx-spec.d.ts +0 -36
  356. package/dist/setup-npx-spec.d.ts.map +1 -1
  357. package/dist/setup-npx-spec.js +0 -38
  358. package/dist/setup-npx-spec.js.map +1 -1
  359. package/dist/setup-prompts.d.ts +5 -70
  360. package/dist/setup-prompts.d.ts.map +1 -1
  361. package/dist/setup-prompts.js +67 -107
  362. package/dist/setup-prompts.js.map +1 -1
  363. package/dist/setup-token.d.ts +0 -34
  364. package/dist/setup-token.d.ts.map +1 -1
  365. package/dist/setup-token.js +0 -40
  366. package/dist/setup-token.js.map +1 -1
  367. package/dist/setup-wizard-defaults.d.ts +2 -37
  368. package/dist/setup-wizard-defaults.d.ts.map +1 -1
  369. package/dist/setup-wizard-defaults.js +28 -93
  370. package/dist/setup-wizard-defaults.js.map +1 -1
  371. package/dist/setup-wizard-handshake.d.ts +1 -19
  372. package/dist/setup-wizard-handshake.d.ts.map +1 -1
  373. package/dist/setup-wizard-handshake.js +8 -45
  374. package/dist/setup-wizard-handshake.js.map +1 -1
  375. package/dist/setup-wizard-types.d.ts +18 -217
  376. package/dist/setup-wizard-types.d.ts.map +1 -1
  377. package/dist/setup-wizard-types.js +0 -9
  378. package/dist/setup-wizard-types.js.map +1 -1
  379. package/dist/setup-wizard-write-types.d.ts +19 -0
  380. package/dist/setup-wizard-write-types.d.ts.map +1 -0
  381. package/dist/setup-wizard-write-types.js +2 -0
  382. package/dist/setup-wizard-write-types.js.map +1 -0
  383. package/dist/setup-wizard.d.ts +0 -28
  384. package/dist/setup-wizard.d.ts.map +1 -1
  385. package/dist/setup-wizard.js +167 -412
  386. package/dist/setup-wizard.js.map +1 -1
  387. package/dist/setup.d.ts +0 -15
  388. package/dist/setup.d.ts.map +1 -1
  389. package/dist/setup.js +0 -15
  390. package/dist/setup.js.map +1 -1
  391. package/dist/sites/bootstrap.d.ts +0 -38
  392. package/dist/sites/bootstrap.d.ts.map +1 -1
  393. package/dist/sites/bootstrap.js +0 -44
  394. package/dist/sites/bootstrap.js.map +1 -1
  395. package/dist/sites/cli/add-site.d.ts +0 -76
  396. package/dist/sites/cli/add-site.d.ts.map +1 -1
  397. package/dist/sites/cli/add-site.js +0 -71
  398. package/dist/sites/cli/add-site.js.map +1 -1
  399. package/dist/sites/cli/list-sites.d.ts +0 -31
  400. package/dist/sites/cli/list-sites.d.ts.map +1 -1
  401. package/dist/sites/cli/list-sites.js +0 -45
  402. package/dist/sites/cli/list-sites.js.map +1 -1
  403. package/dist/sites/cli/remove-site.d.ts +0 -37
  404. package/dist/sites/cli/remove-site.d.ts.map +1 -1
  405. package/dist/sites/cli/remove-site.js +0 -37
  406. package/dist/sites/cli/remove-site.js.map +1 -1
  407. package/dist/sites/cli/set-default.d.ts +0 -25
  408. package/dist/sites/cli/set-default.d.ts.map +1 -1
  409. package/dist/sites/cli/set-default.js +0 -22
  410. package/dist/sites/cli/set-default.js.map +1 -1
  411. package/dist/sites/cli/test-site.d.ts +0 -44
  412. package/dist/sites/cli/test-site.d.ts.map +1 -1
  413. package/dist/sites/cli/test-site.js +0 -39
  414. package/dist/sites/cli/test-site.js.map +1 -1
  415. package/dist/sites/client-pool.d.ts +11 -164
  416. package/dist/sites/client-pool.d.ts.map +1 -1
  417. package/dist/sites/client-pool.js +73 -194
  418. package/dist/sites/client-pool.js.map +1 -1
  419. package/dist/sites/config-problem.d.ts +2 -0
  420. package/dist/sites/config-problem.d.ts.map +1 -0
  421. package/dist/sites/config-problem.js +13 -0
  422. package/dist/sites/config-problem.js.map +1 -0
  423. package/dist/sites/env-bridge.d.ts +0 -69
  424. package/dist/sites/env-bridge.d.ts.map +1 -1
  425. package/dist/sites/env-bridge.js +2 -71
  426. package/dist/sites/env-bridge.js.map +1 -1
  427. package/dist/sites/op-inject.d.ts +23 -0
  428. package/dist/sites/op-inject.d.ts.map +1 -0
  429. package/dist/sites/op-inject.js +131 -0
  430. package/dist/sites/op-inject.js.map +1 -0
  431. package/dist/sites/paths.d.ts +0 -19
  432. package/dist/sites/paths.d.ts.map +1 -1
  433. package/dist/sites/paths.js +0 -19
  434. package/dist/sites/paths.js.map +1 -1
  435. package/dist/sites/probe.d.ts +1 -47
  436. package/dist/sites/probe.d.ts.map +1 -1
  437. package/dist/sites/probe.js +4 -46
  438. package/dist/sites/probe.js.map +1 -1
  439. package/dist/sites/registry.d.ts +4 -135
  440. package/dist/sites/registry.d.ts.map +1 -1
  441. package/dist/sites/registry.js +4 -98
  442. package/dist/sites/registry.js.map +1 -1
  443. package/dist/sites/schema.d.ts +3 -51
  444. package/dist/sites/schema.d.ts.map +1 -1
  445. package/dist/sites/schema.js +6 -52
  446. package/dist/sites/schema.js.map +1 -1
  447. package/dist/sites/secret-resolver.d.ts +6 -76
  448. package/dist/sites/secret-resolver.d.ts.map +1 -1
  449. package/dist/sites/secret-resolver.js +39 -104
  450. package/dist/sites/secret-resolver.js.map +1 -1
  451. package/dist/sites/store.d.ts +12 -69
  452. package/dist/sites/store.d.ts.map +1 -1
  453. package/dist/sites/store.js +37 -86
  454. package/dist/sites/store.js.map +1 -1
  455. package/dist/sites/substituted-registry.d.ts +11 -0
  456. package/dist/sites/substituted-registry.d.ts.map +1 -0
  457. package/dist/sites/substituted-registry.js +13 -0
  458. package/dist/sites/substituted-registry.js.map +1 -0
  459. package/dist/sites/tools/fetch-public-url.d.ts +4 -0
  460. package/dist/sites/tools/fetch-public-url.d.ts.map +1 -0
  461. package/dist/sites/tools/fetch-public-url.js +231 -0
  462. package/dist/sites/tools/fetch-public-url.js.map +1 -0
  463. package/dist/sites/tools/index.d.ts +2 -26
  464. package/dist/sites/tools/index.d.ts.map +1 -1
  465. package/dist/sites/tools/index.js +5 -28
  466. package/dist/sites/tools/index.js.map +1 -1
  467. package/dist/sites/tools/sites-list.d.ts +3 -47
  468. package/dist/sites/tools/sites-list.d.ts.map +1 -1
  469. package/dist/sites/tools/sites-list.js +65 -79
  470. package/dist/sites/tools/sites-list.js.map +1 -1
  471. package/dist/sites/tools/sites-overview.d.ts +4 -0
  472. package/dist/sites/tools/sites-overview.d.ts.map +1 -0
  473. package/dist/sites/tools/sites-overview.js +209 -0
  474. package/dist/sites/tools/sites-overview.js.map +1 -0
  475. package/dist/sites/tools/sites-test.d.ts +2 -45
  476. package/dist/sites/tools/sites-test.d.ts.map +1 -1
  477. package/dist/sites/tools/sites-test.js +2 -56
  478. package/dist/sites/tools/sites-test.js.map +1 -1
  479. package/dist/sites/tools/use-site.d.ts +2 -45
  480. package/dist/sites/tools/use-site.d.ts.map +1 -1
  481. package/dist/sites/tools/use-site.js +31 -79
  482. package/dist/sites/tools/use-site.js.map +1 -1
  483. package/dist/sites/unknown-name-result.d.ts +10 -0
  484. package/dist/sites/unknown-name-result.d.ts.map +1 -0
  485. package/dist/sites/unknown-name-result.js +13 -0
  486. package/dist/sites/unknown-name-result.js.map +1 -0
  487. package/dist/skill-instructions.d.ts +7 -0
  488. package/dist/skill-instructions.d.ts.map +1 -0
  489. package/dist/skill-instructions.js +85 -0
  490. package/dist/skill-instructions.js.map +1 -0
  491. package/dist/skill-loader.d.ts +1 -103
  492. package/dist/skill-loader.d.ts.map +1 -1
  493. package/dist/skill-loader.js +6 -143
  494. package/dist/skill-loader.js.map +1 -1
  495. package/dist/skill-manifest.d.ts +0 -20
  496. package/dist/skill-manifest.d.ts.map +1 -1
  497. package/dist/skill-manifest.js +1 -21
  498. package/dist/skill-manifest.js.map +1 -1
  499. package/dist/skill-topics.d.ts +19 -0
  500. package/dist/skill-topics.d.ts.map +1 -0
  501. package/dist/skill-topics.js +181 -0
  502. package/dist/skill-topics.js.map +1 -0
  503. package/dist/tools/active-site.d.ts +16 -0
  504. package/dist/tools/active-site.d.ts.map +1 -0
  505. package/dist/tools/active-site.js +55 -0
  506. package/dist/tools/active-site.js.map +1 -0
  507. package/dist/tools/bind-guidance.d.ts +2 -72
  508. package/dist/tools/bind-guidance.d.ts.map +1 -1
  509. package/dist/tools/bind-guidance.js +2 -72
  510. package/dist/tools/bind-guidance.js.map +1 -1
  511. package/dist/tools/budgeted-table.d.ts +14 -19
  512. package/dist/tools/budgeted-table.d.ts.map +1 -1
  513. package/dist/tools/budgeted-table.js +171 -105
  514. package/dist/tools/budgeted-table.js.map +1 -1
  515. package/dist/tools/elements/bind-output-schema.d.ts +69 -0
  516. package/dist/tools/elements/bind-output-schema.d.ts.map +1 -0
  517. package/dist/tools/elements/bind-output-schema.js +64 -0
  518. package/dist/tools/elements/bind-output-schema.js.map +1 -0
  519. package/dist/tools/elements/builders.d.ts +1 -22
  520. package/dist/tools/elements/builders.d.ts.map +1 -1
  521. package/dist/tools/elements/builders.js +147 -133
  522. package/dist/tools/elements/builders.js.map +1 -1
  523. package/dist/tools/elements/index.d.ts +0 -26
  524. package/dist/tools/elements/index.d.ts.map +1 -1
  525. package/dist/tools/elements/index.js +0 -25
  526. package/dist/tools/elements/index.js.map +1 -1
  527. package/dist/tools/elements/read-output-schemas.d.ts +121 -0
  528. package/dist/tools/elements/read-output-schemas.d.ts.map +1 -0
  529. package/dist/tools/elements/read-output-schemas.js +225 -0
  530. package/dist/tools/elements/read-output-schemas.js.map +1 -0
  531. package/dist/tools/elements/write-output-schemas.d.ts +185 -0
  532. package/dist/tools/elements/write-output-schemas.d.ts.map +1 -0
  533. package/dist/tools/elements/write-output-schemas.js +98 -0
  534. package/dist/tools/elements/write-output-schemas.js.map +1 -0
  535. package/dist/tools/elements.d.ts +0 -11
  536. package/dist/tools/elements.d.ts.map +1 -1
  537. package/dist/tools/elements.js +0 -11
  538. package/dist/tools/elements.js.map +1 -1
  539. package/dist/tools/format/health-format.d.ts +26 -43
  540. package/dist/tools/format/health-format.d.ts.map +1 -1
  541. package/dist/tools/format/health-format.js +32 -63
  542. package/dist/tools/format/health-format.js.map +1 -1
  543. package/dist/tools/format/inspection-format.d.ts +2 -54
  544. package/dist/tools/format/inspection-format.d.ts.map +1 -1
  545. package/dist/tools/format/inspection-format.js +33 -119
  546. package/dist/tools/format/inspection-format.js.map +1 -1
  547. package/dist/tools/format/pages-format.d.ts +32 -69
  548. package/dist/tools/format/pages-format.d.ts.map +1 -1
  549. package/dist/tools/format/pages-format.js +114 -139
  550. package/dist/tools/format/pages-format.js.map +1 -1
  551. package/dist/tools/format/sources-format.d.ts +5 -39
  552. package/dist/tools/format/sources-format.d.ts.map +1 -1
  553. package/dist/tools/format/sources-format.js +57 -87
  554. package/dist/tools/format/sources-format.js.map +1 -1
  555. package/dist/tools/health-output-schemas.d.ts +84 -0
  556. package/dist/tools/health-output-schemas.d.ts.map +1 -0
  557. package/dist/tools/health-output-schemas.js +132 -0
  558. package/dist/tools/health-output-schemas.js.map +1 -0
  559. package/dist/tools/health.d.ts +2 -20
  560. package/dist/tools/health.d.ts.map +1 -1
  561. package/dist/tools/health.js +47 -233
  562. package/dist/tools/health.js.map +1 -1
  563. package/dist/tools/index.d.ts +1 -26
  564. package/dist/tools/index.d.ts.map +1 -1
  565. package/dist/tools/index.js +12 -39
  566. package/dist/tools/index.js.map +1 -1
  567. package/dist/tools/inspection.d.ts +4 -34
  568. package/dist/tools/inspection.d.ts.map +1 -1
  569. package/dist/tools/inspection.js +164 -536
  570. package/dist/tools/inspection.js.map +1 -1
  571. package/dist/tools/library.d.ts +115 -127
  572. package/dist/tools/library.d.ts.map +1 -1
  573. package/dist/tools/library.js +707 -455
  574. package/dist/tools/library.js.map +1 -1
  575. package/dist/tools/local-content/builders.d.ts +1 -17
  576. package/dist/tools/local-content/builders.d.ts.map +1 -1
  577. package/dist/tools/local-content/builders.js +103 -46
  578. package/dist/tools/local-content/builders.js.map +1 -1
  579. package/dist/tools/local-content/index.d.ts +0 -15
  580. package/dist/tools/local-content/index.d.ts.map +1 -1
  581. package/dist/tools/local-content/index.js +0 -14
  582. package/dist/tools/local-content/index.js.map +1 -1
  583. package/dist/tools/local-content/schemas.d.ts +31 -47
  584. package/dist/tools/local-content/schemas.d.ts.map +1 -1
  585. package/dist/tools/local-content/schemas.js +33 -62
  586. package/dist/tools/local-content/schemas.js.map +1 -1
  587. package/dist/tools/modules/builders.d.ts +4 -0
  588. package/dist/tools/modules/builders.d.ts.map +1 -0
  589. package/dist/tools/modules/builders.js +58 -0
  590. package/dist/tools/modules/builders.js.map +1 -0
  591. package/dist/tools/modules/index.d.ts +2 -0
  592. package/dist/tools/modules/index.d.ts.map +1 -0
  593. package/dist/tools/modules/index.js +2 -0
  594. package/dist/tools/modules/index.js.map +1 -0
  595. package/dist/tools/modules/module-id-schema.d.ts +4 -0
  596. package/dist/tools/modules/module-id-schema.d.ts.map +1 -0
  597. package/dist/tools/modules/module-id-schema.js +14 -0
  598. package/dist/tools/modules/module-id-schema.js.map +1 -0
  599. package/dist/tools/modules/module-type-fields.d.ts +10 -0
  600. package/dist/tools/modules/module-type-fields.d.ts.map +1 -0
  601. package/dist/tools/modules/module-type-fields.js +34 -0
  602. package/dist/tools/modules/module-type-fields.js.map +1 -0
  603. package/dist/tools/modules/modules-create.d.ts +59 -0
  604. package/dist/tools/modules/modules-create.d.ts.map +1 -0
  605. package/dist/tools/modules/modules-create.js +154 -0
  606. package/dist/tools/modules/modules-create.js.map +1 -0
  607. package/dist/tools/modules/modules-delete.d.ts +51 -0
  608. package/dist/tools/modules/modules-delete.d.ts.map +1 -0
  609. package/dist/tools/modules/modules-delete.js +180 -0
  610. package/dist/tools/modules/modules-delete.js.map +1 -0
  611. package/dist/tools/modules/modules-update.d.ts +43 -0
  612. package/dist/tools/modules/modules-update.d.ts.map +1 -0
  613. package/dist/tools/modules/modules-update.js +177 -0
  614. package/dist/tools/modules/modules-update.js.map +1 -0
  615. package/dist/tools/modules/placement-hints.d.ts +5 -0
  616. package/dist/tools/modules/placement-hints.d.ts.map +1 -0
  617. package/dist/tools/modules/placement-hints.js +90 -0
  618. package/dist/tools/modules/placement-hints.js.map +1 -0
  619. package/dist/tools/modules/placement-ways.d.ts +13 -0
  620. package/dist/tools/modules/placement-ways.d.ts.map +1 -0
  621. package/dist/tools/modules/placement-ways.js +61 -0
  622. package/dist/tools/modules/placement-ways.js.map +1 -0
  623. package/dist/tools/multi-items/builders.d.ts +1 -21
  624. package/dist/tools/multi-items/builders.d.ts.map +1 -1
  625. package/dist/tools/multi-items/builders.js +22 -65
  626. package/dist/tools/multi-items/builders.js.map +1 -1
  627. package/dist/tools/multi-items/index.d.ts +0 -15
  628. package/dist/tools/multi-items/index.d.ts.map +1 -1
  629. package/dist/tools/multi-items/index.js +0 -15
  630. package/dist/tools/multi-items/index.js.map +1 -1
  631. package/dist/tools/multi-items/item-container-map.d.ts +0 -35
  632. package/dist/tools/multi-items/item-container-map.d.ts.map +1 -1
  633. package/dist/tools/multi-items/item-container-map.js +0 -35
  634. package/dist/tools/multi-items/item-container-map.js.map +1 -1
  635. package/dist/tools/multi-items/output-schemas.d.ts +101 -0
  636. package/dist/tools/multi-items/output-schemas.d.ts.map +1 -0
  637. package/dist/tools/multi-items/output-schemas.js +74 -0
  638. package/dist/tools/multi-items/output-schemas.js.map +1 -0
  639. package/dist/tools/navigation/builders.d.ts +1 -16
  640. package/dist/tools/navigation/builders.d.ts.map +1 -1
  641. package/dist/tools/navigation/builders.js +5 -27
  642. package/dist/tools/navigation/builders.js.map +1 -1
  643. package/dist/tools/navigation/index.d.ts +0 -13
  644. package/dist/tools/navigation/index.d.ts.map +1 -1
  645. package/dist/tools/navigation/index.js +0 -12
  646. package/dist/tools/navigation/index.js.map +1 -1
  647. package/dist/tools/navigation/schemas.d.ts +0 -30
  648. package/dist/tools/navigation/schemas.d.ts.map +1 -1
  649. package/dist/tools/navigation/schemas.js +0 -30
  650. package/dist/tools/navigation/schemas.js.map +1 -1
  651. package/dist/tools/output-schema-metadata.d.ts +5 -0
  652. package/dist/tools/output-schema-metadata.d.ts.map +1 -0
  653. package/dist/tools/output-schema-metadata.js +83 -0
  654. package/dist/tools/output-schema-metadata.js.map +1 -0
  655. package/dist/tools/pages/builders.d.ts +1 -41
  656. package/dist/tools/pages/builders.d.ts.map +1 -1
  657. package/dist/tools/pages/builders.js +320 -405
  658. package/dist/tools/pages/builders.js.map +1 -1
  659. package/dist/tools/pages/header-transparency.d.ts +2 -32
  660. package/dist/tools/pages/header-transparency.d.ts.map +1 -1
  661. package/dist/tools/pages/header-transparency.js +9 -43
  662. package/dist/tools/pages/header-transparency.js.map +1 -1
  663. package/dist/tools/pages/index.d.ts +1 -19
  664. package/dist/tools/pages/index.d.ts.map +1 -1
  665. package/dist/tools/pages/index.js +1 -19
  666. package/dist/tools/pages/index.js.map +1 -1
  667. package/dist/tools/pages/schemas.d.ts +323 -241
  668. package/dist/tools/pages/schemas.d.ts.map +1 -1
  669. package/dist/tools/pages/schemas.js +514 -549
  670. package/dist/tools/pages/schemas.js.map +1 -1
  671. package/dist/tools/pages/write-output-schemas.d.ts +139 -0
  672. package/dist/tools/pages/write-output-schemas.d.ts.map +1 -0
  673. package/dist/tools/pages/write-output-schemas.js +138 -0
  674. package/dist/tools/pages/write-output-schemas.js.map +1 -0
  675. package/dist/tools/pages.d.ts +0 -11
  676. package/dist/tools/pages.d.ts.map +1 -1
  677. package/dist/tools/pages.js +0 -11
  678. package/dist/tools/pages.js.map +1 -1
  679. package/dist/tools/pool-resolve-helper.d.ts +0 -52
  680. package/dist/tools/pool-resolve-helper.d.ts.map +1 -1
  681. package/dist/tools/pool-resolve-helper.js +31 -106
  682. package/dist/tools/pool-resolve-helper.js.map +1 -1
  683. package/dist/tools/progress-phases.d.ts +0 -42
  684. package/dist/tools/progress-phases.d.ts.map +1 -1
  685. package/dist/tools/progress-phases.js +0 -44
  686. package/dist/tools/progress-phases.js.map +1 -1
  687. package/dist/tools/response-budget.d.ts +0 -69
  688. package/dist/tools/response-budget.d.ts.map +1 -1
  689. package/dist/tools/response-budget.js +0 -127
  690. package/dist/tools/response-budget.js.map +1 -1
  691. package/dist/tools/shared-schemas.d.ts +11 -65
  692. package/dist/tools/shared-schemas.d.ts.map +1 -1
  693. package/dist/tools/shared-schemas.js +40 -123
  694. package/dist/tools/shared-schemas.js.map +1 -1
  695. package/dist/tools/skill/builders.d.ts +3 -0
  696. package/dist/tools/skill/builders.d.ts.map +1 -0
  697. package/dist/tools/skill/builders.js +81 -0
  698. package/dist/tools/skill/builders.js.map +1 -0
  699. package/dist/tools/sources/builders.d.ts +1 -11
  700. package/dist/tools/sources/builders.d.ts.map +1 -1
  701. package/dist/tools/sources/builders.js +194 -170
  702. package/dist/tools/sources/builders.js.map +1 -1
  703. package/dist/tools/sources/index.d.ts +0 -17
  704. package/dist/tools/sources/index.d.ts.map +1 -1
  705. package/dist/tools/sources/index.js +0 -16
  706. package/dist/tools/sources/index.js.map +1 -1
  707. package/dist/tools/sources/read-output-schemas.d.ts +101 -0
  708. package/dist/tools/sources/read-output-schemas.d.ts.map +1 -0
  709. package/dist/tools/sources/read-output-schemas.js +105 -0
  710. package/dist/tools/sources/read-output-schemas.js.map +1 -0
  711. package/dist/tools/sources/source-name-argument.d.ts +17 -0
  712. package/dist/tools/sources/source-name-argument.d.ts.map +1 -0
  713. package/dist/tools/sources/source-name-argument.js +64 -0
  714. package/dist/tools/sources/source-name-argument.js.map +1 -0
  715. package/dist/tools/sources/write-output-schemas.d.ts +100 -0
  716. package/dist/tools/sources/write-output-schemas.d.ts.map +1 -0
  717. package/dist/tools/sources/write-output-schemas.js +61 -0
  718. package/dist/tools/sources/write-output-schemas.js.map +1 -0
  719. package/dist/tools/sources.d.ts +0 -12
  720. package/dist/tools/sources.d.ts.map +1 -1
  721. package/dist/tools/sources.js +0 -12
  722. package/dist/tools/sources.js.map +1 -1
  723. package/dist/tools/sparse-fields.d.ts +16 -232
  724. package/dist/tools/sparse-fields.d.ts.map +1 -1
  725. package/dist/tools/sparse-fields.js +178 -242
  726. package/dist/tools/sparse-fields.js.map +1 -1
  727. package/dist/tools/strict-input.d.ts +5 -0
  728. package/dist/tools/strict-input.d.ts.map +1 -0
  729. package/dist/tools/strict-input.js +55 -0
  730. package/dist/tools/strict-input.js.map +1 -0
  731. package/dist/tools/sublayout/builders.d.ts +1 -21
  732. package/dist/tools/sublayout/builders.d.ts.map +1 -1
  733. package/dist/tools/sublayout/builders.js +10 -39
  734. package/dist/tools/sublayout/builders.js.map +1 -1
  735. package/dist/tools/sublayout/index.d.ts +1 -11
  736. package/dist/tools/sublayout/index.d.ts.map +1 -1
  737. package/dist/tools/sublayout/index.js +1 -11
  738. package/dist/tools/sublayout/index.js.map +1 -1
  739. package/dist/tools/sublayout/output-schemas.d.ts +57 -0
  740. package/dist/tools/sublayout/output-schemas.d.ts.map +1 -0
  741. package/dist/tools/sublayout/output-schemas.js +51 -0
  742. package/dist/tools/sublayout/output-schemas.js.map +1 -0
  743. package/dist/tools/tool-builder/annotations.d.ts +1 -49
  744. package/dist/tools/tool-builder/annotations.d.ts.map +1 -1
  745. package/dist/tools/tool-builder/annotations.js +9 -49
  746. package/dist/tools/tool-builder/annotations.js.map +1 -1
  747. package/dist/tools/tool-builder/confirm-preview.d.ts +24 -0
  748. package/dist/tools/tool-builder/confirm-preview.d.ts.map +1 -0
  749. package/dist/tools/tool-builder/confirm-preview.js +37 -0
  750. package/dist/tools/tool-builder/confirm-preview.js.map +1 -0
  751. package/dist/tools/tool-builder/define.d.ts +6 -26
  752. package/dist/tools/tool-builder/define.d.ts.map +1 -1
  753. package/dist/tools/tool-builder/define.js +0 -25
  754. package/dist/tools/tool-builder/define.js.map +1 -1
  755. package/dist/tools/tool-builder/index.d.ts +3 -26
  756. package/dist/tools/tool-builder/index.d.ts.map +1 -1
  757. package/dist/tools/tool-builder/index.js +2 -25
  758. package/dist/tools/tool-builder/index.js.map +1 -1
  759. package/dist/tools/tool-builder/results.d.ts +4 -124
  760. package/dist/tools/tool-builder/results.d.ts.map +1 -1
  761. package/dist/tools/tool-builder/results.js +258 -263
  762. package/dist/tools/tool-builder/results.js.map +1 -1
  763. package/dist/tools/tool-builder/types.d.ts +5 -59
  764. package/dist/tools/tool-builder/types.d.ts.map +1 -1
  765. package/dist/tools/tool-builder/types.js +0 -7
  766. package/dist/tools/tool-builder/types.js.map +1 -1
  767. package/dist/tools/tool-builder.d.ts +0 -14
  768. package/dist/tools/tool-builder.d.ts.map +1 -1
  769. package/dist/tools/tool-builder.js +0 -14
  770. package/dist/tools/tool-builder.js.map +1 -1
  771. package/dist/uninstall-skill.d.ts +0 -41
  772. package/dist/uninstall-skill.d.ts.map +1 -1
  773. package/dist/uninstall-skill.js +3 -50
  774. package/dist/uninstall-skill.js.map +1 -1
  775. package/dist/uninstall.d.ts +0 -41
  776. package/dist/uninstall.d.ts.map +1 -1
  777. package/dist/uninstall.js +1 -29
  778. package/dist/uninstall.js.map +1 -1
  779. package/dist/update/update-check.d.ts +0 -26
  780. package/dist/update/update-check.d.ts.map +1 -1
  781. package/dist/update/update-check.js +0 -25
  782. package/dist/update/update-check.js.map +1 -1
  783. package/manifest.json +7 -12
  784. package/package.json +6 -11
  785. package/skills/yt-builder-mcp/SKILL.md +1871 -414
  786. package/dist/tools/elements/handlers-write.d.ts +0 -89
  787. package/dist/tools/elements/handlers-write.d.ts.map +0 -1
  788. package/dist/tools/elements/handlers-write.js +0 -797
  789. package/dist/tools/elements/handlers-write.js.map +0 -1
  790. package/dist/tools/elements/handlers.d.ts +0 -130
  791. package/dist/tools/elements/handlers.d.ts.map +0 -1
  792. package/dist/tools/elements/handlers.js +0 -581
  793. package/dist/tools/elements/handlers.js.map +0 -1
  794. package/dist/tools/elements/layout-hierarchy-rules.d.ts +0 -62
  795. package/dist/tools/elements/layout-hierarchy-rules.d.ts.map +0 -1
  796. package/dist/tools/elements/layout-hierarchy-rules.js +0 -154
  797. package/dist/tools/elements/layout-hierarchy-rules.js.map +0 -1
  798. package/dist/tools/elements/schema-validation.d.ts +0 -218
  799. package/dist/tools/elements/schema-validation.d.ts.map +0 -1
  800. package/dist/tools/elements/schema-validation.js +0 -685
  801. package/dist/tools/elements/schema-validation.js.map +0 -1
  802. package/dist/tools/elicitation.d.ts +0 -87
  803. package/dist/tools/elicitation.d.ts.map +0 -1
  804. package/dist/tools/elicitation.js +0 -100
  805. package/dist/tools/elicitation.js.map +0 -1
  806. package/dist/tools/format/elements-format.d.ts +0 -75
  807. package/dist/tools/format/elements-format.d.ts.map +0 -1
  808. package/dist/tools/format/elements-format.js +0 -368
  809. package/dist/tools/format/elements-format.js.map +0 -1
  810. package/dist/tools/format/source-schema-format.d.ts +0 -74
  811. package/dist/tools/format/source-schema-format.d.ts.map +0 -1
  812. package/dist/tools/format/source-schema-format.js +0 -208
  813. package/dist/tools/format/source-schema-format.js.map +0 -1
  814. package/dist/tools/layout-flatten.d.ts +0 -84
  815. package/dist/tools/layout-flatten.d.ts.map +0 -1
  816. package/dist/tools/layout-flatten.js +0 -129
  817. package/dist/tools/layout-flatten.js.map +0 -1
  818. package/dist/tools/local-content/handlers.d.ts +0 -56
  819. package/dist/tools/local-content/handlers.d.ts.map +0 -1
  820. package/dist/tools/local-content/handlers.js +0 -320
  821. package/dist/tools/local-content/handlers.js.map +0 -1
  822. package/dist/tools/multi-items/clean-implode-handler.d.ts +0 -37
  823. package/dist/tools/multi-items/clean-implode-handler.d.ts.map +0 -1
  824. package/dist/tools/multi-items/clean-implode-handler.js +0 -65
  825. package/dist/tools/multi-items/clean-implode-handler.js.map +0 -1
  826. package/dist/tools/multi-items/format/inspection-format.d.ts +0 -41
  827. package/dist/tools/multi-items/format/inspection-format.d.ts.map +0 -1
  828. package/dist/tools/multi-items/format/inspection-format.js +0 -64
  829. package/dist/tools/multi-items/format/inspection-format.js.map +0 -1
  830. package/dist/tools/multi-items/inspect-handler.d.ts +0 -97
  831. package/dist/tools/multi-items/inspect-handler.d.ts.map +0 -1
  832. package/dist/tools/multi-items/inspect-handler.js +0 -323
  833. package/dist/tools/multi-items/inspect-handler.js.map +0 -1
  834. package/dist/tools/multi-items/inspection-cache.d.ts +0 -85
  835. package/dist/tools/multi-items/inspection-cache.d.ts.map +0 -1
  836. package/dist/tools/multi-items/inspection-cache.js +0 -126
  837. package/dist/tools/multi-items/inspection-cache.js.map +0 -1
  838. package/dist/tools/navigation/handlers.d.ts +0 -30
  839. package/dist/tools/navigation/handlers.d.ts.map +0 -1
  840. package/dist/tools/navigation/handlers.js +0 -41
  841. package/dist/tools/navigation/handlers.js.map +0 -1
  842. package/dist/tools/pages/handlers-audit.d.ts +0 -140
  843. package/dist/tools/pages/handlers-audit.d.ts.map +0 -1
  844. package/dist/tools/pages/handlers-audit.js +0 -714
  845. package/dist/tools/pages/handlers-audit.js.map +0 -1
  846. package/dist/tools/pages/handlers-read.d.ts +0 -133
  847. package/dist/tools/pages/handlers-read.d.ts.map +0 -1
  848. package/dist/tools/pages/handlers-read.js +0 -1061
  849. package/dist/tools/pages/handlers-read.js.map +0 -1
  850. package/dist/tools/pages/handlers-write.d.ts +0 -212
  851. package/dist/tools/pages/handlers-write.d.ts.map +0 -1
  852. package/dist/tools/pages/handlers-write.js +0 -419
  853. package/dist/tools/pages/handlers-write.js.map +0 -1
  854. package/dist/tools/pages/target.d.ts +0 -162
  855. package/dist/tools/pages/target.d.ts.map +0 -1
  856. package/dist/tools/pages/target.js +0 -239
  857. package/dist/tools/pages/target.js.map +0 -1
  858. package/dist/tools/sources/handlers-bind.d.ts +0 -148
  859. package/dist/tools/sources/handlers-bind.d.ts.map +0 -1
  860. package/dist/tools/sources/handlers-bind.js +0 -657
  861. package/dist/tools/sources/handlers-bind.js.map +0 -1
  862. package/dist/tools/sources/handlers.d.ts +0 -116
  863. package/dist/tools/sources/handlers.d.ts.map +0 -1
  864. package/dist/tools/sources/handlers.js +0 -289
  865. package/dist/tools/sources/handlers.js.map +0 -1
  866. package/dist/tools/sublayout/inspect-sublayout-handler.d.ts +0 -95
  867. package/dist/tools/sublayout/inspect-sublayout-handler.d.ts.map +0 -1
  868. package/dist/tools/sublayout/inspect-sublayout-handler.js +0 -181
  869. package/dist/tools/sublayout/inspect-sublayout-handler.js.map +0 -1
@@ -1,85 +1,12 @@
1
- /**
2
- * Layout Library tools — browse + fetch + insert + save the YOOtheme Pro
3
- * Library catalog (layouts / element presets), PLUS the Image Sourcing pair
4
- * (Task D1) that fills layout image slots with real photography. Task 2
5
- * (Layout Library, Spec #2) + Task 3 (Spec #3) + Task 6 (Spec #6) + Task 7
6
- * (Spec #7) + Task D1 (Image Sourcing).
7
- *
8
- * yootheme_builder_library_browse
9
- * → paginated, taxonomy-filtered catalog rows (tableResult — Wave G.2).
10
- * yootheme_builder_library_get
11
- * → a SINGLE item's full tree + derived section list (the natural next
12
- * call after library_browse — id comes straight off a browse row).
13
- * yootheme_builder_library_insert
14
- * → compose + persist a browsed/fetched item onto a page/template
15
- * through the REAL hardened write chain.
16
- * yootheme_builder_library_save_as
17
- * → the REVERSE direction: save a page/subtree INTO "My Layouts".
18
- * yootheme_builder_library_delete
19
- * → the INVERSE of library_save_as: remove ONE saved entry from the
20
- * isolated "WootsUp AI Layouts" store (source:"ai" only; the curated
21
- * catalog is read-only). ADVANCED, like library_save_as.
22
- * yootheme_builder_image_search
23
- * → search Unsplash / Pexels (real photography) or YOOtheme Pro Images
24
- * (curated decorative/background assets) — forwards to the SAME
25
- * `?key=<theme apikey>` proxy auth `library_browse` uses (Task A0-C1,
26
- * PHP `ImageSearchService`). Returns candidate rows; `total:0` means
27
- * no Library API key configured, mirroring browse's empty-catalog hint.
28
- * yootheme_builder_image_import
29
- * → sideload ONE chosen photo (typically a `full_url` from
30
- * image_search) into the site's own Media Library. Returns
31
- * `{imported:true, local_url}` to bind via element_update_settings,
32
- * or `{imported:false, reason}` on a caught failure (never a 500).
33
- *
34
- * browse/get/image_search are read-only forwards, exactly like the sibling
35
- * L1 read tools (sources_list / template_summary / pages_list): resolve the
36
- * site via the pool, GET the plugin REST endpoint, project into a table +
37
- * strict structuredContent, stamp the result-level `_meta` (site_id/
38
- * site_url/platform/resolved_via) via withSiteMeta. insert/save_as/
39
- * image_import are WRITE forwards (withWriteSiteRetry — pre-flights a KNOWN
40
- * read-only token before any body build, mirroring every other write tool's
41
- * U2 pattern).
42
- *
43
- * ── Tiering (Task 7, deliberate) ─────────────────────────────────────────
44
- * `library_save_as` is registered as an ADVANCED tool — it is NOT added to
45
- * `ESSENTIAL_TOOLS` (essentials.ts) and does NOT appear as a first-class
46
- * `tools/list` entry. `tools/list` sits close to the 28000B token-baseline
47
- * ceiling (see `tests/perf/token-baseline.test.ts`); a 4th Layout-Library L1
48
- * tool would risk busting it. `CapturingServer` (gateway/capturing-server.ts)
49
- * automatically captures ANY registered tool whose name is absent from both
50
- * `ESSENTIAL_TOOLS` and `DIRECT_TOP_LEVEL_TOOLS` into the L2 advanced
51
- * registry — reachable via `yootheme_builder_advanced({tool, arguments})` —
52
- * so simply NOT listing this name in essentials.ts is the entire tiering
53
- * decision (mirrors how `yootheme_builder_clean_implode_directives` /
54
- * `yootheme_builder_element_unbind_source` are advanced-only today). The
55
- * name falls through the gateway's domain taxonomy (domains.ts) to "misc"
56
- * (no `library` domain exists there) — intentional, NOT a bug (see
57
- * `domain-order.test.ts`'s own pinned "misc" fallthrough cases): it keeps the
58
- * gateway's own tools/list-visible description text from growing at all,
59
- * while `{tool: "yootheme_builder_library_save_as"}` discovery and
60
- * `{tool, arguments}` execution both remain fully reachable (the gateway
61
- * dispatches by an exact Map lookup, independent of the domain-grouped
62
- * DESCRIPTION text — see `gateway/advanced-tool/execute.ts`).
63
- *
64
- * @license MIT
65
- */
66
1
  import { z } from 'zod';
67
2
  import { RestError } from '../errors.js';
68
3
  import { withSiteRetry, withWriteSiteRetry } from './pool-resolve-helper.js';
69
- import { INCLUDE_META_SCHEMA, SITE_ID_SCHEMA } from './shared-schemas.js';
4
+ import { applyMaxChars, MAX_CHARS } from './sparse-fields.js';
5
+ import { ETAG, INCLUDE_META_SCHEMA, SITE_ID_SCHEMA } from './shared-schemas.js';
70
6
  import { creating, defineTool, destructive, errorResult, jsonResult, mutating, readOnly, structuredResult, withSiteMeta, } from './tool-builder.js';
71
7
  import { budgetedTableResult } from "./budgeted-table.js";
72
8
  import { COMPACT_DEFAULT, RESPONSE_BUDGET } from "./response-budget.js";
73
- // ─── outputSchema (Wave G.2 §4) ────────────────────────────────────
74
- /**
75
- * Fixed section-purpose enum (Task 8, Section Corpus Phase 1) — mirrors the
76
- * PHP-side `SectionPurposeClassifier` / `SectionIndexEntry` purpose values
77
- * byte-for-byte (design spec §A, Global Constraints). Only applies when
78
- * `kind="section"`. Declared here (ahead of `LIBRARY_ITEM_SCHEMA`, which
79
- * consumes it for the browse row's `purpose` field) rather than further
80
- * down where the original Task 8 pass left it, since Task 8b (this file)
81
- * now needs it at both the item-schema AND the browse-filter-schema sites.
82
- */
9
+ import { CONFIRM_PREVIEW_FIELDS, HOST_PROMPT_PROMISE, requireOnExecutedWrite } from './tool-builder/confirm-preview.js';
83
10
  export const SECTION_PURPOSE_VALUES = [
84
11
  'hero',
85
12
  'features',
@@ -94,39 +21,10 @@ export const SECTION_PURPOSE_VALUES = [
94
21
  'logos',
95
22
  'content',
96
23
  ];
97
- /**
98
- * Track 1 Task 7 (WootsUp AI Layouts) — the `source` axis for
99
- * browse/get/insert: `"catalog"` (the remote YOOtheme Pro Library, the
100
- * DEFAULT) or `"ai"` (the plugin's OWN isolated "WootsUp AI Layouts" store,
101
- * saved via library_save_as, no apikey). Mirrors the PHP-side
102
- * `LibrarySourceRegistry` keys byte-for-byte.
103
- */
104
24
  const LIBRARY_SOURCE_VALUES = ['catalog', 'ai'];
105
- /**
106
- * The wire default for the `source` axis. It is NEVER forwarded (a catalog
107
- * browse/get/insert is byte-identical to the pre-Task-7 request — the PHP
108
- * controller reads the SAME absent-param default), so only `"ai"` ever
109
- * appears on the wire. Mirrors `LibrarySourceRegistry::DEFAULT_SOURCE`.
110
- */
111
25
  const DEFAULT_LIBRARY_SOURCE = 'catalog';
112
- /**
113
- * Track 1 Task 7 (WootsUp AI Layouts) — the `destination` axis for
114
- * library_save_as: `"ai"` (save INTO the isolated AI store, the DEFAULT) or
115
- * `"native"` (the escape hatch — save into YOOtheme's native "My Layouts").
116
- */
117
26
  const SAVE_DESTINATION_VALUES = ['ai', 'native'];
118
- /**
119
- * The wire default for the `destination` axis — the AI store. NEVER forwarded
120
- * (an ai-default save is byte-identical to the pre-Task-7 body; the PHP
121
- * `MyLayoutsStore` reads the SAME absent-param `ai` default), so only
122
- * `"native"` ever appears on the wire.
123
- */
124
27
  const DEFAULT_SAVE_DESTINATION = 'ai';
125
- /**
126
- * Mirrors `CatalogService::mapItem()` (PHP): every key is always PRESENT
127
- * (never omitted) but each value may be `null` when the raw catalog item
128
- * did not carry that taxonomy field — hence `.nullable()`, not `.optional()`.
129
- */
130
28
  const LIBRARY_ITEM_META_SCHEMA = z.object({
131
29
  topic: z.string().nullable(),
132
30
  type: z.string().nullable(),
@@ -134,23 +32,13 @@ const LIBRARY_ITEM_META_SCHEMA = z.object({
134
32
  website: z.string().nullable(),
135
33
  image: z.string().nullable(),
136
34
  });
137
- /**
138
- * Task 8b (Section Corpus Phase 1, PLAN-GAP fix) — a `kind="section"` browse
139
- * row (`SectionCatalog::mapEntry()`, PHP) carries a DIFFERENT shape than a
140
- * layout/element row: no `meta` wrapper, TOP-LEVEL `topic`/`type`/`website`,
141
- * plus `layout_id`/`section_index`/`purpose`/`element_signature`/
142
- * `visual_effects`/`has_images`/`image_count`/`element_count`. All of these
143
- * are `.optional()` — additive, never present on a layout/element row — so a
144
- * strict-outputSchema host still accepts the pre-existing byte-unchanged
145
- * layout/element shape.
146
- */
147
35
  const LIBRARY_ITEM_SCHEMA = z.object({
148
36
  id: z.string(),
149
37
  name: z.string(),
150
- kind: z.enum(['layout', 'element', 'section']),
151
- meta: LIBRARY_ITEM_META_SCHEMA,
152
- thumbnail_url: z.string().nullable(),
153
- layout_id: z.string().optional(),
38
+ kind: z.enum(['layout', 'element', 'section']).optional(),
39
+ meta: LIBRARY_ITEM_META_SCHEMA.optional(),
40
+ thumbnail_url: z.string().nullable().optional(),
41
+ library_layout_id: z.string().optional(),
154
42
  section_index: z.number().optional(),
155
43
  purpose: z.enum(SECTION_PURPOSE_VALUES).optional(),
156
44
  element_signature: z.string().optional(),
@@ -160,24 +48,21 @@ const LIBRARY_ITEM_SCHEMA = z.object({
160
48
  website: z.string().nullable().optional(),
161
49
  has_images: z.boolean().optional(),
162
50
  image_count: z.number().optional(),
163
- // Close-out finding (f): CONTENT elements only — excludes structural
164
- // section/row/column wrappers. Distinct from library_get's
165
- // `sections[].element_count` (LIBRARY_SECTION_SCHEMA below), which
166
- // counts ALL descendants including structural nodes — the same
167
- // section reports a different count from each; do not assume parity.
168
- element_count: z
51
+ content_element_count: z
169
52
  .number()
170
53
  .optional()
171
- .describe('CONTENT elements only (excludes structural section/row/column wrappers). ' +
172
- 'Distinct from library_get\'s sections[].element_count, which counts ALL descendants.'),
54
+ .describe('CONTENT elements in this section — the section/row/column wrappers are NOT counted. ' +
55
+ 'library_get\'s sections[].element_count and library_insert\'s element_count count ' +
56
+ 'every node including those wrappers, so the same section reads higher there.'),
173
57
  });
174
58
  export const LIBRARY_BROWSE_OUTPUT_SCHEMA = z.object({
175
59
  items: z.array(LIBRARY_ITEM_SCHEMA),
176
60
  total: z.number(),
177
- // `CatalogService::browse()` always emits the key; `null` means "last page".
178
61
  next_cursor: z.string().nullable(),
62
+ api_key_configured: z.boolean().optional(),
63
+ images_note: z.string().optional(),
64
+ detail_note: z.string().optional(),
179
65
  });
180
- // ─── library_get (Task 3) ───────────────────────────────────────────
181
66
  const LIBRARY_SECTION_SCHEMA = z.object({
182
67
  index: z.number(),
183
68
  name: z.string(),
@@ -186,28 +71,14 @@ const LIBRARY_SECTION_SCHEMA = z.object({
186
71
  });
187
72
  export const LIBRARY_GET_OUTPUT_SCHEMA = z.object({
188
73
  id: z.string(),
189
- // Task 8b (Section Corpus Phase 1, PLAN-GAP fix): "section" is a valid
190
- // resolved kind — `LibraryController::getSection()` (PHP) always
191
- // returns a literal `kind: "section"` for a composite-id fetch.
192
74
  kind: z.enum(['layout', 'element', 'section']),
193
75
  name: z.string(),
194
76
  version: z.string(),
195
- // The raw fetched tree (`{type, children, version, images, ...}` for a
196
- // layout; a single element node for a preset) — an arbitrary JSON
197
- // object, not a fixed shape.
198
77
  tree: z.record(z.string(), z.unknown()),
199
- // Top-level `tree.children` (layouts only — always `[]` for a preset,
200
- // even when its own tree happens to carry `children`).
201
78
  sections: z.array(LIBRARY_SECTION_SCHEMA),
202
79
  images_manifest_count: z.number(),
80
+ element_count_note: z.string().optional(),
203
81
  });
204
- // ─── library_insert (Task 6) ─────────────────────────────────────────
205
- /**
206
- * Mirrors `LibraryInsertService::insert()`'s `$target` union (PHP) exactly:
207
- * a fresh page, a splice into an EXISTING template/page (layout/section on
208
- * the layout root, or a preset on a Column), or a splice into a
209
- * sublayout/fragment element.
210
- */
211
82
  const LIBRARY_INSERT_TARGET_SCHEMA = z.union([
212
83
  z.object({
213
84
  new_page: z.object({
@@ -242,33 +113,49 @@ const LIBRARY_INSERT_TARGET_SCHEMA = z.union([
242
113
  'or a Column for an element preset (kind:"element").'),
243
114
  }),
244
115
  ]);
245
- /**
246
- * A DOWNGRADED UI-invariant violation (2026-07-12 fix, "trusted
247
- * library-insert downgrades enum/type violations to warnings"): a pure
248
- * value-shape mismatch (`invalid_enum_value` / `type_mismatch`) that
249
- * YOOtheme's OWN factory content carried but the site's installed YOOtheme
250
- * schema doesn't declare — a version-skew artifact, allowed through rather
251
- * than hard-rejecting the whole insert. Every OTHER violation class still
252
- * 422s (`library.ui_invariant_violation`) exactly as before.
253
- */
254
116
  const LIBRARY_INSERT_WARNING_SCHEMA = z.object({
255
117
  field: z.string(),
256
118
  code: z.string(),
257
119
  message: z.string(),
258
120
  });
259
- export const LIBRARY_INSERT_OUTPUT_SCHEMA = z.object({
260
- template_id: z.string(),
261
- inserted_path: z.string(),
262
- element_count: z.number(),
263
- images_mode: z.enum(['placeholder', 'curated']),
264
- images_resolved: z.object({
121
+ const LIBRARY_INSERT_IMAGE_FAILURE_SCHEMA = z.object({
122
+ prop_path: z.string(),
123
+ exception_class: z.string(),
124
+ message: z.string(),
125
+ });
126
+ export const LIBRARY_INSERT_OUTPUT_SCHEMA = z
127
+ .object({
128
+ layout_id: z.string(),
129
+ inserted_path: z.string().optional(),
130
+ inserted_paths: z.array(z.string()).optional(),
131
+ element_count: z.number().optional(),
132
+ images_mode: z.enum(['placeholder', 'curated', 'as_saved']).optional(),
133
+ images_resolved: z
134
+ .object({
265
135
  downloaded: z.number(),
266
136
  placeholders: z.number(),
267
- }),
268
- new_etag: z.string(),
269
- frontend_url: z.string().nullable(),
137
+ })
138
+ .optional(),
139
+ etag: z.string().optional(),
140
+ new_etag: z.string().optional(),
141
+ frontend_url: z.string().nullable().optional(),
142
+ images_note: z.string().optional(),
143
+ media_note: z.string().optional(),
270
144
  warnings: z.array(LIBRARY_INSERT_WARNING_SCHEMA).optional(),
271
- });
145
+ image_import_failures: z.array(LIBRARY_INSERT_IMAGE_FAILURE_SCHEMA).optional(),
146
+ root_type_inserted: z.string().optional(),
147
+ would_remove_count: z.number().optional(),
148
+ would_remove_types: z.array(z.string()).optional(),
149
+ ...CONFIRM_PREVIEW_FIELDS,
150
+ })
151
+ .superRefine((v, ctx) => requireOnExecutedWrite(v, ctx, [
152
+ 'inserted_path',
153
+ 'element_count',
154
+ 'images_mode',
155
+ 'images_resolved',
156
+ 'new_etag',
157
+ 'frontend_url',
158
+ ]));
272
159
  const LIBRARY_TABLE_COLUMNS = [
273
160
  { key: 'id', label: 'ID', width: 10, llmOnly: true },
274
161
  { key: 'name', label: 'NAME', width: 26 },
@@ -283,17 +170,6 @@ function asNullableString(v) {
283
170
  function isSectionPurpose(v) {
284
171
  return typeof v === 'string' && SECTION_PURPOSE_VALUES.includes(v);
285
172
  }
286
- /**
287
- * Task 8b (Section Corpus Phase 1, PLAN-GAP fix) — `requestedKind` is the
288
- * caller's own `library_browse({kind})` argument. It is needed because a
289
- * `kind="section"` row from `SectionCatalog::mapEntry()` (PHP) carries NO
290
- * per-row `kind` field at all (unlike layout/element rows, which always do)
291
- * — the whole response is homogeneous per browse call, since `kind` is a
292
- * REST-level filter. So: `r.kind` wins when the row DOES carry one
293
- * (layout/element, and defensively "section" too, should REST ever add it);
294
- * otherwise fall back to what the caller asked for; otherwise "layout"
295
- * (today's pre-Task-8b default, byte-unchanged).
296
- */
297
173
  function mapLibraryItem(raw, requestedKind) {
298
174
  if (raw === null || typeof raw !== 'object')
299
175
  return undefined;
@@ -313,15 +189,11 @@ function mapLibraryItem(raw, requestedKind) {
313
189
  },
314
190
  thumbnail_url: asNullableString(r.thumbnail_url),
315
191
  };
316
- // ADDITIVE ONLY: a layout/element row returns exactly `base` — no
317
- // section keys are ever added, not even as `undefined` — so the
318
- // pre-Task-8b shape stays byte-unchanged (regression-tested via
319
- // toStrictEqual in library.test.ts).
320
192
  if (kind !== 'section')
321
193
  return base;
322
194
  return {
323
195
  ...base,
324
- layout_id: typeof r.layout_id === 'string' ? r.layout_id : undefined,
196
+ library_layout_id: typeof r.library_layout_id === 'string' ? r.library_layout_id : undefined,
325
197
  section_index: typeof r.section_index === 'number' ? r.section_index : undefined,
326
198
  purpose: isSectionPurpose(r.purpose) ? r.purpose : undefined,
327
199
  element_signature: typeof r.element_signature === 'string' ? r.element_signature : undefined,
@@ -333,41 +205,17 @@ function mapLibraryItem(raw, requestedKind) {
333
205
  website: asNullableString(r.website),
334
206
  has_images: typeof r.has_images === 'boolean' ? r.has_images : undefined,
335
207
  image_count: typeof r.image_count === 'number' ? r.image_count : undefined,
336
- element_count: typeof r.element_count === 'number' ? r.element_count : undefined,
208
+ content_element_count: typeof r.content_element_count === 'number' ? r.content_element_count : undefined,
337
209
  };
338
210
  }
339
- /**
340
- * PAGES_LIBRARY_MEDIA-10 the Layout Library + Image Sourcing domain is
341
- * WordPress-only (the `src/modules/library` host module bootstraps on WP only;
342
- * plugin 1.10.0). On a Joomla site the REST endpoints do not exist, so a
343
- * library/image call used to return an empty `{items:[],total:0}` (or a bare
344
- * 404) with no hint about WHY. This returns an explicit
345
- * `not_supported_on_platform` (501) instead, so the agent stops retrying and
346
- * knows the capability simply is not there on this platform. Returns `null` on
347
- * WordPress so the real forward runs.
348
- */
349
- function joomlaNotSupportedGuard(client, toolName, context) {
350
- if (client.getPlatform().kind !== 'joomla')
351
- return null;
352
- return errorResult({
353
- error: new Error(`${toolName} is not supported on Joomla — the Library + image domain is ` +
354
- 'WordPress-only in plugin 1.10.0.'),
355
- context: { ...context, platform: 'joomla', error_code: 'not_supported_on_platform', status: 501 },
356
- hint: 'This capability exists only on WordPress sites. On Joomla, build layouts with ' +
357
- 'the element/page tools; there is no Library catalog or image proxy to browse.',
358
- });
359
- }
211
+ export const BROWSE_IMAGES_NOTE = 'Images: an insert uses generic PLACEHOLDERS by default '
212
+ + '- has_images describes the catalog entry, not the inserted page. Pass '
213
+ + 'images:"curated" to yootheme_builder_library_insert to download the real photos.';
360
214
  export async function handleLibraryBrowse({ client }, { kind, source, topic, type, role, website, element_type, purpose, has_effects, query, limit, cursor, include_meta }) {
361
- const guard = joomlaNotSupportedGuard(client, 'yootheme_builder_library_browse', { kind: kind ?? 'layout' });
362
- if (guard !== null)
363
- return guard;
364
215
  try {
365
216
  const qs = new URLSearchParams();
366
217
  if (kind !== undefined)
367
218
  qs.set('kind', kind);
368
- // Only forward a NON-default source (the PHP controller reads the same
369
- // absent-param `catalog` default) so a catalog browse is byte-identical
370
- // to the pre-Task-7 request.
371
219
  if (source !== undefined && source !== DEFAULT_LIBRARY_SOURCE)
372
220
  qs.set('source', source);
373
221
  if (typeof topic === 'string' && topic !== '')
@@ -398,26 +246,51 @@ export async function handleLibraryBrowse({ client }, { kind, source, topic, typ
398
246
  .filter((i) => i !== undefined);
399
247
  const total = typeof data.total === 'number' ? data.total : items.length;
400
248
  const nextCursor = typeof data.next_cursor === 'string' ? data.next_cursor : null;
249
+ const apiKeyConfigured = typeof data.api_key_configured === 'boolean' ? data.api_key_configured : undefined;
250
+ const detailNote = typeof data.detail_note === 'string' && data.detail_note !== '' ? data.detail_note : undefined;
401
251
  const rows = items;
402
252
  const toolkitResult = budgetedTableResult(rows, {
403
253
  columns: [...LIBRARY_TABLE_COLUMNS],
404
254
  header: (count) => `${String(count)} of ${String(total)} library items`,
405
- footer: 'Activate one on a page with yootheme_builder_element_add / page_dynamize, ' +
406
- 'or pass `cursor` (next_cursor) for the next page.',
255
+ footer: 'Activate one on a page with yootheme_builder_element_add / page_generate_dynamic, ' +
256
+ 'or pass `cursor` (next_cursor) for the next page. ' +
257
+ BROWSE_IMAGES_NOTE,
407
258
  includeMeta: include_meta,
408
259
  }, RESPONSE_BUDGET.BULKY);
409
- // HARD CONSTRAINT (Task 8): browse NEVER returns an image content
410
- // block, even for kind="section" rows that carry a thumbnail_url —
411
- // tableResult() only ever emits text rows. Use
412
- // yootheme_builder_library_get's include_thumbnail on a shortlisted
413
- // id for an actual image.
414
260
  return structuredResult(toolkitResult, {
415
261
  items,
416
262
  total,
417
263
  next_cursor: nextCursor,
264
+ images_note: BROWSE_IMAGES_NOTE,
265
+ ...(apiKeyConfigured !== undefined ? { api_key_configured: apiKeyConfigured } : {}),
266
+ ...(detailNote !== undefined ? { detail_note: detailNote } : {}),
418
267
  });
419
268
  }
420
269
  catch (e) {
270
+ if (isLibraryNotConfigured(e)) {
271
+ return errorResult({
272
+ error: e,
273
+ context: {
274
+ kind: kind ?? 'layout',
275
+ source: source ?? DEFAULT_LIBRARY_SOURCE,
276
+ error_code: LIBRARY_NOT_CONFIGURED,
277
+ ...libraryUnavailableContext(e),
278
+ },
279
+ hint: LIBRARY_NOT_CONFIGURED_HINT,
280
+ });
281
+ }
282
+ if (isLibraryUpstreamUnavailable(e)) {
283
+ return errorResult({
284
+ error: e,
285
+ context: {
286
+ kind: kind ?? 'layout',
287
+ source: source ?? DEFAULT_LIBRARY_SOURCE,
288
+ error_code: LIBRARY_UPSTREAM_UNAVAILABLE,
289
+ ...libraryUnavailableContext(e),
290
+ },
291
+ hint: LIBRARY_UPSTREAM_UNAVAILABLE_HINT,
292
+ });
293
+ }
421
294
  return errorResult({
422
295
  error: e,
423
296
  context: { kind: kind ?? 'layout', source: source ?? DEFAULT_LIBRARY_SOURCE, topic, type, role, website, element_type, purpose, has_effects, query },
@@ -425,6 +298,69 @@ export async function handleLibraryBrowse({ client }, { kind, source, topic, typ
425
298
  });
426
299
  }
427
300
  }
301
+ const LIBRARY_UPSTREAM_UNAVAILABLE = 'library_upstream_unavailable';
302
+ export const LIBRARY_UPSTREAM_UNAVAILABLE_HINT = 'This is a CATALOG FETCH FAILURE, not an empty catalog. Do NOT retry with different '
303
+ + 'filters or a different id; the YOOtheme Pro Library never answered with data. An HTTP 401 '
304
+ + 'here means api.yootheme.com REFUSED the Library API key this site has configured. WHY it '
305
+ + 'was refused is not visible from here, so do NOT name a cause: report that the key was '
306
+ + 'refused and let the site owner check it with YOOtheme. The keyless source:"ai" store and '
307
+ + 'yootheme_builder_page_get_layout still work.';
308
+ function restErrorBody(e) {
309
+ if (!(e instanceof RestError))
310
+ return undefined;
311
+ const body = e.body;
312
+ if (body === null || typeof body !== 'object' || Array.isArray(body))
313
+ return undefined;
314
+ return body;
315
+ }
316
+ const LIBRARY_NOT_CONFIGURED = 'library_api_key_not_configured';
317
+ export const LIBRARY_NOT_CONFIGURED_HINT = 'This is a CONFIGURATION GAP, not an empty catalog and not a refusal: no request reached the '
318
+ + 'upstream catalog, so nothing here tells you what it holds. Do NOT retry with different '
319
+ + 'filters, and do NOT assume the customer forgot to enter a key. Run yootheme_builder_diagnose '
320
+ + 'and read library_key.state first: it separates "no key entered", which the customer fixes in '
321
+ + 'the YOOtheme Customizer theme settings, from a lookup that found no YOOtheme update site at '
322
+ + 'all, where there is nothing to fix in the key field. Its hint names who has to act. Without a '
323
+ + 'key you can still BROWSE the bundled section index (library_browse with kind:"section", '
324
+ + 'including its filters), read the keyless source:"ai" store, and use '
325
+ + 'yootheme_builder_page_get_layout. What the key unlocks is kind:"layout"/"element" browsing '
326
+ + 'plus library_get detail and library_insert for every kind.';
327
+ function isLibraryUpstreamUnavailable(e) {
328
+ return libraryCodeMatches(e, LIBRARY_UPSTREAM_UNAVAILABLE);
329
+ }
330
+ function isLibraryNotConfigured(e) {
331
+ return libraryCodeMatches(e, LIBRARY_NOT_CONFIGURED);
332
+ }
333
+ function libraryCodeMatches(e, code) {
334
+ if (e instanceof RestError && typeof e.code === 'string' && e.code.endsWith(code)) {
335
+ return true;
336
+ }
337
+ const body = restErrorBody(e);
338
+ if (body === undefined)
339
+ return false;
340
+ if (body.error_code === code)
341
+ return true;
342
+ const data = body.data;
343
+ return (data !== null
344
+ && typeof data === 'object'
345
+ && !Array.isArray(data)
346
+ && data.error_code === code);
347
+ }
348
+ function libraryUnavailableContext(e) {
349
+ const body = restErrorBody(e);
350
+ if (body === undefined)
351
+ return {};
352
+ const data = body.data !== null && typeof body.data === 'object' && !Array.isArray(body.data)
353
+ ? body.data
354
+ : {};
355
+ const out = {};
356
+ const upstream = body.upstream_status ?? data.upstream_status;
357
+ if (typeof upstream === 'number')
358
+ out.upstream_status = upstream;
359
+ const configured = body.api_key_configured ?? data.api_key_configured;
360
+ if (typeof configured === 'boolean')
361
+ out.api_key_configured = configured;
362
+ return out;
363
+ }
428
364
  function mapLibrarySection(raw) {
429
365
  if (raw === null || typeof raw !== 'object')
430
366
  return undefined;
@@ -437,14 +373,6 @@ function mapLibrarySection(raw) {
437
373
  }
438
374
  return { index: r.index, name: r.name, path: r.path, element_count: r.element_count };
439
375
  }
440
- /**
441
- * Task 8 (Section Corpus Phase 1) — the REST `/library/get` response's
442
- * `thumbnail` field (present ONLY when `include_thumbnail=true` was passed
443
- * AND the server-side fetch succeeded — see `LibraryController::attachThumbnail()`).
444
- * A missing/malformed field (either key absent, or either value not a
445
- * non-empty string) is treated as "no thumbnail" — the caller never emits a
446
- * broken image block.
447
- */
448
376
  function mapThumbnail(raw) {
449
377
  if (raw === null || typeof raw !== 'object')
450
378
  return undefined;
@@ -455,15 +383,76 @@ function mapThumbnail(raw) {
455
383
  return undefined;
456
384
  return { mime_type: r.mime_type, data_base64: r.data_base64 };
457
385
  }
458
- export async function handleLibraryGet({ client }, { id, kind, source, include_thumbnail }) {
459
- const guard = joomlaNotSupportedGuard(client, 'yootheme_builder_library_get', { id });
460
- if (guard !== null)
461
- return guard;
386
+ function asPlainRecord(value) {
387
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
388
+ ? value
389
+ : {};
390
+ }
391
+ function collectElementTypeCounts(node, counts) {
392
+ const n = asPlainRecord(node);
393
+ if (typeof n.type === 'string' && n.type !== '') {
394
+ counts.set(n.type, (counts.get(n.type) ?? 0) + 1);
395
+ }
396
+ if (Array.isArray(n.children)) {
397
+ for (const child of n.children)
398
+ collectElementTypeCounts(child, counts);
399
+ }
400
+ }
401
+ function elementTypeCounts(tree) {
402
+ const counts = new Map();
403
+ collectElementTypeCounts(tree, counts);
404
+ return Object.fromEntries([...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])));
405
+ }
406
+ function encodedLength(payload) {
407
+ return JSON.stringify(payload).length;
408
+ }
409
+ function truncatedHint(sectionCount) {
410
+ const sectionAdvice = sectionCount > 0
411
+ ? ' To inspect ONE section instead of the whole item, call this tool again with ' +
412
+ 'kind:"section" and an id of "<this id>#<sectionIndex>" (see sections[].index) — a ' +
413
+ 'single section is a fraction of the full size.'
414
+ : '';
415
+ const whatSurvived = sectionCount > 0
416
+ ? ' What is missing is the ELEMENTS inside the sections; the `sections` list beside ' +
417
+ 'this note is complete and unabridged (every section, its name and element count).'
418
+ : '';
419
+ return ('The full tree was replaced by this summary because the payload was too large.' +
420
+ whatSurvived +
421
+ ' To INSERT this item you do not need the tree at all — call ' +
422
+ 'yootheme_builder_library_insert with the same id directly.' +
423
+ sectionAdvice +
424
+ ' Passing a larger max_chars recovers more of the tree.');
425
+ }
426
+ const LIBRARY_GET_DEFAULT_STRUCTURED_BUDGET_CHARS = 20_000;
427
+ function shrinkLibraryGetTree(payload, budget) {
428
+ const bytesFull = encodedLength(payload);
429
+ const tree = asPlainRecord(payload.tree);
430
+ if (bytesFull <= budget)
431
+ return tree;
432
+ const rootType = typeof tree.type === 'string' ? tree.type : undefined;
433
+ const sections = Array.isArray(payload.sections) ? payload.sections : [];
434
+ const counts = elementTypeCounts(tree);
435
+ const summaryTree = {
436
+ ...(rootType !== undefined ? { type: rootType } : {}),
437
+ truncated: true,
438
+ bytes_full: bytesFull,
439
+ element_type_counts: counts,
440
+ hint: truncatedHint(sections.length),
441
+ };
442
+ if (encodedLength({ ...payload, tree: summaryTree }) <= budget)
443
+ return summaryTree;
444
+ const top = Object.fromEntries(Object.entries(counts).slice(0, 5));
445
+ return {
446
+ ...summaryTree,
447
+ element_type_counts: top,
448
+ element_type_counts_truncated: Object.keys(counts).length > Object.keys(top).length,
449
+ };
450
+ }
451
+ export async function handleLibraryGet({ client }, { id, kind, source, include_thumbnail, max_chars }) {
462
452
  try {
463
453
  const qs = new URLSearchParams({ id });
464
454
  if (kind !== undefined)
465
455
  qs.set('kind', kind);
466
- // Only forward a NON-default source (see handleLibraryBrowse).
467
456
  if (source !== undefined && source !== DEFAULT_LIBRARY_SOURCE)
468
457
  qs.set('source', source);
469
458
  if (include_thumbnail === true)
@@ -486,24 +475,58 @@ export async function handleLibraryGet({ client }, { id, kind, source, include_t
486
475
  sections,
487
476
  images_manifest_count: typeof data.images_manifest_count === 'number' ? data.images_manifest_count : 0,
488
477
  };
489
- // Task 8: an image content block is ADDITIONAL to the JSON text
490
- // block, never a replacement — `structuredContent` (below) stays
491
- // exactly the pre-Task-8 shape so a strict-outputSchema host never
492
- // sees an undeclared key, and a large base64 blob is never
493
- // duplicated into structuredContent alongside the image block.
494
- // Gated on the CALLER's own `include_thumbnail === true` (not just
495
- // "the REST body happened to carry a thumbnail field") — a defensive
496
- // belt-and-braces on top of the server-side opt-in gate, so a future
497
- // REST regression can never surprise-inject an unrequested,
498
- // token-costly image block.
478
+ const structuredBudget = max_chars ?? LIBRARY_GET_DEFAULT_STRUCTURED_BUDGET_CHARS;
479
+ const finalTree = shrinkLibraryGetTree(payload, structuredBudget);
480
+ const structuredPayload = {
481
+ id: payload.id,
482
+ kind: payload.kind,
483
+ name: payload.name,
484
+ version: payload.version,
485
+ tree: finalTree,
486
+ sections: payload.sections,
487
+ images_manifest_count: payload.images_manifest_count,
488
+ };
499
489
  const thumbnail = include_thumbnail === true ? mapThumbnail(data.thumbnail) : undefined;
500
- const jsonEnvelope = jsonResult(payload);
490
+ const jsonEnvelope = jsonResult(structuredPayload);
501
491
  const content = thumbnail !== undefined
502
492
  ? [...jsonEnvelope.content, { type: 'image', data: thumbnail.data_base64, mimeType: thumbnail.mime_type }]
503
493
  : jsonEnvelope.content;
504
- return structuredResult({ content }, payload);
494
+ return structuredResult(applyMaxChars({ content }, max_chars), structuredPayload);
505
495
  }
506
496
  catch (e) {
497
+ if (isLibraryNotConfigured(e)) {
498
+ return errorResult({
499
+ error: e,
500
+ context: {
501
+ id,
502
+ kind: kind ?? 'layout',
503
+ source: source ?? DEFAULT_LIBRARY_SOURCE,
504
+ error_code: LIBRARY_NOT_CONFIGURED,
505
+ ...libraryUnavailableContext(e),
506
+ },
507
+ hint: LIBRARY_NOT_CONFIGURED_HINT,
508
+ });
509
+ }
510
+ if (isLibraryUpstreamUnavailable(e)) {
511
+ const usedDefaultSource = source === undefined || source === DEFAULT_LIBRARY_SOURCE;
512
+ return errorResult({
513
+ error: e,
514
+ context: {
515
+ id,
516
+ kind: kind ?? 'layout',
517
+ source: source ?? DEFAULT_LIBRARY_SOURCE,
518
+ error_code: LIBRARY_UPSTREAM_UNAVAILABLE,
519
+ ...libraryUnavailableContext(e),
520
+ },
521
+ hint: usedDefaultSource
522
+ ? LIBRARY_UPSTREAM_UNAVAILABLE_HINT
523
+ + ' If this id came from your OWN store (library_save_as, or '
524
+ + 'library_browse with source:"ai"), it does not live in the remote '
525
+ + 'catalog at all: retry with source:"ai". The catalog was queried '
526
+ + 'because `source` defaults to "catalog".'
527
+ : LIBRARY_UPSTREAM_UNAVAILABLE_HINT,
528
+ });
529
+ }
507
530
  return errorResult({
508
531
  error: e,
509
532
  context: { id, kind: kind ?? 'layout', source: source ?? DEFAULT_LIBRARY_SOURCE },
@@ -512,15 +535,6 @@ export async function handleLibraryGet({ client }, { id, kind, source, include_t
512
535
  });
513
536
  }
514
537
  }
515
- /**
516
- * 2026-07-12 fix ("trusted library-insert downgrades enum/type violations to
517
- * warnings") — maps the REST `warnings[]` array (each `{field, code,
518
- * message}`) if present. Mirrors the OTHER `map*` helpers in this file:
519
- * malformed entries are dropped rather than propagated, and an absent/empty
520
- * source array maps to `undefined` (the key is OMITTED from the result, not
521
- * emitted as `[]`) — this is the "if present" contract: pre-fix inserts
522
- * (a REST body carrying no `warnings` key at all) round-trip byte-unchanged.
523
- */
524
538
  function mapLibraryInsertWarnings(raw) {
525
539
  if (!Array.isArray(raw) || raw.length === 0)
526
540
  return undefined;
@@ -535,55 +549,148 @@ function mapLibraryInsertWarnings(raw) {
535
549
  }
536
550
  return warnings.length > 0 ? warnings : undefined;
537
551
  }
552
+ function mapLibraryInsertImageFailures(raw) {
553
+ if (!Array.isArray(raw) || raw.length === 0)
554
+ return undefined;
555
+ const failures = [];
556
+ for (const entry of raw) {
557
+ if (entry === null || typeof entry !== 'object')
558
+ continue;
559
+ const f = entry;
560
+ if (typeof f.prop_path !== 'string' || typeof f.exception_class !== 'string')
561
+ continue;
562
+ if (typeof f.message !== 'string')
563
+ continue;
564
+ failures.push({
565
+ prop_path: f.prop_path,
566
+ exception_class: f.exception_class,
567
+ message: f.message,
568
+ });
569
+ }
570
+ return failures.length > 0 ? failures : undefined;
571
+ }
572
+ function isLibraryInsertPreview(raw) {
573
+ return raw !== null && typeof raw === 'object' && raw.preview === true;
574
+ }
575
+ function mapLibraryInsertPreview(r) {
576
+ const types = Array.isArray(r.would_remove_types)
577
+ ? r.would_remove_types.filter((t) => typeof t === 'string')
578
+ : undefined;
579
+ return {
580
+ preview: true,
581
+ applied: false,
582
+ requires_confirmation: true,
583
+ requires_confirm: true,
584
+ operation: typeof r.operation === 'string' ? r.operation : 'insert this Library item',
585
+ warning: typeof r.warning === 'string' ? r.warning : '',
586
+ instruction: typeof r.instruction === 'string'
587
+ ? r.instruction
588
+ : 'Ask the user to confirm, then call again with `confirm: true`.',
589
+ hint: typeof r.hint === 'string' ? r.hint : '',
590
+ received_confirm: r.received_confirm,
591
+ received_confirm_type: typeof r.received_confirm_type === 'string' ? r.received_confirm_type : 'absent',
592
+ expected_confirm: typeof r.expected_confirm === 'string' ? r.expected_confirm : '',
593
+ ...(typeof r.step_reversible === 'boolean' ? { step_reversible: r.step_reversible } : {}),
594
+ layout_id: typeof r.layout_id === 'string' ? r.layout_id : '',
595
+ ...(typeof r.etag === 'string' && r.etag !== '' ? { etag: r.etag } : {}),
596
+ ...(typeof r.would_remove_count === 'number' ? { would_remove_count: r.would_remove_count } : {}),
597
+ ...(types !== undefined ? { would_remove_types: types } : {}),
598
+ };
599
+ }
538
600
  function mapLibraryInsertResult(raw) {
539
601
  if (raw === null || typeof raw !== 'object')
540
602
  return undefined;
541
603
  const r = raw;
542
- if (typeof r.template_id !== 'string' || typeof r.inserted_path !== 'string')
604
+ if (typeof r.layout_id !== 'string' || typeof r.inserted_path !== 'string')
543
605
  return undefined;
544
606
  const resolvedRaw = r.images_resolved !== null && typeof r.images_resolved === 'object'
545
607
  ? r.images_resolved
546
608
  : {};
547
609
  const warnings = mapLibraryInsertWarnings(r.warnings);
610
+ const imageFailures = mapLibraryInsertImageFailures(r.image_import_failures);
611
+ const mode = r.images_mode === 'curated' || r.images_mode === 'as_saved' ? r.images_mode : 'placeholder';
612
+ const placeholders = typeof resolvedRaw.placeholders === 'number' ? resolvedRaw.placeholders : 0;
613
+ const imagesNote = mode === 'placeholder' && placeholders > 0
614
+ ? `${String(placeholders)} image(s) are generic placeholders, not the photos shown on `
615
+ + 'the catalog thumbnail. The layout renders, but it does not look like its preview. '
616
+ + 'Re-insert with images:"curated" to download the real images into the Media Library.'
617
+ : undefined;
548
618
  return {
549
- template_id: r.template_id,
619
+ layout_id: r.layout_id,
550
620
  inserted_path: r.inserted_path,
551
621
  element_count: typeof r.element_count === 'number' ? r.element_count : 0,
552
- images_mode: r.images_mode === 'curated' ? 'curated' : 'placeholder',
622
+ images_mode: mode,
553
623
  images_resolved: {
554
624
  downloaded: typeof resolvedRaw.downloaded === 'number' ? resolvedRaw.downloaded : 0,
555
- placeholders: typeof resolvedRaw.placeholders === 'number' ? resolvedRaw.placeholders : 0,
625
+ placeholders,
556
626
  },
557
627
  new_etag: typeof r.new_etag === 'string' ? r.new_etag : '',
558
628
  frontend_url: typeof r.frontend_url === 'string' ? r.frontend_url : null,
629
+ ...(imagesNote !== undefined ? { images_note: imagesNote } : {}),
630
+ ...(typeof r.media_note === 'string' && r.media_note !== '' ? { media_note: r.media_note } : {}),
559
631
  ...(warnings !== undefined ? { warnings } : {}),
632
+ ...(imageFailures !== undefined ? { image_import_failures: imageFailures } : {}),
633
+ root_type_inserted: typeof r.root_type_inserted === 'string' ? r.root_type_inserted : '',
560
634
  };
561
635
  }
562
- export async function handleLibraryInsert({ client }, { id, kind, source, target, section_index, images, etag }) {
563
- const guard = joomlaNotSupportedGuard(client, 'yootheme_builder_library_insert', { id });
564
- if (guard !== null)
565
- return guard;
636
+ export async function handleLibraryInsert({ client }, { id, kind, source, target, section_index, images, insert_mode, index, confirm, etag }) {
566
637
  const resolvedImages = images ?? 'placeholder';
567
638
  try {
568
639
  const body = { id, target, images: resolvedImages };
569
640
  if (kind !== undefined)
570
641
  body.kind = kind;
571
- // Only forward a NON-default source (see handleLibraryBrowse); the PHP
572
- // LibraryInsertService reads the same absent-param `catalog` default.
573
642
  if (source !== undefined && source !== DEFAULT_LIBRARY_SOURCE)
574
643
  body.source = source;
575
644
  if (section_index !== undefined)
576
645
  body.section_index = section_index;
646
+ if (insert_mode !== undefined)
647
+ body.insert_mode = insert_mode;
648
+ if (index !== undefined)
649
+ body.index = index;
650
+ if (confirm !== undefined)
651
+ body.confirm = confirm;
577
652
  if (typeof etag === 'string' && etag !== '')
578
653
  body.etag = etag;
579
654
  const data = await client.post('/library/insert', { body });
655
+ if (isLibraryInsertPreview(data)) {
656
+ const preview = mapLibraryInsertPreview(data);
657
+ return structuredResult(jsonResult(preview), preview);
658
+ }
580
659
  const mapped = mapLibraryInsertResult(data);
581
660
  if (mapped === undefined) {
582
- throw new Error('Malformed /library/insert response — missing template_id/inserted_path.');
661
+ throw new Error('Malformed /library/insert response — missing layout_id/inserted_path.');
583
662
  }
584
663
  return structuredResult(jsonResult(mapped), mapped);
585
664
  }
586
665
  catch (e) {
666
+ if (isLibraryNotConfigured(e)) {
667
+ return errorResult({
668
+ error: e,
669
+ context: {
670
+ id,
671
+ kind: kind ?? 'layout',
672
+ source: source ?? DEFAULT_LIBRARY_SOURCE,
673
+ images: resolvedImages,
674
+ error_code: LIBRARY_NOT_CONFIGURED,
675
+ ...libraryUnavailableContext(e),
676
+ },
677
+ hint: LIBRARY_NOT_CONFIGURED_HINT,
678
+ });
679
+ }
680
+ if (isLibraryUpstreamUnavailable(e)) {
681
+ return errorResult({
682
+ error: e,
683
+ context: {
684
+ id,
685
+ kind: kind ?? 'layout',
686
+ source: source ?? DEFAULT_LIBRARY_SOURCE,
687
+ images: resolvedImages,
688
+ error_code: LIBRARY_UPSTREAM_UNAVAILABLE,
689
+ ...libraryUnavailableContext(e),
690
+ },
691
+ hint: LIBRARY_UPSTREAM_UNAVAILABLE_HINT,
692
+ });
693
+ }
587
694
  return errorResult({
588
695
  error: e,
589
696
  context: { id, kind: kind ?? 'layout', source: source ?? DEFAULT_LIBRARY_SOURCE, images: resolvedImages },
@@ -591,26 +698,20 @@ export async function handleLibraryInsert({ client }, { id, kind, source, target
591
698
  });
592
699
  }
593
700
  }
594
- /**
595
- * PAGES_LIBRARY_MEDIA-4 — status/code-aware catch hint for `library_insert`.
596
- *
597
- * The former static hint always said "Verify the id via library_get first" —
598
- * but a 404 `not_found`/`unavailable` means the catalog item cannot be FETCHED
599
- * (no Library API key configured, or the id is genuinely absent), so library_get
600
- * returns an empty success and confirms nothing → a loop. On that class steer
601
- * to the real fixes (configure the apikey, or use the keyless source:"ai")
602
- * instead. Keep the target-column / stale-etag guidance for the 422/412 classes
603
- * they actually describe.
604
- */
605
701
  function libraryInsertHint(e) {
606
702
  if (e instanceof RestError) {
607
703
  const code = e.code ?? '';
608
- if (e.status === 404 || code.includes('not_found') || code.includes('unavailable')) {
609
- return ('The Library item could not be fetched (missing key or absent id). Do NOT ' +
610
- 'loop on yootheme_builder_library_get — it returns an empty success for an ' +
611
- 'unfetchable id. Either configure the YOOtheme Pro Library API key ' +
612
- '(Customizer → Theme Settings → apikey), or use source:"ai" (your keyless ' +
613
- '"WootsUp AI Layouts" store).');
704
+ if (isLibraryNotConfigured(e)) {
705
+ return LIBRARY_NOT_CONFIGURED_HINT;
706
+ }
707
+ if (isLibraryUpstreamUnavailable(e)) {
708
+ return LIBRARY_UPSTREAM_UNAVAILABLE_HINT;
709
+ }
710
+ if (e.status === 404 || code.includes('not_found')) {
711
+ return ('The Library item could not be fetched (no key configured, or the id is not in ' +
712
+ 'the catalog). Do NOT loop on yootheme_builder_library_get. Either configure the ' +
713
+ 'YOOtheme Pro Library API key (Customizer, Theme Settings, apikey), or use ' +
714
+ 'source:"ai" (your keyless "WootsUp AI Layouts" store).');
614
715
  }
615
716
  if (e.status === 422) {
616
717
  return ('A 422 target_not_a_column means a preset (kind:"element") was pointed at a ' +
@@ -626,7 +727,6 @@ function libraryInsertHint(e) {
626
727
  'hull first with yootheme_builder_scaffold_section. A 412 means the etag is stale — ' +
627
728
  're-read (get_etag/pages_list) and retry.');
628
729
  }
629
- // ─── library_save_as (Task 7) ────────────────────────────────────────────
630
730
  const LIBRARY_SAVE_SOURCE_SCHEMA = z.object({
631
731
  template_id: z
632
732
  .string()
@@ -642,6 +742,9 @@ export const LIBRARY_SAVE_AS_OUTPUT_SCHEMA = z.object({
642
742
  name: z.string(),
643
743
  storage: z.string(),
644
744
  invalid_source_possible: z.boolean(),
745
+ destination: z.string().optional(),
746
+ kind: z.string().optional(),
747
+ storage_note: z.string().optional(),
645
748
  });
646
749
  function mapLibrarySaveAsResult(raw) {
647
750
  if (raw === null || typeof raw !== 'object')
@@ -650,25 +753,31 @@ function mapLibrarySaveAsResult(raw) {
650
753
  if (typeof r.library_id !== 'string' || typeof r.name !== 'string' || typeof r.storage !== 'string') {
651
754
  return undefined;
652
755
  }
756
+ const storage = r.storage;
757
+ const isOwnStore = storage === 'ai' || storage === 'wootsup-ai-layouts';
653
758
  return {
654
759
  library_id: r.library_id,
655
760
  name: r.name,
656
- storage: r.storage,
761
+ storage,
657
762
  invalid_source_possible: r.invalid_source_possible === true,
763
+ storage_note: isOwnStore
764
+ ? 'Saved to your isolated "WootsUp AI Layouts" store, NOT to YOOtheme\'s '
765
+ + 'built-in My Layouts: it will not appear in the builder\'s Library panel. '
766
+ + 'Reuse it with library_browse/library_insert using source:"ai", remove it '
767
+ + 'with library_delete. Saving again with destination:"native" puts a copy '
768
+ + 'where the builder shows it, but that copy is one-way: no tool here can '
769
+ + 'find or delete it afterwards.'
770
+ : 'Saved to YOOtheme\'s built-in My Layouts, so the builder shows it under '
771
+ + 'that panel. This copy is ONE-WAY for this server: library_browse/'
772
+ + 'library_get/library_insert reach only "catalog" and "ai" entries, and '
773
+ + 'library_delete removes only "ai" ones, so nothing here can find, reuse '
774
+ + 'or remove it again. Save it with destination:"ai" as well if you want to '
775
+ + 'keep working with it from here.',
658
776
  };
659
777
  }
660
778
  export async function handleLibrarySaveAs({ client }, { source, name, destination }) {
661
- const guard = joomlaNotSupportedGuard(client, 'yootheme_builder_library_save_as', {
662
- template_id: source.template_id,
663
- });
664
- if (guard !== null)
665
- return guard;
666
779
  try {
667
780
  const body = { source, name };
668
- // Only forward a NON-default destination (the PHP MyLayoutsStore reads
669
- // the same absent-param `ai` default), so an ai-default save is
670
- // byte-identical to the pre-Task-7 body — only the `native` escape
671
- // hatch ever appears on the wire.
672
781
  if (destination !== undefined && destination !== DEFAULT_SAVE_DESTINATION)
673
782
  body.destination = destination;
674
783
  const data = await client.post('/library/save', { body });
@@ -688,12 +797,22 @@ export async function handleLibrarySaveAs({ client }, { source, name, destinatio
688
797
  });
689
798
  }
690
799
  }
691
- // ─── library_delete (PAGES_LIBRARY_MEDIA-16) ─────────────────────────────
692
- export const LIBRARY_DELETE_OUTPUT_SCHEMA = z.object({
800
+ export const LIBRARY_DELETE_OUTPUT_SCHEMA = z
801
+ .object({
693
802
  deleted: z.boolean(),
694
803
  id: z.string(),
695
804
  deleted_count: z.number(),
696
805
  source: z.string(),
806
+ ...CONFIRM_PREVIEW_FIELDS,
807
+ })
808
+ .superRefine((v, ctx) => {
809
+ if (v.requires_confirmation === true && v.deleted !== false) {
810
+ ctx.addIssue({
811
+ code: 'custom',
812
+ path: ['deleted'],
813
+ message: 'a confirm preview must report deleted:false — nothing was removed',
814
+ });
815
+ }
697
816
  });
698
817
  function mapLibraryDeleteResult(raw) {
699
818
  if (raw === null || typeof raw !== 'object')
@@ -709,14 +828,8 @@ function mapLibraryDeleteResult(raw) {
709
828
  };
710
829
  }
711
830
  export async function handleLibraryDelete({ client }, { id, source }) {
712
- const guard = joomlaNotSupportedGuard(client, 'yootheme_builder_library_delete', { id });
713
- if (guard !== null)
714
- return guard;
715
831
  try {
716
- const body = { id };
717
- // Only forward a NON-default source. The PHP controller reads the same
718
- // absent-param "ai" default; the curated catalog (source:"catalog") is
719
- // read-only, so forwarding it is what makes the controller answer 403.
832
+ const body = { id, confirm: true };
720
833
  if (source !== undefined && source !== 'ai')
721
834
  body.source = source;
722
835
  const data = await client.post('/library/delete', { body });
@@ -734,11 +847,6 @@ export async function handleLibraryDelete({ client }, { id, source }) {
734
847
  });
735
848
  }
736
849
  }
737
- /**
738
- * Status/code-aware catch hint for library_delete. A 404 means the id was
739
- * already gone (not a failure to act on — the end state is what you wanted); a
740
- * 403 means a read-only source (only source:"ai" is deletable).
741
- */
742
850
  function libraryDeleteHint(e) {
743
851
  if (e instanceof RestError) {
744
852
  if (e.status === 404) {
@@ -753,57 +861,57 @@ function libraryDeleteHint(e) {
753
861
  return ('Pass the `id` of an entry from yootheme_builder_library_browse({source:"ai"}). Only ' +
754
862
  'source:"ai" entries are deletable — the curated library is read-only.');
755
863
  }
756
- // ─── image_search / image_import (Task D1, Image Sourcing) ────────────────
757
- //
758
- // Thin GET/POST forwards to `/library/image-search` (Task C1) mirroring
759
- // library_browse's read-projection pattern, and `/library/image-import`
760
- // mirroring library_insert's write-forward pattern. Response shapes are
761
- // pinned by the PHP controller tests
762
- // (tests/php/unit/Library/LibraryControllerImageSearchTest.php +
763
- // LibraryControllerImageImportTest.php):
764
- // GET /library/image-search → {items, total, source, query, hint?}
765
- // POST /library/image-import → {imported:true, local_url} |
766
- // {imported:false, reason}
767
- const IMAGE_SOURCE_VALUES = ['unsplash', 'pexels', 'proimages'];
768
- /**
769
- * Cross-source orientation dialect (D1 concern, see the plan doc): the
770
- * Unsplash proxy accepts "squarish", the Pexels proxy accepts "square" for
771
- * the SAME concept. `ImageSearchService::buildQueryString()` (PHP) forwards
772
- * whatever string is supplied verbatim, per-source, with NO translation — so
773
- * this schema accepts BOTH spellings instead of forcing one source's
774
- * vocabulary onto the other (which would either reject a legitimate Pexels
775
- * "square" request or silently mis-filter it).
776
- */
864
+ const IMAGE_SOURCE_VALUES = ['unsplash', 'pexels', 'proimages', 'local'];
777
865
  const IMAGE_ORIENTATION_VALUES = ['landscape', 'portrait', 'squarish', 'square'];
866
+ const IMAGE_LICENSE_KEYS = {
867
+ license: z.string().nullable().optional(),
868
+ attribution_required: z.boolean().nullable().optional(),
869
+ license_url: z.string().optional(),
870
+ license_note: z.string().optional(),
871
+ };
778
872
  const IMAGE_RESULT_SCHEMA = z.object({
779
873
  id: z.string(),
780
874
  source: z.string(),
781
875
  thumb_url: z.string(),
782
876
  full_url: z.string(),
783
- width: z.number(),
784
- height: z.number(),
785
- alt: z.string(),
786
- author: z.string(),
787
- author_url: z.string(),
788
- orientation: z.string(),
877
+ width: z.number().nullable(),
878
+ height: z.number().nullable(),
879
+ alt: z.string().optional(),
880
+ author: z.string().optional(),
881
+ author_url: z.string().optional(),
882
+ orientation: z.string().optional(),
883
+ ...IMAGE_LICENSE_KEYS,
789
884
  });
790
885
  export const IMAGE_SEARCH_OUTPUT_SCHEMA = z.object({
791
886
  items: z.array(IMAGE_RESULT_SCHEMA),
792
887
  total: z.number(),
793
888
  source: z.string(),
794
889
  query: z.string(),
795
- // Present ONLY on the empty-result path (no Library API key configured,
796
- // or genuinely zero matches — the REST layer cannot and must not
797
- // distinguish the two; see LibraryControllerImageSearchTest).
798
890
  hint: z.string().optional(),
891
+ api_key_configured: z.boolean().optional(),
892
+ next_offset: z.number().int().min(0).optional(),
893
+ local_note: z.string().optional(),
799
894
  });
800
895
  export const IMAGE_IMPORT_OUTPUT_SCHEMA = z.object({
801
896
  imported: z.boolean(),
802
- // Present only when imported:true.
803
897
  local_url: z.string().optional(),
804
- // Present only when imported:false (a caught importer failure — never a 500).
805
898
  reason: z.string().optional(),
899
+ failure: z.enum(['rejected', 'unavailable']).optional(),
900
+ media_note: z.string().optional(),
901
+ ...IMAGE_LICENSE_KEYS,
806
902
  });
903
+ function pickImageLicense(r) {
904
+ return {
905
+ ...(typeof r.license === 'string' || r.license === null ? { license: r.license } : {}),
906
+ ...(typeof r.attribution_required === 'boolean' || r.attribution_required === null
907
+ ? { attribution_required: r.attribution_required }
908
+ : {}),
909
+ ...(typeof r.license_url === 'string' ? { license_url: r.license_url } : {}),
910
+ ...(typeof r.license_note === 'string' && r.license_note !== ''
911
+ ? { license_note: r.license_note }
912
+ : {}),
913
+ };
914
+ }
807
915
  function mapImageResult(raw) {
808
916
  if (raw === null || typeof raw !== 'object')
809
917
  return undefined;
@@ -815,12 +923,13 @@ function mapImageResult(raw) {
815
923
  source: typeof r.source === 'string' ? r.source : '',
816
924
  thumb_url: typeof r.thumb_url === 'string' ? r.thumb_url : '',
817
925
  full_url: typeof r.full_url === 'string' ? r.full_url : '',
818
- width: typeof r.width === 'number' ? r.width : 0,
819
- height: typeof r.height === 'number' ? r.height : 0,
820
- alt: typeof r.alt === 'string' ? r.alt : '',
821
- author: typeof r.author === 'string' ? r.author : '',
822
- author_url: typeof r.author_url === 'string' ? r.author_url : '',
823
- orientation: typeof r.orientation === 'string' ? r.orientation : '',
926
+ width: typeof r.width === 'number' ? r.width : null,
927
+ height: typeof r.height === 'number' ? r.height : null,
928
+ ...(typeof r.alt === 'string' ? { alt: r.alt } : {}),
929
+ ...(typeof r.author === 'string' ? { author: r.author } : {}),
930
+ ...(typeof r.author_url === 'string' ? { author_url: r.author_url } : {}),
931
+ ...(typeof r.orientation === 'string' ? { orientation: r.orientation } : {}),
932
+ ...pickImageLicense(r),
824
933
  };
825
934
  }
826
935
  const IMAGE_TABLE_COLUMNS = [
@@ -828,42 +937,44 @@ const IMAGE_TABLE_COLUMNS = [
828
937
  { key: 'dimensions', label: 'SIZE', width: 10 },
829
938
  { key: 'author', label: 'AUTHOR', width: 18 },
830
939
  { key: 'orientation', label: 'ORIENT', width: 10 },
831
- // llmOnly: the copyable next-call value (yootheme_builder_image_import's
832
- // `url` arg) — excluded from the Rich Card UI (too long for a table
833
- // cell) but present in the ASCII text the LLM reads. Always present in
834
- // structuredContent.items[].full_url regardless of table membership.
835
940
  { key: 'full_url', label: 'FULL_URL', width: 50, llmOnly: true },
836
941
  ];
837
- export async function handleImageSearch({ client }, { source, query, per_page, orientation, color, include_meta }) {
838
- const guard = joomlaNotSupportedGuard(client, 'yootheme_builder_image_search', { query });
839
- if (guard !== null)
840
- return guard;
942
+ export async function handleImageSearch({ client }, { source, query, per_page, orientation, color, offset, include_meta }) {
841
943
  const resolvedSource = source ?? 'unsplash';
842
944
  try {
843
945
  const qs = new URLSearchParams();
844
946
  qs.set('source', resolvedSource);
845
- qs.set('query', query);
947
+ if (typeof query === 'string' && query !== '')
948
+ qs.set('query', query);
846
949
  if (per_page !== undefined)
847
950
  qs.set('per_page', String(per_page));
848
951
  if (typeof orientation === 'string' && orientation !== '')
849
952
  qs.set('orientation', orientation);
850
953
  if (typeof color === 'string' && color !== '')
851
954
  qs.set('color', color);
955
+ if (offset !== undefined)
956
+ qs.set('offset', String(offset));
852
957
  const data = await client.get(`/library/image-search?${qs.toString()}`);
853
958
  const rawItems = Array.isArray(data.items) ? data.items : [];
854
959
  const items = rawItems.map(mapImageResult).filter((i) => i !== undefined);
855
960
  const total = typeof data.total === 'number' ? data.total : items.length;
856
961
  const resolvedResponseSource = typeof data.source === 'string' ? data.source : resolvedSource;
857
- const resolvedQuery = typeof data.query === 'string' ? data.query : query;
962
+ const resolvedQuery = typeof data.query === 'string' ? data.query : (query ?? '');
858
963
  const hint = typeof data.hint === 'string' ? data.hint : undefined;
964
+ const nextOffset = typeof data.next_offset === 'number' && Number.isInteger(data.next_offset) && data.next_offset >= 0
965
+ ? data.next_offset
966
+ : undefined;
967
+ const localNote = typeof data.local_note === 'string' && data.local_note !== '' ? data.local_note : undefined;
968
+ const apiKeyConfigured = typeof data.api_key_configured === 'boolean' ? data.api_key_configured : undefined;
859
969
  const rows = items.map((i) => ({
860
970
  ...i,
861
- dimensions: `${String(i.width)}x${String(i.height)}`,
971
+ dimensions: `${i.width === null ? '?' : String(i.width)}x${i.height === null ? '?' : String(i.height)}`,
862
972
  }));
863
973
  const toolkitResult = budgetedTableResult(rows, {
864
974
  columns: [...IMAGE_TABLE_COLUMNS],
865
975
  header: (count) => `${String(count)} of ${String(total)} ${resolvedResponseSource} image${total === 1 ? '' : 's'} for "${resolvedQuery}"`,
866
976
  footer: hint
977
+ ?? localNote
867
978
  ?? 'Import a chosen row\'s full_url with yootheme_builder_image_import, then bind '
868
979
  + 'the returned local_url via element_update_settings.',
869
980
  includeMeta: include_meta,
@@ -874,18 +985,108 @@ export async function handleImageSearch({ client }, { source, query, per_page, o
874
985
  source: resolvedResponseSource,
875
986
  query: resolvedQuery,
876
987
  ...(hint !== undefined ? { hint } : {}),
988
+ ...(nextOffset !== undefined ? { next_offset: nextOffset } : {}),
989
+ ...(localNote !== undefined ? { local_note: localNote } : {}),
990
+ ...(apiKeyConfigured !== undefined ? { api_key_configured: apiKeyConfigured } : {}),
877
991
  });
878
992
  }
879
993
  catch (e) {
994
+ if (isImageSearchNotConfigured(e)) {
995
+ return errorResult({
996
+ error: e,
997
+ context: {
998
+ source: resolvedSource,
999
+ query,
1000
+ error_code: IMAGE_SEARCH_NOT_CONFIGURED,
1001
+ ...imageSearchUnavailableContext(e),
1002
+ },
1003
+ hint: IMAGE_SEARCH_NOT_CONFIGURED_HINT,
1004
+ });
1005
+ }
1006
+ if (isImageSearchUnavailable(e)) {
1007
+ return errorResult({
1008
+ error: e,
1009
+ context: {
1010
+ source: resolvedSource,
1011
+ query,
1012
+ error_code: 'image_search_unavailable',
1013
+ ...imageSearchUnavailableContext(e),
1014
+ },
1015
+ hint: IMAGE_SEARCH_UNAVAILABLE_HINT,
1016
+ });
1017
+ }
880
1018
  return errorResult({
881
1019
  error: e,
882
1020
  context: { source: resolvedSource, query, per_page, orientation, color },
883
- hint: 'total:0 usually means no YOOtheme Pro Library API key is configured (the same '
884
- + 'theme apikey library_browse/insert use). Run yootheme_builder_diagnose to verify auth. '
1021
+ hint: 'Run yootheme_builder_diagnose to verify auth and connectivity, then retry. '
885
1022
  + 'yootheme_builder_image_import still works with a direct image URL (no key needed).',
886
1023
  });
887
1024
  }
888
1025
  }
1026
+ const IMAGE_SEARCH_UNAVAILABLE = 'image_search_unavailable';
1027
+ export const IMAGE_SEARCH_UNAVAILABLE_HINT = 'This is a SEARCH FAILURE, not an empty result. Do NOT retry with a broader query; the '
1028
+ + 'provider never answered with results. An HTTP 401 here means the YOOtheme Pro Library API '
1029
+ + 'key this site has configured was REFUSED for this call. WHY it was refused is not visible '
1030
+ + 'from here, so do NOT name a cause: report that the key was refused and let the site owner '
1031
+ + 'check it with YOOtheme. yootheme_builder_image_import still works with a direct Unsplash, '
1032
+ + 'Pexels or YOOtheme library image URL (no key needed).';
1033
+ const IMAGE_SEARCH_NOT_CONFIGURED = 'image_search_api_key_not_configured';
1034
+ export const IMAGE_SEARCH_NOT_CONFIGURED_HINT = 'This is a CONFIGURATION GAP, not an empty result: no YOOtheme Pro Library API key could be '
1035
+ + 'read on this site, so no search request was made and nothing is known about what the '
1036
+ + 'provider holds. Do NOT retry with a broader query. Run yootheme_builder_diagnose and read '
1037
+ + 'library_key.state before naming a fix: it separates "no key entered", which the customer '
1038
+ + 'fixes in the YOOtheme Customizer theme settings, from a lookup that found no YOOtheme update '
1039
+ + 'site at all, where there is nothing to fix in the key field. yootheme_builder_image_import '
1040
+ + 'still works with a direct Unsplash, Pexels or YOOtheme library image URL (no key needed).';
1041
+ function imageSearchErrorBody(e) {
1042
+ if (!(e instanceof RestError))
1043
+ return undefined;
1044
+ const body = e.body;
1045
+ if (body === null || typeof body !== 'object' || Array.isArray(body))
1046
+ return undefined;
1047
+ return body;
1048
+ }
1049
+ function isImageSearchUnavailable(e) {
1050
+ return imageSearchCodeMatches(e, IMAGE_SEARCH_UNAVAILABLE);
1051
+ }
1052
+ function isImageSearchNotConfigured(e) {
1053
+ return imageSearchCodeMatches(e, IMAGE_SEARCH_NOT_CONFIGURED);
1054
+ }
1055
+ function imageSearchCodeMatches(e, code) {
1056
+ if (e instanceof RestError && typeof e.code === 'string' && e.code.endsWith(code)) {
1057
+ return true;
1058
+ }
1059
+ const body = imageSearchErrorBody(e);
1060
+ if (body === undefined)
1061
+ return false;
1062
+ if (body.error_code === code)
1063
+ return true;
1064
+ const data = body.data;
1065
+ return (data !== null
1066
+ && typeof data === 'object'
1067
+ && !Array.isArray(data)
1068
+ && data.error_code === code);
1069
+ }
1070
+ function imageSearchUnavailableContext(e) {
1071
+ const body = imageSearchErrorBody(e);
1072
+ if (body === undefined)
1073
+ return {};
1074
+ const data = body.data !== null && typeof body.data === 'object' && !Array.isArray(body.data)
1075
+ ? body.data
1076
+ : {};
1077
+ const out = {};
1078
+ const upstream = body.upstream_status ?? data.upstream_status;
1079
+ if (typeof upstream === 'number')
1080
+ out.upstream_status = upstream;
1081
+ const configured = body.api_key_configured ?? data.api_key_configured;
1082
+ if (typeof configured === 'boolean')
1083
+ out.api_key_configured = configured;
1084
+ return out;
1085
+ }
1086
+ const IMAGE_IMPORT_FAILURE_VALUES = ['rejected', 'unavailable'];
1087
+ function mapImageImportFailure(raw) {
1088
+ return IMAGE_IMPORT_FAILURE_VALUES.find((v) => v === raw);
1089
+ }
889
1090
  function mapImageImportResult(raw) {
890
1091
  if (raw === null || typeof raw !== 'object')
891
1092
  return undefined;
@@ -895,17 +1096,41 @@ function mapImageImportResult(raw) {
895
1096
  if (r.imported) {
896
1097
  if (typeof r.local_url !== 'string' || r.local_url === '')
897
1098
  return undefined;
898
- return { imported: true, local_url: r.local_url };
1099
+ return {
1100
+ imported: true,
1101
+ local_url: r.local_url,
1102
+ ...(typeof r.media_note === 'string' && r.media_note !== '' ? { media_note: r.media_note } : {}),
1103
+ ...pickImageLicense(r),
1104
+ };
899
1105
  }
1106
+ const failure = mapImageImportFailure(r.failure);
900
1107
  return {
901
1108
  imported: false,
902
1109
  reason: typeof r.reason === 'string' && r.reason !== '' ? r.reason : 'Import failed for an unknown reason.',
1110
+ ...(failure === undefined ? {} : { failure }),
903
1111
  };
904
1112
  }
905
- export async function handleImageImport({ client }, { url }) {
906
- const guard = joomlaNotSupportedGuard(client, 'yootheme_builder_image_import', { url });
907
- if (guard !== null)
908
- return guard;
1113
+ export async function handleImageImport({ client }, args) {
1114
+ if (args.url !== undefined
1115
+ && args.full_url !== undefined
1116
+ && args.url !== args.full_url) {
1117
+ return errorResult({
1118
+ error: new Error('The "url" and "full_url" arguments are two names for ONE value and were '
1119
+ + 'given different ones. Pass only the url to import.'),
1120
+ context: { url: args.url, full_url: args.full_url },
1121
+ hint: 'Drop one of the two. "full_url" is accepted only because that is the name a '
1122
+ + 'yootheme_builder_image_search row carries.',
1123
+ });
1124
+ }
1125
+ const url = args.url ?? args.full_url;
1126
+ if (url === undefined || url === '') {
1127
+ return errorResult({
1128
+ error: new Error('The "url" argument is required — a full_url from yootheme_builder_image_search '
1129
+ + 'is accepted under either name.'),
1130
+ context: {},
1131
+ hint: 'Pass a full_url from yootheme_builder_image_search.',
1132
+ });
1133
+ }
909
1134
  try {
910
1135
  const data = await client.post('/library/image-import', { body: { url } });
911
1136
  const mapped = mapImageImportResult(data);
@@ -915,16 +1140,6 @@ export async function handleImageImport({ client }, { url }) {
915
1140
  return structuredResult(jsonResult(mapped), mapped);
916
1141
  }
917
1142
  catch (e) {
918
- // T3 (2026-07-25, Sheet-0 handover) — the REST route returns the graceful
919
- // { imported:false, reason } envelope for SSRF-disallowed-host /
920
- // invalid-url / authz-denied rejections but with a 4xx STATUS, so the
921
- // client raises a RestError and the raw HTTP error would otherwise hide
922
- // the reason (the "image_import just 400s with no explanation" report).
923
- // When the error body IS that graceful envelope, surface it as a normal
924
- // { imported:false, reason } result — identical to the 200-status
925
- // graceful paths above — so the agent sees WHY (e.g. "host not on the
926
- // image-import allowlist") and can recover with an allowlisted full_url
927
- // from yootheme_builder_image_search.
928
1143
  if (e instanceof RestError) {
929
1144
  const mapped = mapImageImportResult(e.body);
930
1145
  if (mapped !== undefined && !mapped.imported) {
@@ -936,7 +1151,7 @@ export async function handleImageImport({ client }, { url }) {
936
1151
  context: { url },
937
1152
  hint: 'Verify the url is a reachable http(s) image URL (typically a full_url from '
938
1153
  + 'yootheme_builder_image_search). A read_only_token error means this bearer key '
939
- + 'needs the write scope — regenerate it in YT Builder MCP → Keys.',
1154
+ + 'needs the write scope — regenerate it in MCP for YOOtheme Pro Bearer Keys.',
940
1155
  });
941
1156
  }
942
1157
  }
@@ -945,12 +1160,15 @@ export function buildLibraryTools(pool) {
945
1160
  defineTool({
946
1161
  name: 'yootheme_builder_library_browse',
947
1162
  description: 'Browse the YOOtheme Pro Library catalog: pre-designed page LAYOUTS, element ' +
948
- 'PRESETS, or purpose-classified SECTIONS (hero/features/cta/...). Filter with ' +
949
- '`kind` ("layout" default | "element" | "section"), `topic`, `type`/`role`, ' +
950
- '`element_type`, `purpose`/`has_effects` (sections only), `website`, `query`. ' +
951
- 'Returns `{items, total, next_cursor}`; a "section" id is the composite ' +
952
- '"<layoutId>#<sectionIndex>" feeds straight into library_get/library_insert. ' +
953
- '`total:0` usually means no Library API key configured.',
1163
+ 'PRESETS, or purpose-classified SECTIONS. Filter: `kind`, `topic`, `type`/`role`, ' +
1164
+ '`element_type`, `purpose`, `query`. A section id is the composite ' +
1165
+ '"<layoutId>#<sectionIndex>" feeds library_get/insert. ' +
1166
+ '`source`: "catalog" (default) or "ai" YOUR saved entries from ' +
1167
+ 'library_save_as, which library_delete removes again both gateway-only, ' +
1168
+ 'call via yootheme_builder_advanced({tool, arguments}). `total:0` = the ' +
1169
+ 'catalog matched nothing; a read with no data answers ' +
1170
+ '`library_upstream_unavailable` or `library_api_key_not_configured`, never an ' +
1171
+ 'empty page.',
954
1172
  inputSchema: {
955
1173
  site_id: SITE_ID_SCHEMA,
956
1174
  kind: z
@@ -985,7 +1203,13 @@ export function buildLibraryTools(pool) {
985
1203
  .string()
986
1204
  .min(1)
987
1205
  .optional()
988
- .describe('Page-role filter (e.g. "Home", "Contact", "Services"). Only applies when kind="layout". Exact match.'),
1206
+ .describe('Filters on how the SOURCE demo website filed this layout in ITS OWN menu ' +
1207
+ '(e.g. "Home", "Contact", "Services") — sourced verbatim from the YOOtheme ' +
1208
+ 'Pro Library catalog, NOT a classification of what the layout actually ' +
1209
+ 'builds. A layout can carry a role unrelated to its content if that is ' +
1210
+ 'where the source site\'s own navigation put it; prefer `query` (name ' +
1211
+ 'match) or browsing without this filter when the exact wording is ' +
1212
+ 'unknown. Only applies when kind="layout". Exact match.'),
989
1213
  website: z
990
1214
  .string()
991
1215
  .min(1)
@@ -999,7 +1223,11 @@ export function buildLibraryTools(pool) {
999
1223
  purpose: z
1000
1224
  .enum(SECTION_PURPOSE_VALUES)
1001
1225
  .optional()
1002
- .describe('Section-purpose filter (e.g. "hero", "features", "cta"). Only applies when kind="section". Exact match.'),
1226
+ .describe('Section-purpose filter (e.g. "hero", "features", "cta"). Only applies when ' +
1227
+ 'kind="section". Exact match. BROAD buckets, not topics: hundreds of sections ' +
1228
+ 'share one, none names a specific subject, so the closest-looking value is a ' +
1229
+ 'guess. For a subject ("services") use `query` instead - it matches the ' +
1230
+ 'section NAME.'),
1003
1231
  has_effects: z
1004
1232
  .boolean()
1005
1233
  .optional()
@@ -1029,13 +1257,12 @@ export function buildLibraryTools(pool) {
1029
1257
  }),
1030
1258
  defineTool({
1031
1259
  name: 'yootheme_builder_library_get',
1032
- description: 'Fetch a SINGLE YOOtheme Pro Library layout or element preset by `id` — call ' +
1033
- 'after yootheme_builder_library_browse (items[].id feeds this). Returns ' +
1034
- '`{id, kind, name, version, tree, sections, images_manifest_count}`: `tree` is ' +
1035
- 'the raw JSON; `sections` lists `tree.children` (layouts only, presets return ' +
1036
- '`[]`), each `{index, name, path, element_count}`. `include_thumbnail:true` ' +
1037
- 'also returns an image block (costs tokens see its own description). ' +
1038
- 'Read-only; does not insert/bind.',
1260
+ description: 'Fetch ONE Library layout or element preset by `id` — call after ' +
1261
+ 'library_browse (items[].id feeds this). `include_thumbnail:true` adds an ' +
1262
+ 'image (costs tokens). This DETAIL endpoint checks the key, browse does not: no ' +
1263
+ 'key gives 503 library_api_key_not_configured, a refused key 503 ' +
1264
+ 'library_upstream_unavailable. Report the refusal, not a cause. `source`: ' +
1265
+ '"catalog" (default) or "ai" for your own library_save_as (gateway-only) entries.',
1039
1266
  inputSchema: {
1040
1267
  site_id: SITE_ID_SCHEMA,
1041
1268
  id: z
@@ -1054,6 +1281,15 @@ export function buildLibraryTools(pool) {
1054
1281
  .describe('Which library the `id` belongs to: "catalog" (remote YOOtheme Pro Library, ' +
1055
1282
  'default) or "ai" (your OWN "WootsUp AI Layouts"). Must match the source the ' +
1056
1283
  'id came from in yootheme_builder_library_browse.'),
1284
+ max_chars: MAX_CHARS.describe('Cap (chars) on this result. On THIS tool it ALSO bounds structuredContent — ' +
1285
+ 'an oversized `tree` is replaced by a compact summary inside the same key ' +
1286
+ '(element_type_counts, truncated:true, bytes_full, hint) instead of riding ' +
1287
+ 'through whole; small items are unaffected. Applies even when omitted ' +
1288
+ '(default safety cap: 20000 chars). Over the plugin-served endpoint both ' +
1289
+ 'legs already carry the same shortened payload. ' +
1290
+ 'Raising it stops helping at the limit your own client puts on a single tool ' +
1291
+ 'result, which this server cannot see. ' +
1292
+ 'Out of range, the cap that actually applied comes back as `max_chars`.'),
1057
1293
  include_thumbnail: z
1058
1294
  .boolean()
1059
1295
  .default(false)
@@ -1069,13 +1305,14 @@ export function buildLibraryTools(pool) {
1069
1305
  }),
1070
1306
  defineTool({
1071
1307
  name: 'yootheme_builder_library_insert',
1072
- description: 'Insert a YOOtheme Pro Library item (layout, section, or preset) onto a page — ' +
1073
- 'call after yootheme_builder_library_get/browse. `target`: new_page (creates a ' +
1074
- 'page), {template_id,element_path?} (splices onto the layout root, or a preset ' +
1075
- 'onto a Column), or sublayout (splices rows into a fragment). A preset ' +
1076
- '(kind:"element") MUST target a Column 422 otherwise; scaffold one first with ' +
1077
- 'yootheme_builder_scaffold_section. `images`: "placeholder" (default) or ' +
1078
- '"curated" (downloads to Media Library). Optional `etag` locks (412 if stale).',
1308
+ description: 'Insert a Library item onto a page — call after library_get/browse. `target`: ' +
1309
+ 'new_page, {template_id,element_path?} (splices onto the layout root, or a ' +
1310
+ 'preset onto a Column), or sublayout. A preset (kind:"element") MUST target a ' +
1311
+ 'Column (422 otherwise) scaffold one with scaffold_section. `images`: ' +
1312
+ '"placeholder" (default) or "curated" (downloads to Media Library). Placement: ' +
1313
+ '`insert_mode` for layouts, `index` for presets. `etag` ' +
1314
+ 'locks (412 if stale). `source`: "catalog" (default) or "ai" to insert your own ' +
1315
+ 'saved entry.',
1079
1316
  inputSchema: {
1080
1317
  site_id: SITE_ID_SCHEMA,
1081
1318
  id: z
@@ -1105,29 +1342,42 @@ export function buildLibraryTools(pool) {
1105
1342
  .enum(['placeholder', 'curated'])
1106
1343
  .default('placeholder')
1107
1344
  .describe('"placeholder" (default, safe, no network I/O) or "curated" (downloads + sideloads every unique image into the Media Library).'),
1108
- etag: z
1109
- .string()
1345
+ insert_mode: z
1346
+ .enum(['replace', 'top', 'bottom'])
1347
+ .default('replace')
1348
+ .describe('How the composed content joins the target\'s EXISTING children — mirrors the ' +
1349
+ 'Library dialog\'s own picker. "replace" (default) drops them; "top"/"bottom" ' +
1350
+ 'prepend/append. Ignored for target:new_page (a fresh page starts empty). ' +
1351
+ 'REFUSED for kind:"element" — a preset takes `index`.'),
1352
+ index: z
1353
+ .number()
1354
+ .int()
1355
+ .min(0)
1110
1356
  .optional()
1111
- .describe('Optional optimistic-lock ETag (from get_etag/pages_list). Mismatched 412; caller must re-read and retry.'),
1357
+ .describe('kind:"element" ONLY (refused otherwise): where the preset lands among the ' +
1358
+ 'Column\'s children. 0 = first, omit to append, past-end clamps — as in ' +
1359
+ 'element_add. A preset never replaces what the Column holds.'),
1360
+ confirm: z
1361
+ .boolean()
1362
+ .optional()
1363
+ .describe('Set true to proceed when insert_mode:"replace" would remove the target\'s ' +
1364
+ 'existing content. Omit for a preview naming what would be lost; "top"/' +
1365
+ '"bottom", an empty target and kind:"element" (a preset only ever adds) ' +
1366
+ 'never need it. ' +
1367
+ HOST_PROMPT_PROMISE),
1368
+ etag: ETAG,
1112
1369
  },
1113
1370
  outputSchema: LIBRARY_INSERT_OUTPUT_SCHEMA,
1114
- annotations: creating('Insert Layout Library Item'),
1371
+ annotations: destructive('Insert Layout Library Item'),
1115
1372
  handler: async ({ site_id, ...rest }) => withWriteSiteRetry(pool, site_id, 'yootheme_builder_library_insert', async (client, site) => withSiteMeta(await handleLibraryInsert({ client }, rest), site)),
1116
1373
  }),
1117
- // Task 7 (Layout Library, Spec #7) — ADVANCED tool (deliberately NOT
1118
- // added to essentials.ts/ESSENTIAL_TOOLS): reachable ONLY via
1119
- // yootheme_builder_advanced({tool, arguments}), never a first-class
1120
- // tools/list entry. See the module docblock's "Tiering" note.
1121
1374
  defineTool({
1122
1375
  name: 'yootheme_builder_library_save_as',
1123
1376
  description: 'Save a page or subtree into a reusable library — the REVERSE of ' +
1124
- 'yootheme_builder_library_insert. `source`: {template_id, ' +
1125
- 'element_path?} (omit element_path to save the WHOLE page). ' +
1126
- '`destination`: "ai" (default your isolated "WootsUp AI Layouts", ' +
1127
- 'reusable via library_browse/insert source:"ai") or "native" ' +
1128
- '(YOOtheme\'s built-in My Layouts). Returns `{library_id, name, ' +
1129
- 'storage, invalid_source_possible}` (the last flags bound sources ' +
1130
- 'that may need re-binding after reload). Advanced/gateway-only.',
1377
+ 'library_insert. `source`: {template_id, element_path?} (omit element_path to ' +
1378
+ 'save the WHOLE page). `destination`: "ai" (default — your isolated "WootsUp AI ' +
1379
+ 'Layouts", reusable via library_browse source:"ai") or "native" (YOOtheme\'s ' +
1380
+ 'built-in My Layouts).',
1131
1381
  inputSchema: {
1132
1382
  site_id: SITE_ID_SCHEMA,
1133
1383
  source: LIBRARY_SAVE_SOURCE_SCHEMA.describe('The page/subtree to save.'),
@@ -1135,10 +1385,6 @@ export function buildLibraryTools(pool) {
1135
1385
  .string()
1136
1386
  .min(1)
1137
1387
  .describe('Display name for the new library entry (e.g. "Hero Section").'),
1138
- // PAGES_LIBRARY_MEDIA-15: `.optional()` (not `.default('ai')`) so
1139
- // the generated schema does NOT advertise a REQUIRED field that
1140
- // also carries a default. The handler applies the `ai` default
1141
- // when omitted (it only forwards a NON-default `native`).
1142
1388
  destination: z
1143
1389
  .enum(SAVE_DESTINATION_VALUES)
1144
1390
  .optional()
@@ -1148,31 +1394,15 @@ export function buildLibraryTools(pool) {
1148
1394
  '(the escape hatch: save into YOOtheme\'s built-in "My Layouts").'),
1149
1395
  },
1150
1396
  outputSchema: LIBRARY_SAVE_AS_OUTPUT_SCHEMA,
1151
- // PAGES_LIBRARY_MEDIA-15: title names the DEFAULT destination (the AI
1152
- // Layouts store), not "My Layouts" (which is the non-default `native`).
1153
1397
  annotations: creating('Save to AI Layouts library'),
1154
1398
  handler: async ({ site_id, ...rest }) => withWriteSiteRetry(pool, site_id, 'yootheme_builder_library_save_as', async (client, site) => withSiteMeta(await handleLibrarySaveAs({ client }, rest), site)),
1155
1399
  }),
1156
- // PAGES_LIBRARY_MEDIA-16 — ADVANCED tool (deliberately NOT added to
1157
- // essentials.ts/ESSENTIAL_TOOLS): reachable ONLY via
1158
- // yootheme_builder_advanced({tool, arguments}). The INVERSE of
1159
- // library_save_as. Annotated destructive() — it IRREVERSIBLY removes a
1160
- // saved AI-Layouts entry (a layout the user deliberately saved via
1161
- // library_save_as; there is no undo). This matches the codebase's own
1162
- // "irreversible → destructive → host confirm-gate" precedent:
1163
- // element_delete + pages_delete are destructive (audit F9), and
1164
- // clean_implode_directives was re-classified mutating→destructive in
1165
- // Wave H5 for the same reason (cannot be restored). Deleting saved user
1166
- // data should fire the host's destructive-approval gate, not slip
1167
- // through as an ordinary mutating write.
1168
1400
  defineTool({
1169
1401
  name: 'yootheme_builder_library_delete',
1170
- description: 'Delete ONE saved entry from your "WootsUp AI Layouts" store (the isolated library ' +
1171
- 'yootheme_builder_library_save_as writes into) — clean up experimental sections/layouts ' +
1172
- 'the agent saved. Pass the `id` from a library_browse(source:"ai") row. `source` defaults ' +
1173
- 'to "ai", the ONLY deletable store; the curated library is read-only, so source:"catalog" ' +
1174
- 'returns a 403. IRREVERSIBLE — requires confirm:true. Returns `{deleted, id, deleted_count, ' +
1175
- 'source}`; an unknown id returns a 404. Advanced/gateway-only.',
1402
+ description: 'Delete ONE saved entry from your "WootsUp AI Layouts" store (library_save_as ' +
1403
+ 'writes into it) — pass the `id` from a library_browse(source:"ai") row. ' +
1404
+ '`source` defaults to "ai", the ONLY deletable store; the curated library is ' +
1405
+ 'read-only, source:"catalog" returns 403. IRREVERSIBLE — requires confirm:true.',
1176
1406
  inputSchema: {
1177
1407
  site_id: SITE_ID_SCHEMA,
1178
1408
  id: z
@@ -1186,9 +1416,6 @@ export function buildLibraryTools(pool) {
1186
1416
  .describe('Which library to delete from. Only "ai" (your OWN "WootsUp AI Layouts") is ' +
1187
1417
  'deletable; "catalog" (the curated remote YOOtheme Pro Library) is read-only ' +
1188
1418
  'and returns a 403. Omit to default to "ai".'),
1189
- // Destructive-tool confirm gate (matches element_delete / pages_delete):
1190
- // z.literal(true) makes the MCP boundary REJECT any call without
1191
- // confirm:true, so an irreversible delete can never fire by accident.
1192
1419
  confirm: z
1193
1420
  .literal(true)
1194
1421
  .describe('Required — must be `true`. Deleting a saved AI-Layouts entry is IRREVERSIBLE ' +
@@ -1198,27 +1425,33 @@ export function buildLibraryTools(pool) {
1198
1425
  annotations: destructive('Delete AI Layouts entry'),
1199
1426
  handler: async ({ site_id, ...rest }) => withWriteSiteRetry(pool, site_id, 'yootheme_builder_library_delete', async (client, site) => withSiteMeta(await handleLibraryDelete({ client }, rest), site)),
1200
1427
  }),
1201
- // Task D1 (Image Sourcing) — read-only forward, mirrors library_browse.
1202
1428
  defineTool({
1203
1429
  name: 'yootheme_builder_image_search',
1204
- description: 'Search real on-topic photos (Unsplash/Pexels) or YOOtheme Pro Images to fill '
1205
- + 'layout image slots with REAL, on-brand photography instead of bare '
1206
- + 'placeholders. Returns candidate rows with `full_url` (pass straight to '
1207
- + 'yootheme_builder_image_import) plus dimensions/author/orientation. '
1208
- + '`total:0` usually means no Library API key is configured (the same theme '
1209
- + 'apikey library_browse/insert use).',
1430
+ description: 'Search on-topic photos (Unsplash/Pexels), YOOtheme Pro Images, or (source:"local") '
1431
+ + 'this site\'s own media library. Rows carry `full_url` (feeds image_import) plus '
1432
+ + 'dimensions/author. `total:0` is the ONLY shape meaning the provider matched '
1433
+ + 'nothing; a search that never happened answers an `error_code` instead. No '
1434
+ + 'apikey gives `image_search_api_key_not_configured`; a provider that REFUSES '
1435
+ + 'gives `error_code: image_search_unavailable`, which no broader query can fix. '
1436
+ + 'A key can be CONFIGURED and still be REFUSED (401): report that the upstream '
1437
+ + 'refused it, never why (source:"local" is exempt — see its field description). '
1438
+ + 'Works on WordPress and Joomla.',
1210
1439
  inputSchema: {
1211
1440
  site_id: SITE_ID_SCHEMA,
1212
1441
  source: z
1213
1442
  .enum(IMAGE_SOURCE_VALUES)
1214
1443
  .default('unsplash')
1215
1444
  .describe('Image source. unsplash/pexels = real photography; proimages = YOOtheme '
1216
- + 'Pro curated decorative/background assets, not topical photography.'),
1445
+ + 'Pro curated decorative/background assets, not topical photography; '
1446
+ + 'local = this site\'s own media library — no key, no image_import '
1447
+ + '(bind full_url directly), paginated via `offset`/`next_offset`.'),
1217
1448
  query: z
1218
1449
  .string()
1219
1450
  .min(1)
1451
+ .optional()
1220
1452
  .describe('Search terms, e.g. "coffee roasting", "modern architecture interior". '
1221
- + 'Search deliberately by industry + tone, not a generic term like "business".'),
1453
+ + 'Search deliberately by industry + tone, not a generic term like "business". '
1454
+ + 'Optional only for source:"local" (lists without one); the other three require it.'),
1222
1455
  per_page: z
1223
1456
  .number()
1224
1457
  .int()
@@ -1229,40 +1462,59 @@ export function buildLibraryTools(pool) {
1229
1462
  orientation: z
1230
1463
  .enum(IMAGE_ORIENTATION_VALUES)
1231
1464
  .optional()
1232
- .describe('landscape for heroes/banners, portrait for cards. "squarish" is the '
1233
- + 'Unsplash spelling, "square" is the Pexels spelling for the same shape. '
1234
- + 'Both are accepted; ignored for proimages.'),
1465
+ .describe('landscape for heroes/banners, portrait for cards. Each square spelling '
1466
+ + 'belongs to ONE source: "squarish" to unsplash, "square" to pexels. '
1467
+ + 'Crossing them fails two different ways, and only one is visible: '
1468
+ + '"square" on unsplash is refused upstream (HTTP 400, '
1469
+ + 'image_search_unavailable), while "squarish" on pexels is silently '
1470
+ + 'ignored and you get unfiltered rows back. Even on its own source '
1471
+ + '"squarish" is approximate: unsplash still returns landscape and '
1472
+ + 'portrait rows for it. Ignored for proimages and local.'),
1235
1473
  color: z
1236
1474
  .string()
1237
1475
  .min(1)
1238
1476
  .optional()
1239
- .describe('Optional upstream color filter (e.g. "black_and_white"). Passed through as-is.'),
1477
+ .describe('Optional upstream color filter (e.g. "black_and_white"). Passed through as-is; ignored for local.'),
1478
+ offset: z
1479
+ .number()
1480
+ .int()
1481
+ .min(0)
1482
+ .optional()
1483
+ .describe('source:"local" only: pass back a prior response\'s `next_offset` to continue. '
1484
+ + 'Ignored by the three proxy sources.'),
1240
1485
  include_meta: INCLUDE_META_SCHEMA,
1241
1486
  },
1242
1487
  outputSchema: IMAGE_SEARCH_OUTPUT_SCHEMA,
1243
1488
  annotations: readOnly('Search Images'),
1244
1489
  handler: async ({ site_id, ...rest }) => withSiteRetry(pool, site_id, async (client, site) => withSiteMeta(await handleImageSearch({ client }, rest), site)),
1245
1490
  }),
1246
- // Task D1 (Image Sourcing) — write forward, mirrors library_insert.
1247
- // mutating() (writes to the Media Library) but NOT destructive() — it
1248
- // only creates, never deletes (Global Constraints, plan doc).
1249
1491
  defineTool({
1250
1492
  name: 'yootheme_builder_image_import',
1251
- description: 'Sideload a photo into the site\'s own Media Library. Accepts ANY public image '
1252
- + 'URL it does NOT require a Library API key, so it still works when '
1253
- + 'image_search is unavailable (typically pass a `full_url` from '
1254
- + 'yootheme_builder_image_search). Returns `{imported:true, local_url}` where '
1255
- + 'local_url is a SITE-RELATIVE path (e.g. /wp-content/uploads/...). Bind it via '
1256
- + 'element_update_settings({props:{image: local_url}, merge:true}). A caught '
1257
- + 'import failure returns `{imported:false, reason}`, never a 500.',
1493
+ description: 'Sideload a photo into the site\'s Media Library. Only Unsplash, Pexels and the '
1494
+ + 'YOOtheme library CDN are accepted; other hosts are refused. Returns '
1495
+ + '`{imported:true, local_url}`; shape is platform-specific: WordPress leads '
1496
+ + 'with a slash (/wp-content/uploads/...), Joomla does NOT (images/yootheme/...). '
1497
+ + 'Bind it VERBATIM via element_update_settings({props:{image: local_url}, ' +
1498
+ 'merge:true}). Give the address as `url` OR as `full_url`, the field name an ' +
1499
+ 'image_search row carries; one of the two is required. Identical re-imports ' +
1500
+ 'reuse the existing file in the current upload folder. Failures return ' +
1501
+ '`{imported:false, reason, failure}`; `unavailable` = no url works now.',
1258
1502
  inputSchema: {
1259
1503
  site_id: SITE_ID_SCHEMA,
1260
1504
  url: z
1261
1505
  .string()
1262
1506
  .url()
1507
+ .optional()
1263
1508
  .describe('The image URL to sideload (typically a full_url from '
1264
1509
  + 'yootheme_builder_image_search). Downloads it into the site Media '
1265
1510
  + 'Library and returns a local URL to bind via element_update_settings.'),
1511
+ full_url: z
1512
+ .string()
1513
+ .url()
1514
+ .optional()
1515
+ .describe('Second name for `url` — the field name an image_search row carries, so a '
1516
+ + 'row can be passed on unchanged. Pass ONE of the two; two different '
1517
+ + 'values are refused rather than reconciled.'),
1266
1518
  },
1267
1519
  outputSchema: IMAGE_IMPORT_OUTPUT_SCHEMA,
1268
1520
  annotations: mutating('Import Image'),