keboola-cli 0.63.4__py3-none-any.whl

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 (306) hide show
  1. keboola_agent_cli/__init__.py +34 -0
  2. keboola_agent_cli/__main__.py +5 -0
  3. keboola_agent_cli/_ui_dist/assets/arc-DhFYIddx.js +2 -0
  4. keboola_agent_cli/_ui_dist/assets/arc-DhFYIddx.js.map +1 -0
  5. keboola_agent_cli/_ui_dist/assets/architecture-7EHR7CIX-hNCijx_H.js +1 -0
  6. keboola_agent_cli/_ui_dist/assets/architectureDiagram-3BPJPVTR-C6hUlprM.js +37 -0
  7. keboola_agent_cli/_ui_dist/assets/architectureDiagram-3BPJPVTR-C6hUlprM.js.map +1 -0
  8. keboola_agent_cli/_ui_dist/assets/array-BifhSqXX.js +2 -0
  9. keboola_agent_cli/_ui_dist/assets/array-BifhSqXX.js.map +1 -0
  10. keboola_agent_cli/_ui_dist/assets/blockDiagram-GPEHLZMM-DC7qY9i4.js +133 -0
  11. keboola_agent_cli/_ui_dist/assets/blockDiagram-GPEHLZMM-DC7qY9i4.js.map +1 -0
  12. keboola_agent_cli/_ui_dist/assets/c4Diagram-AAUBKEIU-5Lh44evt.js +11 -0
  13. keboola_agent_cli/_ui_dist/assets/c4Diagram-AAUBKEIU-5Lh44evt.js.map +1 -0
  14. keboola_agent_cli/_ui_dist/assets/channel-DBMrXlxx.js +2 -0
  15. keboola_agent_cli/_ui_dist/assets/channel-DBMrXlxx.js.map +1 -0
  16. keboola_agent_cli/_ui_dist/assets/chunk-2J33WTMH-Coy82EBh.js +2 -0
  17. keboola_agent_cli/_ui_dist/assets/chunk-2J33WTMH-Coy82EBh.js.map +1 -0
  18. keboola_agent_cli/_ui_dist/assets/chunk-3OPIFGDE-BQC5CRHI.js +63 -0
  19. keboola_agent_cli/_ui_dist/assets/chunk-3OPIFGDE-BQC5CRHI.js.map +1 -0
  20. keboola_agent_cli/_ui_dist/assets/chunk-4BX2VUAB-DUuEt70o.js +2 -0
  21. keboola_agent_cli/_ui_dist/assets/chunk-4BX2VUAB-DUuEt70o.js.map +1 -0
  22. keboola_agent_cli/_ui_dist/assets/chunk-55IACEB6-BvR-6chF.js +2 -0
  23. keboola_agent_cli/_ui_dist/assets/chunk-55IACEB6-BvR-6chF.js.map +1 -0
  24. keboola_agent_cli/_ui_dist/assets/chunk-5ZQYHXKU-BjcTN7ul.js +3 -0
  25. keboola_agent_cli/_ui_dist/assets/chunk-5ZQYHXKU-BjcTN7ul.js.map +1 -0
  26. keboola_agent_cli/_ui_dist/assets/chunk-727SXJPM-C0zxqqRN.js +207 -0
  27. keboola_agent_cli/_ui_dist/assets/chunk-727SXJPM-C0zxqqRN.js.map +1 -0
  28. keboola_agent_cli/_ui_dist/assets/chunk-AQP2D5EJ-CXf7rIlZ.js +232 -0
  29. keboola_agent_cli/_ui_dist/assets/chunk-AQP2D5EJ-CXf7rIlZ.js.map +1 -0
  30. keboola_agent_cli/_ui_dist/assets/chunk-BSJP7CBP-Oj_FO9Q7.js +2 -0
  31. keboola_agent_cli/_ui_dist/assets/chunk-BSJP7CBP-Oj_FO9Q7.js.map +1 -0
  32. keboola_agent_cli/_ui_dist/assets/chunk-CSCIHK7Q-CcTsLrFc.js +124 -0
  33. keboola_agent_cli/_ui_dist/assets/chunk-CSCIHK7Q-CcTsLrFc.js.map +1 -0
  34. keboola_agent_cli/_ui_dist/assets/chunk-FMBD7UC4-FH-zLkkW.js +16 -0
  35. keboola_agent_cli/_ui_dist/assets/chunk-FMBD7UC4-FH-zLkkW.js.map +1 -0
  36. keboola_agent_cli/_ui_dist/assets/chunk-L5ZTLDWV-B1Ky_e7O.js +2 -0
  37. keboola_agent_cli/_ui_dist/assets/chunk-L5ZTLDWV-B1Ky_e7O.js.map +1 -0
  38. keboola_agent_cli/_ui_dist/assets/chunk-ND2GUHAM-BHz1rpbm.js +2 -0
  39. keboola_agent_cli/_ui_dist/assets/chunk-ND2GUHAM-BHz1rpbm.js.map +1 -0
  40. keboola_agent_cli/_ui_dist/assets/chunk-NNHCCRGN-DlpIbxXb.js +160 -0
  41. keboola_agent_cli/_ui_dist/assets/chunk-NNHCCRGN-DlpIbxXb.js.map +1 -0
  42. keboola_agent_cli/_ui_dist/assets/chunk-NZK2D7GU-tnrSoegS.js +2 -0
  43. keboola_agent_cli/_ui_dist/assets/chunk-NZK2D7GU-tnrSoegS.js.map +1 -0
  44. keboola_agent_cli/_ui_dist/assets/chunk-O5CBEL6O-DxxqDH0l.js +71 -0
  45. keboola_agent_cli/_ui_dist/assets/chunk-O5CBEL6O-DxxqDH0l.js.map +1 -0
  46. keboola_agent_cli/_ui_dist/assets/chunk-QZHKN3VN-CSjc2gjj.js +2 -0
  47. keboola_agent_cli/_ui_dist/assets/chunk-QZHKN3VN-CSjc2gjj.js.map +1 -0
  48. keboola_agent_cli/_ui_dist/assets/classDiagram-4FO5ZUOK-BuZcZu85.js +2 -0
  49. keboola_agent_cli/_ui_dist/assets/classDiagram-4FO5ZUOK-BuZcZu85.js.map +1 -0
  50. keboola_agent_cli/_ui_dist/assets/classDiagram-v2-Q7XG4LA2-BuZcZu85.js +2 -0
  51. keboola_agent_cli/_ui_dist/assets/classDiagram-v2-Q7XG4LA2-BuZcZu85.js.map +1 -0
  52. keboola_agent_cli/_ui_dist/assets/cose-bilkent-S5V4N54A-Y0L8LDMa.js +2 -0
  53. keboola_agent_cli/_ui_dist/assets/cose-bilkent-S5V4N54A-Y0L8LDMa.js.map +1 -0
  54. keboola_agent_cli/_ui_dist/assets/cytoscape.esm-C8YCVR3_.js +322 -0
  55. keboola_agent_cli/_ui_dist/assets/cytoscape.esm-C8YCVR3_.js.map +1 -0
  56. keboola_agent_cli/_ui_dist/assets/dagre-BM42HDAG-UZ-9BTqF.js +5 -0
  57. keboola_agent_cli/_ui_dist/assets/dagre-BM42HDAG-UZ-9BTqF.js.map +1 -0
  58. keboola_agent_cli/_ui_dist/assets/dagre-Bx709z4p.js +2 -0
  59. keboola_agent_cli/_ui_dist/assets/dagre-Bx709z4p.js.map +1 -0
  60. keboola_agent_cli/_ui_dist/assets/defaultLocale-C8Fc0cco.js +2 -0
  61. keboola_agent_cli/_ui_dist/assets/defaultLocale-C8Fc0cco.js.map +1 -0
  62. keboola_agent_cli/_ui_dist/assets/diagram-2AECGRRQ-DoDQ60wi.js +44 -0
  63. keboola_agent_cli/_ui_dist/assets/diagram-2AECGRRQ-DoDQ60wi.js.map +1 -0
  64. keboola_agent_cli/_ui_dist/assets/diagram-5GNKFQAL-CMGFxpUs.js +11 -0
  65. keboola_agent_cli/_ui_dist/assets/diagram-5GNKFQAL-CMGFxpUs.js.map +1 -0
  66. keboola_agent_cli/_ui_dist/assets/diagram-KO2AKTUF-1uGDa-Iu.js +4 -0
  67. keboola_agent_cli/_ui_dist/assets/diagram-KO2AKTUF-1uGDa-Iu.js.map +1 -0
  68. keboola_agent_cli/_ui_dist/assets/diagram-LMA3HP47-XtFH7B51.js +25 -0
  69. keboola_agent_cli/_ui_dist/assets/diagram-LMA3HP47-XtFH7B51.js.map +1 -0
  70. keboola_agent_cli/_ui_dist/assets/diagram-OG6HWLK6-B4_Te1T5.js +25 -0
  71. keboola_agent_cli/_ui_dist/assets/diagram-OG6HWLK6-B4_Te1T5.js.map +1 -0
  72. keboola_agent_cli/_ui_dist/assets/dist-Di6zmlv0.js +2 -0
  73. keboola_agent_cli/_ui_dist/assets/dist-Di6zmlv0.js.map +1 -0
  74. keboola_agent_cli/_ui_dist/assets/erDiagram-TEJ5UH35-NjQkrdFt.js +86 -0
  75. keboola_agent_cli/_ui_dist/assets/erDiagram-TEJ5UH35-NjQkrdFt.js.map +1 -0
  76. keboola_agent_cli/_ui_dist/assets/eventmodeling-FCH6USID-BrJMIks8.js +1 -0
  77. keboola_agent_cli/_ui_dist/assets/flowDiagram-I6XJVG4X-CIr8DWl7.js +163 -0
  78. keboola_agent_cli/_ui_dist/assets/flowDiagram-I6XJVG4X-CIr8DWl7.js.map +1 -0
  79. keboola_agent_cli/_ui_dist/assets/ganttDiagram-6RSMTGT7-C1VY_xbQ.js +293 -0
  80. keboola_agent_cli/_ui_dist/assets/ganttDiagram-6RSMTGT7-C1VY_xbQ.js.map +1 -0
  81. keboola_agent_cli/_ui_dist/assets/gitGraph-WXDBUCRP-COacYjo-.js +1 -0
  82. keboola_agent_cli/_ui_dist/assets/gitGraphDiagram-PVQCEYII-DQT8-kg2.js +107 -0
  83. keboola_agent_cli/_ui_dist/assets/gitGraphDiagram-PVQCEYII-DQT8-kg2.js.map +1 -0
  84. keboola_agent_cli/_ui_dist/assets/graphlib-B8gBHxth.js +2 -0
  85. keboola_agent_cli/_ui_dist/assets/graphlib-B8gBHxth.js.map +1 -0
  86. keboola_agent_cli/_ui_dist/assets/index-CMq50kkV.css +1 -0
  87. keboola_agent_cli/_ui_dist/assets/index-D8W97DAz.js +118 -0
  88. keboola_agent_cli/_ui_dist/assets/index-D8W97DAz.js.map +1 -0
  89. keboola_agent_cli/_ui_dist/assets/info-J43DQDTF-DdCTRIzU.js +1 -0
  90. keboola_agent_cli/_ui_dist/assets/infoDiagram-5YYISTIA-C77rsoTp.js +3 -0
  91. keboola_agent_cli/_ui_dist/assets/infoDiagram-5YYISTIA-C77rsoTp.js.map +1 -0
  92. keboola_agent_cli/_ui_dist/assets/init-D6jRqBbL.js +2 -0
  93. keboola_agent_cli/_ui_dist/assets/init-D6jRqBbL.js.map +1 -0
  94. keboola_agent_cli/_ui_dist/assets/ishikawaDiagram-YF4QCWOH-BcTbXaLy.js +71 -0
  95. keboola_agent_cli/_ui_dist/assets/ishikawaDiagram-YF4QCWOH-BcTbXaLy.js.map +1 -0
  96. keboola_agent_cli/_ui_dist/assets/journeyDiagram-JHISSGLW-BejeAJQ_.js +140 -0
  97. keboola_agent_cli/_ui_dist/assets/journeyDiagram-JHISSGLW-BejeAJQ_.js.map +1 -0
  98. keboola_agent_cli/_ui_dist/assets/kanban-definition-UN3LZRKU-BRNz_UrH.js +90 -0
  99. keboola_agent_cli/_ui_dist/assets/kanban-definition-UN3LZRKU-BRNz_UrH.js.map +1 -0
  100. keboola_agent_cli/_ui_dist/assets/katex-C4eR7coU.js +258 -0
  101. keboola_agent_cli/_ui_dist/assets/katex-C4eR7coU.js.map +1 -0
  102. keboola_agent_cli/_ui_dist/assets/line-CzAQKFbJ.js +2 -0
  103. keboola_agent_cli/_ui_dist/assets/line-CzAQKFbJ.js.map +1 -0
  104. keboola_agent_cli/_ui_dist/assets/linear-DUNFFdck.js +2 -0
  105. keboola_agent_cli/_ui_dist/assets/linear-DUNFFdck.js.map +1 -0
  106. keboola_agent_cli/_ui_dist/assets/mermaid-parser.core-CpuBOkFa.js +5 -0
  107. keboola_agent_cli/_ui_dist/assets/mermaid-parser.core-CpuBOkFa.js.map +1 -0
  108. keboola_agent_cli/_ui_dist/assets/mindmap-definition-RKZ34NQL-9EJQNjH0.js +97 -0
  109. keboola_agent_cli/_ui_dist/assets/mindmap-definition-RKZ34NQL-9EJQNjH0.js.map +1 -0
  110. keboola_agent_cli/_ui_dist/assets/ordinal-hYBb2elL.js +2 -0
  111. keboola_agent_cli/_ui_dist/assets/ordinal-hYBb2elL.js.map +1 -0
  112. keboola_agent_cli/_ui_dist/assets/packet-YPE3B663-DLiiw_B2.js +1 -0
  113. keboola_agent_cli/_ui_dist/assets/path-BWPyau1x.js +2 -0
  114. keboola_agent_cli/_ui_dist/assets/path-BWPyau1x.js.map +1 -0
  115. keboola_agent_cli/_ui_dist/assets/pie-LRSECV5Y-CRoO8G1g.js +1 -0
  116. keboola_agent_cli/_ui_dist/assets/pieDiagram-4H26LBE5-XH4cy6Cb.js +31 -0
  117. keboola_agent_cli/_ui_dist/assets/pieDiagram-4H26LBE5-XH4cy6Cb.js.map +1 -0
  118. keboola_agent_cli/_ui_dist/assets/quadrantDiagram-W4KKPZXB-fdhc93U8.js +8 -0
  119. keboola_agent_cli/_ui_dist/assets/quadrantDiagram-W4KKPZXB-fdhc93U8.js.map +1 -0
  120. keboola_agent_cli/_ui_dist/assets/radar-GUYGQ44K-DAlLVJHm.js +1 -0
  121. keboola_agent_cli/_ui_dist/assets/requirementDiagram-4Y6WPE33-a94eP3R9.js +85 -0
  122. keboola_agent_cli/_ui_dist/assets/requirementDiagram-4Y6WPE33-a94eP3R9.js.map +1 -0
  123. keboola_agent_cli/_ui_dist/assets/rough.esm-CSKSodPl.js +2 -0
  124. keboola_agent_cli/_ui_dist/assets/rough.esm-CSKSodPl.js.map +1 -0
  125. keboola_agent_cli/_ui_dist/assets/sankeyDiagram-5OEKKPKP-jcBa02sp.js +41 -0
  126. keboola_agent_cli/_ui_dist/assets/sankeyDiagram-5OEKKPKP-jcBa02sp.js.map +1 -0
  127. keboola_agent_cli/_ui_dist/assets/sequenceDiagram-3UESZ5HK-A5-GGM-e.js +163 -0
  128. keboola_agent_cli/_ui_dist/assets/sequenceDiagram-3UESZ5HK-A5-GGM-e.js.map +1 -0
  129. keboola_agent_cli/_ui_dist/assets/src-ZI-V_AF0.js +2 -0
  130. keboola_agent_cli/_ui_dist/assets/src-ZI-V_AF0.js.map +1 -0
  131. keboola_agent_cli/_ui_dist/assets/stateDiagram-AJRCARHV-BKAA5rqE.js +2 -0
  132. keboola_agent_cli/_ui_dist/assets/stateDiagram-AJRCARHV-BKAA5rqE.js.map +1 -0
  133. keboola_agent_cli/_ui_dist/assets/stateDiagram-v2-BHNVJYJU-DnJwJBsE.js +2 -0
  134. keboola_agent_cli/_ui_dist/assets/stateDiagram-v2-BHNVJYJU-DnJwJBsE.js.map +1 -0
  135. keboola_agent_cli/_ui_dist/assets/timeline-definition-PNZ67QCA-Cy39jp8b.js +121 -0
  136. keboola_agent_cli/_ui_dist/assets/timeline-definition-PNZ67QCA-Cy39jp8b.js.map +1 -0
  137. keboola_agent_cli/_ui_dist/assets/treeView-BLDUP644-DbLYl23-.js +1 -0
  138. keboola_agent_cli/_ui_dist/assets/treemap-LRROVOQU-Bp0eGlOt.js +1 -0
  139. keboola_agent_cli/_ui_dist/assets/vennDiagram-CIIHVFJN-BGECKubd.js +35 -0
  140. keboola_agent_cli/_ui_dist/assets/vennDiagram-CIIHVFJN-BGECKubd.js.map +1 -0
  141. keboola_agent_cli/_ui_dist/assets/wardley-L42UT6IY-D4yH4jqS.js +1 -0
  142. keboola_agent_cli/_ui_dist/assets/wardleyDiagram-YWT4CUSO-D6XRG3cZ.js +79 -0
  143. keboola_agent_cli/_ui_dist/assets/wardleyDiagram-YWT4CUSO-D6XRG3cZ.js.map +1 -0
  144. keboola_agent_cli/_ui_dist/assets/xychartDiagram-2RQKCTM6-DRre-pfZ.js +8 -0
  145. keboola_agent_cli/_ui_dist/assets/xychartDiagram-2RQKCTM6-DRre-pfZ.js.map +1 -0
  146. keboola_agent_cli/_ui_dist/index.html +50 -0
  147. keboola_agent_cli/ai_client.py +83 -0
  148. keboola_agent_cli/auto_update.py +550 -0
  149. keboola_agent_cli/changelog.py +1198 -0
  150. keboola_agent_cli/cli.py +448 -0
  151. keboola_agent_cli/client.py +3422 -0
  152. keboola_agent_cli/commands/__init__.py +0 -0
  153. keboola_agent_cli/commands/_data_app_git.py +343 -0
  154. keboola_agent_cli/commands/_helpers.py +377 -0
  155. keboola_agent_cli/commands/_metadata_input.py +49 -0
  156. keboola_agent_cli/commands/_semantic_layer_crud.py +632 -0
  157. keboola_agent_cli/commands/_semantic_layer_helpers.py +44 -0
  158. keboola_agent_cli/commands/_semantic_layer_reference_data.py +247 -0
  159. keboola_agent_cli/commands/agent.py +968 -0
  160. keboola_agent_cli/commands/branch.py +423 -0
  161. keboola_agent_cli/commands/changelog.py +168 -0
  162. keboola_agent_cli/commands/component.py +216 -0
  163. keboola_agent_cli/commands/config.py +2442 -0
  164. keboola_agent_cli/commands/context.py +1481 -0
  165. keboola_agent_cli/commands/data_app.py +1279 -0
  166. keboola_agent_cli/commands/dev_portal.py +584 -0
  167. keboola_agent_cli/commands/doctor.py +37 -0
  168. keboola_agent_cli/commands/encrypt.py +145 -0
  169. keboola_agent_cli/commands/feature.py +311 -0
  170. keboola_agent_cli/commands/flow.py +948 -0
  171. keboola_agent_cli/commands/http_client.py +157 -0
  172. keboola_agent_cli/commands/init.py +279 -0
  173. keboola_agent_cli/commands/job.py +661 -0
  174. keboola_agent_cli/commands/kai.py +301 -0
  175. keboola_agent_cli/commands/lineage.py +1464 -0
  176. keboola_agent_cli/commands/org.py +292 -0
  177. keboola_agent_cli/commands/permissions.py +360 -0
  178. keboola_agent_cli/commands/project.py +1192 -0
  179. keboola_agent_cli/commands/repl.py +243 -0
  180. keboola_agent_cli/commands/schedule.py +340 -0
  181. keboola_agent_cli/commands/search.py +178 -0
  182. keboola_agent_cli/commands/semantic_layer.py +939 -0
  183. keboola_agent_cli/commands/serve.py +272 -0
  184. keboola_agent_cli/commands/sharing.py +340 -0
  185. keboola_agent_cli/commands/storage.py +2630 -0
  186. keboola_agent_cli/commands/stream.py +266 -0
  187. keboola_agent_cli/commands/sync.py +1277 -0
  188. keboola_agent_cli/commands/tool.py +206 -0
  189. keboola_agent_cli/commands/version.py +186 -0
  190. keboola_agent_cli/commands/workspace.py +635 -0
  191. keboola_agent_cli/config_store.py +582 -0
  192. keboola_agent_cli/constants.py +528 -0
  193. keboola_agent_cli/data_science_client.py +342 -0
  194. keboola_agent_cli/dev_portal_client.py +323 -0
  195. keboola_agent_cli/errors.py +248 -0
  196. keboola_agent_cli/http_base.py +315 -0
  197. keboola_agent_cli/json_utils.py +126 -0
  198. keboola_agent_cli/lib.py +536 -0
  199. keboola_agent_cli/manage_client.py +324 -0
  200. keboola_agent_cli/metastore_client.py +214 -0
  201. keboola_agent_cli/models.py +427 -0
  202. keboola_agent_cli/output.py +1084 -0
  203. keboola_agent_cli/permissions.py +469 -0
  204. keboola_agent_cli/py.typed +3 -0
  205. keboola_agent_cli/result_models.py +271 -0
  206. keboola_agent_cli/server/__init__.py +34 -0
  207. keboola_agent_cli/server/agent_runner.py +1289 -0
  208. keboola_agent_cli/server/agents_store.py +325 -0
  209. keboola_agent_cli/server/app.py +764 -0
  210. keboola_agent_cli/server/auth.py +117 -0
  211. keboola_agent_cli/server/dependencies.py +149 -0
  212. keboola_agent_cli/server/pricing.py +303 -0
  213. keboola_agent_cli/server/routers/__init__.py +1 -0
  214. keboola_agent_cli/server/routers/agents.py +616 -0
  215. keboola_agent_cli/server/routers/ai_chat.py +129 -0
  216. keboola_agent_cli/server/routers/branches.py +133 -0
  217. keboola_agent_cli/server/routers/components.py +48 -0
  218. keboola_agent_cli/server/routers/configs.py +507 -0
  219. keboola_agent_cli/server/routers/data_apps.py +384 -0
  220. keboola_agent_cli/server/routers/dev_portal.py +67 -0
  221. keboola_agent_cli/server/routers/encrypt.py +35 -0
  222. keboola_agent_cli/server/routers/feature.py +179 -0
  223. keboola_agent_cli/server/routers/flows.py +204 -0
  224. keboola_agent_cli/server/routers/health.py +53 -0
  225. keboola_agent_cli/server/routers/jobs.py +175 -0
  226. keboola_agent_cli/server/routers/kai.py +80 -0
  227. keboola_agent_cli/server/routers/lineage.py +226 -0
  228. keboola_agent_cli/server/routers/mcp.py +70 -0
  229. keboola_agent_cli/server/routers/members.py +170 -0
  230. keboola_agent_cli/server/routers/org.py +96 -0
  231. keboola_agent_cli/server/routers/projects.py +106 -0
  232. keboola_agent_cli/server/routers/schedules.py +54 -0
  233. keboola_agent_cli/server/routers/search.py +30 -0
  234. keboola_agent_cli/server/routers/semantic_layer.py +650 -0
  235. keboola_agent_cli/server/routers/sharing.py +86 -0
  236. keboola_agent_cli/server/routers/storage.py +574 -0
  237. keboola_agent_cli/server/routers/stream.py +100 -0
  238. keboola_agent_cli/server/routers/workspaces.py +302 -0
  239. keboola_agent_cli/server/run_broadcaster.py +329 -0
  240. keboola_agent_cli/server/sse.py +25 -0
  241. keboola_agent_cli/services/__init__.py +0 -0
  242. keboola_agent_cli/services/_encryption.py +217 -0
  243. keboola_agent_cli/services/_semantic_layer_cascade.py +147 -0
  244. keboola_agent_cli/services/_semantic_layer_crud.py +382 -0
  245. keboola_agent_cli/services/_semantic_layer_internals.py +1078 -0
  246. keboola_agent_cli/services/_semantic_layer_lookup.py +181 -0
  247. keboola_agent_cli/services/_semantic_layer_reference_data.py +217 -0
  248. keboola_agent_cli/services/_sync_bindings.py +456 -0
  249. keboola_agent_cli/services/_sync_branch.py +191 -0
  250. keboola_agent_cli/services/_sync_bulk.py +228 -0
  251. keboola_agent_cli/services/_sync_clone.py +163 -0
  252. keboola_agent_cli/services/_sync_models.py +97 -0
  253. keboola_agent_cli/services/_sync_push_ops.py +369 -0
  254. keboola_agent_cli/services/_sync_storage.py +376 -0
  255. keboola_agent_cli/services/_sync_writeback.py +167 -0
  256. keboola_agent_cli/services/agent_service.py +458 -0
  257. keboola_agent_cli/services/base.py +175 -0
  258. keboola_agent_cli/services/branch_service.py +588 -0
  259. keboola_agent_cli/services/component_service.py +694 -0
  260. keboola_agent_cli/services/config_service.py +2099 -0
  261. keboola_agent_cli/services/data_app_git_service.py +224 -0
  262. keboola_agent_cli/services/data_app_service.py +2082 -0
  263. keboola_agent_cli/services/deep_lineage_service.py +1322 -0
  264. keboola_agent_cli/services/dev_portal_service.py +345 -0
  265. keboola_agent_cli/services/doctor_service.py +445 -0
  266. keboola_agent_cli/services/encrypt_service.py +87 -0
  267. keboola_agent_cli/services/feature_service.py +268 -0
  268. keboola_agent_cli/services/flow_service.py +769 -0
  269. keboola_agent_cli/services/flow_validation.py +188 -0
  270. keboola_agent_cli/services/http_forwarder_service.py +236 -0
  271. keboola_agent_cli/services/job_idempotency_store.py +285 -0
  272. keboola_agent_cli/services/job_service.py +797 -0
  273. keboola_agent_cli/services/kai_service.py +367 -0
  274. keboola_agent_cli/services/lineage_service.py +274 -0
  275. keboola_agent_cli/services/mcp_service.py +1498 -0
  276. keboola_agent_cli/services/mcp_transport.py +259 -0
  277. keboola_agent_cli/services/member_service.py +593 -0
  278. keboola_agent_cli/services/org_service.py +619 -0
  279. keboola_agent_cli/services/project_service.py +947 -0
  280. keboola_agent_cli/services/repo_validate_service.py +767 -0
  281. keboola_agent_cli/services/schedule_service.py +731 -0
  282. keboola_agent_cli/services/search_service.py +331 -0
  283. keboola_agent_cli/services/semantic_layer_service.py +1497 -0
  284. keboola_agent_cli/services/sharing_service.py +307 -0
  285. keboola_agent_cli/services/storage_service.py +2524 -0
  286. keboola_agent_cli/services/stream_service.py +395 -0
  287. keboola_agent_cli/services/sync_service.py +2244 -0
  288. keboola_agent_cli/services/variables_service.py +447 -0
  289. keboola_agent_cli/services/version_service.py +1038 -0
  290. keboola_agent_cli/services/workspace_service.py +1103 -0
  291. keboola_agent_cli/stream_client.py +217 -0
  292. keboola_agent_cli/sync/__init__.py +1 -0
  293. keboola_agent_cli/sync/branch_mapping.py +174 -0
  294. keboola_agent_cli/sync/clone.py +211 -0
  295. keboola_agent_cli/sync/code_extraction.py +655 -0
  296. keboola_agent_cli/sync/config_format.py +290 -0
  297. keboola_agent_cli/sync/diff_engine.py +566 -0
  298. keboola_agent_cli/sync/git_utils.py +93 -0
  299. keboola_agent_cli/sync/manifest.py +162 -0
  300. keboola_agent_cli/sync/naming.py +90 -0
  301. keboola_agent_cli/sync/secrets.py +62 -0
  302. keboola_agent_cli/sync/sql_split.py +134 -0
  303. keboola_cli-0.63.4.dist-info/METADATA +308 -0
  304. keboola_cli-0.63.4.dist-info/RECORD +306 -0
  305. keboola_cli-0.63.4.dist-info/WHEEL +4 -0
  306. keboola_cli-0.63.4.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,376 @@
1
+ """Pull-side storage-metadata, jobs, and sample writers (from sync_service.py).
2
+
3
+ Free functions that materialise a project's storage metadata (buckets/tables),
4
+ per-config job history, and table data samples to the filesystem during
5
+ ``sync pull``. Only :func:`fetch_jobs_per_config` needs the ``SyncService`` (for
6
+ its ``_resolve_max_workers`` helper); the rest are pure. ``_ensure_path_within``
7
+ (the storage-write path-traversal guard) moved here with its only callers.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import csv
13
+ import io
14
+ import json
15
+ import logging
16
+ import threading
17
+ from concurrent.futures import ThreadPoolExecutor, as_completed
18
+ from pathlib import Path
19
+ from typing import TYPE_CHECKING, Any
20
+
21
+ from ..constants import (
22
+ ENCRYPTED_COLUMN_MASK,
23
+ ENCRYPTED_COLUMN_PREFIX,
24
+ JOBS_FILENAME,
25
+ STORAGE_BUCKETS_FILENAME,
26
+ STORAGE_DIR_NAME,
27
+ STORAGE_SAMPLES_DIR_NAME,
28
+ )
29
+ from ..errors import ConfigError
30
+ from ..sync.manifest import ManifestConfiguration
31
+ from ..sync.naming import sanitize_path_segment
32
+
33
+ if TYPE_CHECKING:
34
+ from .sync_service import SyncService
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+ # Storage API sync-export column ceiling (avoids the API 400 on wide tables).
39
+ _MAX_SYNC_COLUMNS = 30
40
+
41
+
42
+ def _ensure_path_within(base_dir: Path, target: Path, what: str) -> None:
43
+ """Reject a write whose path escapes *base_dir* (defense-in-depth).
44
+
45
+ Mirrors ``_ensure_within_branch`` for non-config writes (storage metadata +
46
+ samples) whose path segments derive from API-controlled bucket ids / table
47
+ names (GHSA-833q-c5wv-26r7). Raises ConfigError on escape so a malformed or
48
+ compromised Storage response cannot write outside the sync workspace.
49
+ """
50
+ try:
51
+ base_resolved = base_dir.resolve()
52
+ target_resolved = target.resolve()
53
+ except OSError as exc:
54
+ raise ConfigError(f"Cannot resolve sync path: {exc}") from exc
55
+ if not target_resolved.is_relative_to(base_resolved):
56
+ raise ConfigError(
57
+ f"Storage path escapes sync workspace ({what}). Refusing to write "
58
+ f"outside '{base_resolved}'. This indicates a malformed or compromised "
59
+ f"API response or a path-sanitization regression."
60
+ )
61
+
62
+
63
+ def write_storage_metadata(
64
+ project_root: Path,
65
+ buckets: list[dict[str, Any]],
66
+ tables: list[dict[str, Any]],
67
+ samples: dict[str, str],
68
+ ) -> dict[str, int]:
69
+ """Write storage bucket and table metadata to the filesystem.
70
+
71
+ Creates:
72
+ storage/buckets.json - list of all buckets
73
+ storage/tables/{bucket_id}/{table_name}.json - per-table metadata
74
+ storage/samples/{bucket}/{table}/sample.csv - data samples (if any)
75
+
76
+ Returns:
77
+ Dict with counts: buckets, tables, samples written.
78
+ """
79
+ storage_dir = project_root / STORAGE_DIR_NAME
80
+ storage_dir.mkdir(parents=True, exist_ok=True)
81
+
82
+ # Write buckets index. API may return null for tablesCount / dataSizeBytes
83
+ # on empty buckets; coerce to 0 (dict.get default only fires when key is
84
+ # missing, not when the value is explicitly null).
85
+ bucket_summaries = [
86
+ {
87
+ "id": b.get("id", ""),
88
+ "name": b.get("name", ""),
89
+ "stage": b.get("stage", ""),
90
+ "description": b.get("description", ""),
91
+ "tables_count": b.get("tablesCount") or 0,
92
+ "data_size_bytes": b.get("dataSizeBytes") or 0,
93
+ "metadata": b.get("metadata", []),
94
+ }
95
+ for b in buckets
96
+ ]
97
+ buckets_file = storage_dir / STORAGE_BUCKETS_FILENAME
98
+ buckets_file.write_text(
99
+ json.dumps(bucket_summaries, indent=2, ensure_ascii=False),
100
+ encoding="utf-8",
101
+ )
102
+
103
+ # Group tables by bucket
104
+ tables_by_bucket: dict[str, list[dict[str, Any]]] = {}
105
+ for t in tables:
106
+ bucket_id = (
107
+ t.get("bucket", {}).get("id", "")
108
+ if isinstance(t.get("bucket"), dict)
109
+ else t.get("bucketId", "")
110
+ )
111
+ if not bucket_id:
112
+ continue
113
+ tables_by_bucket.setdefault(bucket_id, []).append(t)
114
+
115
+ tables_written = 0
116
+ tables_dir = storage_dir / "tables"
117
+ for bucket_id, bucket_tables in tables_by_bucket.items():
118
+ # Sanitize bucket_id for filesystem. sanitize_path_segment first kills
119
+ # traversal (`/`, `..`, absolute paths); the trailing replace keeps the
120
+ # legacy `in.c-foo` -> `in-c-foo` directory naming for legitimate ids
121
+ # (GHSA-833q-c5wv-26r7).
122
+ safe_bucket = sanitize_path_segment(bucket_id).replace(".", "-")
123
+ bucket_dir = tables_dir / safe_bucket
124
+ _ensure_path_within(storage_dir, bucket_dir, f"bucket_id={bucket_id!r}")
125
+ bucket_dir.mkdir(parents=True, exist_ok=True)
126
+
127
+ for t in bucket_tables:
128
+ table_name = t.get("name", "unknown")
129
+ table_meta = {
130
+ "id": t.get("id", ""),
131
+ "name": table_name,
132
+ "primary_key": t.get("primaryKey", []),
133
+ "columns": t.get("columns", []),
134
+ # API may return null for rowsCount / dataSizeBytes on
135
+ # newly-created or empty tables; coerce to 0 explicitly
136
+ # (dict.get default only fires when the key is missing).
137
+ "rows_count": t.get("rowsCount") or 0,
138
+ "data_size_bytes": t.get("dataSizeBytes") or 0,
139
+ "last_import_date": t.get("lastImportDate", ""),
140
+ "last_change_date": t.get("lastChangeDate", ""),
141
+ "description": t.get("description", ""),
142
+ "metadata": t.get("metadata", []),
143
+ "column_metadata": t.get("columnMetadata", {}),
144
+ }
145
+ # The table name comes from the API; sanitize it for the filename
146
+ # and assert containment so a crafted name cannot escape the bucket
147
+ # dir (GHSA-833q-c5wv-26r7). The original name stays verbatim in the
148
+ # metadata body above.
149
+ safe_table = sanitize_path_segment(table_name)
150
+ table_file = bucket_dir / f"{safe_table}.json"
151
+ _ensure_path_within(storage_dir, table_file, f"table={table_name!r}")
152
+ table_file.write_text(
153
+ json.dumps(table_meta, indent=2, ensure_ascii=False),
154
+ encoding="utf-8",
155
+ )
156
+ tables_written += 1
157
+
158
+ # Write samples
159
+ samples_written = 0
160
+ if samples:
161
+ samples_dir = storage_dir / STORAGE_SAMPLES_DIR_NAME
162
+ for table_id, csv_data in samples.items():
163
+ # table_id format: "in.c-bucket.table" -> samples/in-c-bucket/table/
164
+ # Every segment derives from the API table_id; sanitize each and
165
+ # assert containment so a crafted id cannot escape (GHSA-833q).
166
+ parts = table_id.split(".", 2)
167
+ if len(parts) >= 3:
168
+ safe_bucket = sanitize_path_segment(f"{parts[0]}-{parts[1]}")
169
+ safe_table = sanitize_path_segment(parts[2])
170
+ else:
171
+ safe_bucket = sanitize_path_segment(table_id.replace(".", "-"))
172
+ safe_table = "data"
173
+ sample_dir = samples_dir / safe_bucket / safe_table
174
+ _ensure_path_within(storage_dir, sample_dir, f"table_id={table_id!r}")
175
+ sample_dir.mkdir(parents=True, exist_ok=True)
176
+
177
+ masked_csv = mask_encrypted_columns(csv_data)
178
+ (sample_dir / "sample.csv").write_text(masked_csv, encoding="utf-8")
179
+ samples_written += 1
180
+
181
+ return {
182
+ "buckets": len(buckets),
183
+ "tables": tables_written,
184
+ "samples": samples_written,
185
+ }
186
+
187
+
188
+ def fetch_jobs_per_config(
189
+ service: SyncService,
190
+ client: Any,
191
+ components: list[dict[str, Any]],
192
+ job_limit: int,
193
+ ) -> list[dict[str, Any]]:
194
+ """Fetch jobs per config via /search/jobs in parallel.
195
+
196
+ Used as fallback when the grouped-jobs API cannot return all configs in a
197
+ single call (jobsPerGroup * limit <= 500 constraint). Returns data in the
198
+ same format as ``list_jobs_grouped()`` so :func:`write_per_config_jobs`
199
+ works unchanged.
200
+ """
201
+ config_pairs: list[tuple[str, str]] = []
202
+ for comp in components:
203
+ comp_id = comp.get("id", "")
204
+ for cfg in comp.get("configurations", []):
205
+ cfg_id = str(cfg.get("id", ""))
206
+ if comp_id and cfg_id:
207
+ config_pairs.append((comp_id, cfg_id))
208
+
209
+ if not config_pairs:
210
+ return []
211
+
212
+ results: list[dict[str, Any]] = []
213
+ lock = threading.Lock()
214
+ max_workers = min(len(config_pairs), service._resolve_max_workers())
215
+
216
+ def _fetch_one(pair: tuple[str, str]) -> None:
217
+ comp_id, cfg_id = pair
218
+ try:
219
+ jobs = client.list_jobs(
220
+ component_id=comp_id,
221
+ config_id=cfg_id,
222
+ limit=job_limit,
223
+ )
224
+ if jobs:
225
+ with lock:
226
+ results.append(
227
+ {
228
+ "group": {"componentId": comp_id, "configId": cfg_id},
229
+ "jobs": jobs,
230
+ }
231
+ )
232
+ except Exception:
233
+ logger.debug("Failed to fetch jobs for %s/%s", comp_id, cfg_id, exc_info=True)
234
+
235
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
236
+ futures = [executor.submit(_fetch_one, pair) for pair in config_pairs]
237
+ for future in as_completed(futures):
238
+ future.result() # propagate unexpected errors
239
+
240
+ return results
241
+
242
+
243
+ def write_per_config_jobs(
244
+ branch_dir: Path,
245
+ configurations: list[ManifestConfiguration],
246
+ jobs_grouped: list[dict[str, Any]],
247
+ ) -> int:
248
+ """Write ``_jobs.jsonl`` files next to each configuration.
249
+
250
+ Matches grouped jobs to configs by componentId+configId, then writes a JSONL
251
+ file with light job records. Returns the number of files written.
252
+ """
253
+ jobs_by_config: dict[tuple[str, str], list[dict[str, Any]]] = {}
254
+ for group in jobs_grouped:
255
+ group_key = group.get("group", {})
256
+ component_id = group_key.get("componentId", "")
257
+ config_id = group_key.get("configId", "")
258
+ if component_id and config_id:
259
+ jobs_by_config[(component_id, config_id)] = group.get("jobs", [])
260
+
261
+ files_written = 0
262
+ for cfg in configurations:
263
+ key = (cfg.component_id, cfg.id)
264
+ jobs = jobs_by_config.get(key)
265
+ if not jobs:
266
+ continue
267
+
268
+ config_dir = branch_dir / cfg.path
269
+ config_dir.mkdir(parents=True, exist_ok=True)
270
+ jobs_file = config_dir / JOBS_FILENAME
271
+
272
+ lines: list[str] = []
273
+ for job in jobs:
274
+ light_job: dict[str, Any] = {
275
+ "id": str(job.get("id", "")),
276
+ "status": job.get("status", ""),
277
+ "start_time": job.get("startTime", ""),
278
+ "end_time": job.get("endTime", ""),
279
+ "duration_seconds": job.get("durationSeconds", 0),
280
+ }
281
+ if job.get("mode") and job["mode"] != "run":
282
+ light_job["mode"] = job["mode"]
283
+ # Include error message for failed/warning jobs
284
+ status = job.get("status", "")
285
+ if status in ("error", "warning", "terminated", "cancelled"):
286
+ result = job.get("result", {})
287
+ if isinstance(result, dict) and result.get("message"):
288
+ light_job["error_message"] = result["message"]
289
+ lines.append(json.dumps(light_job, ensure_ascii=False))
290
+
291
+ jobs_file.write_text("\n".join(lines) + "\n", encoding="utf-8")
292
+ files_written += 1
293
+
294
+ return files_written
295
+
296
+
297
+ def fetch_samples(
298
+ client: Any,
299
+ tables: list[dict[str, Any]],
300
+ sample_limit: int,
301
+ max_samples: int,
302
+ ) -> dict[str, str]:
303
+ """Fetch CSV data previews for tables, respecting limits.
304
+
305
+ Selects tables sorted by rowsCount descending (largest first), limited to
306
+ ``max_samples`` tables. Returns a dict mapping table_id -> CSV string.
307
+ """
308
+
309
+ # Storage API may return rowsCount=None for empty/newly-created tables on
310
+ # some backends; dict.get() does not coerce None to the default value.
311
+ def _rows(t: dict[str, Any]) -> int:
312
+ return t.get("rowsCount") or 0
313
+
314
+ sorted_tables = sorted(
315
+ [t for t in tables if _rows(t) > 0],
316
+ key=_rows,
317
+ reverse=True,
318
+ )[:max_samples]
319
+
320
+ samples: dict[str, str] = {}
321
+ for t in sorted_tables:
322
+ table_id = t.get("id", "")
323
+ if not table_id:
324
+ continue
325
+ try:
326
+ # Limit columns to avoid the API 400 error on wide tables.
327
+ all_columns = t.get("columns", [])
328
+ columns = (
329
+ all_columns[:_MAX_SYNC_COLUMNS] if len(all_columns) > _MAX_SYNC_COLUMNS else None
330
+ )
331
+ csv_data = client.get_table_data_preview(table_id, limit=sample_limit, columns=columns)
332
+ samples[table_id] = csv_data
333
+ except Exception:
334
+ logger.warning("Failed to fetch sample for %s", table_id, exc_info=True)
335
+
336
+ return samples
337
+
338
+
339
+ def mask_encrypted_columns(csv_data: str) -> str:
340
+ """Mask encrypted column values in CSV data.
341
+
342
+ Encrypted columns in Keboola start with '#' in the column name; their values
343
+ are replaced with the masked placeholder.
344
+ """
345
+ if not csv_data:
346
+ return csv_data
347
+
348
+ lines = csv_data.split("\n")
349
+ if not lines:
350
+ return csv_data
351
+
352
+ reader = csv.reader(io.StringIO(lines[0]))
353
+ try:
354
+ header = next(reader)
355
+ except StopIteration:
356
+ return csv_data
357
+
358
+ encrypted_indices = [
359
+ i for i, col in enumerate(header) if col.startswith(ENCRYPTED_COLUMN_PREFIX)
360
+ ]
361
+ if not encrypted_indices:
362
+ return csv_data
363
+
364
+ output = io.StringIO()
365
+ writer = csv.writer(output)
366
+ full_reader = csv.reader(io.StringIO(csv_data))
367
+ for row_idx, row in enumerate(full_reader):
368
+ if row_idx == 0:
369
+ writer.writerow(row) # header unchanged
370
+ else:
371
+ for idx in encrypted_indices:
372
+ if idx < len(row):
373
+ row[idx] = ENCRYPTED_COLUMN_MASK
374
+ writer.writerow(row)
375
+
376
+ return output.getvalue()
@@ -0,0 +1,167 @@
1
+ """Manifest + local-file writeback after a push (extracted from sync_service.py).
2
+
3
+ Free functions that record a freshly-created config/row in the manifest, write
4
+ the API-assigned ULID + encrypted secrets back into the local ``_config.yml``,
5
+ and propagate ``KBC.*`` metadata. Only :func:`writeback_after_push` needs the
6
+ ``SyncService`` (for its ``_write_config_file`` helper); the rest are pure.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from pathlib import Path
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ from ..errors import KeboolaApiError
16
+ from ..sync.manifest import ManifestConfigRow, ManifestConfiguration
17
+ from ._encryption import apply_encrypted_to_local
18
+ from ._sync_models import WritebackResult
19
+
20
+ if TYPE_CHECKING:
21
+ from ..sync.manifest import Manifest
22
+ from .sync_service import SyncService
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ def writeback_create_config_in_manifest(
28
+ *,
29
+ manifest: Manifest,
30
+ component_id: str,
31
+ branch_id: int | None,
32
+ config_path_str: str,
33
+ new_id: str,
34
+ file_hash: str,
35
+ cfg_hash: str,
36
+ ) -> WritebackResult:
37
+ """Record a freshly-created config in the manifest.
38
+
39
+ If a placeholder entry already exists at ``(branch_id, component_id, path)``
40
+ -- the FIIA / scaffold emit pattern -- update it in place, preserving any
41
+ user-declared metadata (e.g. ``KBC.configuration.folderName``) and
42
+ refreshing only the bookkeeping hashes. Otherwise append a new entry.
43
+
44
+ Matching includes ``branch_id`` because a single manifest can hold entries
45
+ from multiple branches in git-branching mode; matching on
46
+ ``(component_id, path)`` alone would risk updating the wrong branch's entry
47
+ when the same logical path exists under two branches.
48
+
49
+ Returns a :class:`WritebackResult` carrying the entry and its pre-overwrite
50
+ ``previous_id`` so the create pass can remap any child row parents /
51
+ transformation variable links from the placeholder id to the freshly-assigned
52
+ ULID.
53
+ """
54
+ target_branch = branch_id or 0
55
+ for entry in manifest.configurations:
56
+ if (
57
+ entry.branch_id == target_branch
58
+ and entry.component_id == component_id
59
+ and entry.path == config_path_str
60
+ ):
61
+ previous_id = entry.id
62
+ entry.id = new_id
63
+ entry.metadata["pull_hash"] = file_hash
64
+ entry.metadata["pull_config_hash"] = cfg_hash
65
+ return WritebackResult(entry=entry, previous_id=previous_id)
66
+ new_entry = ManifestConfiguration(
67
+ branchId=target_branch,
68
+ componentId=component_id,
69
+ id=new_id,
70
+ path=config_path_str,
71
+ metadata={"pull_hash": file_hash, "pull_config_hash": cfg_hash},
72
+ )
73
+ manifest.configurations.append(new_entry)
74
+ return WritebackResult(entry=new_entry, previous_id="")
75
+
76
+
77
+ def writeback_create_row_in_manifest(
78
+ *,
79
+ parent: ManifestConfiguration,
80
+ row_path_str: str,
81
+ new_row_id: str,
82
+ file_hash: str,
83
+ cfg_hash: str,
84
+ ) -> ManifestConfigRow:
85
+ """Record a freshly-created row under its parent in the manifest.
86
+
87
+ Mirrors :func:`writeback_create_config_in_manifest` for rows: update any
88
+ placeholder row entry in place, otherwise append.
89
+ """
90
+ for row in parent.rows:
91
+ if row.path == row_path_str:
92
+ row.id = new_row_id
93
+ row.metadata["pull_hash"] = file_hash
94
+ row.metadata["pull_config_hash"] = cfg_hash
95
+ return row
96
+ new_row = ManifestConfigRow(
97
+ id=new_row_id,
98
+ path=row_path_str,
99
+ metadata={"pull_hash": file_hash, "pull_config_hash": cfg_hash},
100
+ )
101
+ parent.rows.append(new_row)
102
+ return new_row
103
+
104
+
105
+ def propagate_kbc_metadata(
106
+ client: Any,
107
+ entry: ManifestConfiguration,
108
+ branch_id: int | None,
109
+ ) -> str | None:
110
+ """POST any ``KBC.*`` keys from the manifest entry to the metadata API.
111
+
112
+ Bookkeeping keys (``pull_hash``, ``pull_config_hash``, ...) live in the same
113
+ metadata dict but are filtered by the ``KBC.`` prefix. Called only on CREATE;
114
+ updates use ``kbagent config set-metadata`` explicitly. The metadata API
115
+ stores configuration-level annotations only -- this is **not** a secret
116
+ store; do not place tokens or passwords under ``KBC.*`` keys.
117
+
118
+ Returns ``None`` on success (or when there are no KBC.* keys to propagate).
119
+ Returns the API error message on a non-fatal write failure: the config is
120
+ already created on the remote and the manifest writeback is complete, so a
121
+ single failed metadata POST is reported back to the push loop as an
122
+ accumulated error rather than aborting the rest of the push.
123
+ """
124
+ entries = [(key, str(value)) for key, value in entry.metadata.items() if key.startswith("KBC.")]
125
+ if not entries:
126
+ return None
127
+ try:
128
+ client.set_config_metadata(
129
+ component_id=entry.component_id,
130
+ config_id=entry.id,
131
+ entries=entries,
132
+ branch_id=branch_id,
133
+ )
134
+ except KeboolaApiError as exc:
135
+ logger.warning(
136
+ "Failed to propagate KBC.* metadata for %s/%s: %s",
137
+ entry.component_id,
138
+ entry.id,
139
+ exc,
140
+ )
141
+ return exc.message
142
+ return None
143
+
144
+
145
+ def writeback_after_push(
146
+ service: SyncService,
147
+ local_data: dict[str, Any],
148
+ config_dir: Path,
149
+ config_id: str,
150
+ pushed_configuration: dict[str, Any],
151
+ ) -> None:
152
+ """Update local ``_config.yml`` after a successful push.
153
+
154
+ Writes back the API-assigned ``_keboola.config_id`` (on first create) and
155
+ the encrypted secret values (so local matches remote state).
156
+ """
157
+ keboola_meta = local_data.setdefault("_keboola", {})
158
+ if config_id:
159
+ keboola_meta["config_id"] = config_id
160
+
161
+ pushed_params = pushed_configuration.get("parameters", {})
162
+ local_params = local_data.get("parameters", {})
163
+ if pushed_params and local_params:
164
+ apply_encrypted_to_local(local_params, pushed_params)
165
+
166
+ service._write_config_file(config_dir, local_data)
167
+ logger.debug("Updated local config at %s after push", config_dir)