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,367 @@
1
+ """Kai (Keboola AI Assistant) service — bridge between CLI and cloud Kai API.
2
+
3
+ Provides sync wrappers around the async kai-client library, with feature
4
+ detection (agent-chat flag) and project resolution via BaseService.
5
+ """
6
+
7
+ import asyncio
8
+ import logging
9
+ from typing import Any
10
+
11
+ from kai_client import KaiClient, KaiError
12
+
13
+ from ..constants import KAI_FEATURE_FLAG, KAI_REQUEST_TIMEOUT, KAI_STREAM_TIMEOUT
14
+ from ..errors import ConfigError, ErrorCode, KeboolaApiError
15
+ from .base import BaseService
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class KaiService(BaseService):
21
+ """Business logic for Kai AI Assistant integration.
22
+
23
+ All public methods are synchronous — they wrap the async KaiClient
24
+ via asyncio.run() so Typer commands can call them directly.
25
+ """
26
+
27
+ # ------------------------------------------------------------------
28
+ # Project resolution
29
+ # ------------------------------------------------------------------
30
+
31
+ def resolve_alias(self, alias: str | None) -> str:
32
+ """Resolve a project alias, falling back to the default project.
33
+
34
+ Args:
35
+ alias: Explicit alias, or None for default.
36
+
37
+ Returns:
38
+ Resolved alias string.
39
+
40
+ Raises:
41
+ ConfigError: If no projects configured or alias not found.
42
+ """
43
+ if alias:
44
+ # Validate it exists
45
+ self.resolve_projects([alias])
46
+ return alias
47
+ # Fall back to default (first project)
48
+ projects = self.resolve_projects()
49
+ if not projects:
50
+ raise ConfigError("No projects configured. Run 'kbagent project add' first.")
51
+ return next(iter(projects))
52
+
53
+ # ------------------------------------------------------------------
54
+ # Feature detection
55
+ # ------------------------------------------------------------------
56
+
57
+ def _check_kai_enabled(self, alias: str) -> None:
58
+ """Raise KeboolaApiError if Kai is not enabled for the project.
59
+
60
+ Calls verify_token to check owner.features for the agent-chat flag.
61
+ """
62
+ projects = self.resolve_projects([alias])
63
+ project = projects[alias]
64
+ client = self._client_factory(project.stack_url, project.token)
65
+ try:
66
+ token_info = client.verify_token()
67
+ finally:
68
+ client.close()
69
+
70
+ if KAI_FEATURE_FLAG not in token_info.features:
71
+ raise KeboolaApiError(
72
+ message=(
73
+ f"Kai is not enabled for project '{alias}'. "
74
+ "Enable the 'AI Agent Chat' feature in project settings and use "
75
+ "the project's master Storage API token (the auto-generated "
76
+ "'owner' token, not a custom one) — custom tokens cannot access Kai."
77
+ ),
78
+ status_code=0,
79
+ error_code=ErrorCode.KAI_NOT_ENABLED,
80
+ )
81
+
82
+ # ------------------------------------------------------------------
83
+ # Async helpers
84
+ # ------------------------------------------------------------------
85
+
86
+ async def _create_kai_client(self, alias: str) -> KaiClient:
87
+ """Create a KaiClient with auto-discovered URL for the given project."""
88
+ projects = self.resolve_projects([alias])
89
+ project = projects[alias]
90
+ return await KaiClient.from_storage_api(
91
+ storage_api_token=project.token,
92
+ storage_api_url=project.stack_url,
93
+ timeout=KAI_REQUEST_TIMEOUT,
94
+ stream_timeout=KAI_STREAM_TIMEOUT,
95
+ )
96
+
97
+ # ------------------------------------------------------------------
98
+ # Public methods (sync wrappers)
99
+ # ------------------------------------------------------------------
100
+
101
+ def ping(self, alias: str) -> dict[str, Any]:
102
+ """Check Kai server health for a project.
103
+
104
+ Returns:
105
+ Dict with timestamp and server info.
106
+ """
107
+ self._check_kai_enabled(alias)
108
+
109
+ async def _ping() -> dict[str, Any]:
110
+ client = await self._create_kai_client(alias)
111
+ async with client:
112
+ ping_resp = await client.ping()
113
+ info_resp = await client.info()
114
+
115
+ return {
116
+ "project_alias": alias,
117
+ "timestamp": ping_resp.timestamp.isoformat(),
118
+ "app_name": info_resp.app_name,
119
+ "app_version": info_resp.app_version,
120
+ "server_version": info_resp.server_version,
121
+ "mcp_status": (
122
+ info_resp.connected_mcp.get("status", "unknown")
123
+ if isinstance(info_resp.connected_mcp, dict)
124
+ else "unknown"
125
+ ),
126
+ }
127
+
128
+ try:
129
+ return asyncio.run(_ping())
130
+ except KaiError as exc:
131
+ raise KeboolaApiError(
132
+ message=f"Kai ping failed: {exc.message}",
133
+ status_code=0,
134
+ error_code=ErrorCode.KAI_ERROR,
135
+ ) from exc
136
+
137
+ def ask(self, alias: str, message: str) -> dict[str, Any]:
138
+ """Send a one-shot question to Kai and collect the full text response.
139
+
140
+ Args:
141
+ alias: Project alias.
142
+ message: The question to ask.
143
+
144
+ Returns:
145
+ Dict with chat_id and response text.
146
+ """
147
+ self._check_kai_enabled(alias)
148
+
149
+ async def _ask() -> dict[str, Any]:
150
+ client = await self._create_kai_client(alias)
151
+ async with client:
152
+ chat_id, response_text = await client.chat(message)
153
+
154
+ return {
155
+ "project_alias": alias,
156
+ "chat_id": chat_id,
157
+ "response": response_text,
158
+ }
159
+
160
+ try:
161
+ return asyncio.run(_ask())
162
+ except KaiError as exc:
163
+ raise KeboolaApiError(
164
+ message=f"Kai ask failed: {exc.message}",
165
+ status_code=0,
166
+ error_code=ErrorCode.KAI_ERROR,
167
+ ) from exc
168
+
169
+ def chat_message(self, alias: str, message: str, chat_id: str | None = None) -> dict[str, Any]:
170
+ """Send a message in a chat session and collect the response.
171
+
172
+ Args:
173
+ alias: Project alias.
174
+ message: The message to send.
175
+ chat_id: Optional existing chat ID to continue.
176
+
177
+ Returns:
178
+ Dict with chat_id and response text.
179
+ """
180
+ self._check_kai_enabled(alias)
181
+
182
+ async def _chat() -> dict[str, Any]:
183
+ client = await self._create_kai_client(alias)
184
+ async with client:
185
+ cid = chat_id or client.new_chat_id()
186
+ response_parts: list[str] = []
187
+ async for event in client.send_message(cid, message):
188
+ if event.type == "text":
189
+ text = getattr(event, "text", None)
190
+ if text is not None:
191
+ response_parts.append(text)
192
+
193
+ return {
194
+ "project_alias": alias,
195
+ "chat_id": cid,
196
+ "response": "".join(response_parts),
197
+ }
198
+
199
+ try:
200
+ return asyncio.run(_chat())
201
+ except KaiError as exc:
202
+ raise KeboolaApiError(
203
+ message=f"Kai chat failed: {exc.message}",
204
+ status_code=0,
205
+ error_code=ErrorCode.KAI_ERROR,
206
+ ) from exc
207
+
208
+ def preflight(self, alias: str) -> dict[str, Any]:
209
+ """Inspect the configured token for Kai readiness without raising.
210
+
211
+ Unlike ``ping``/``ask``/``chat`` which call ``_check_kai_enabled`` and
212
+ raise ``KAI_NOT_ENABLED`` when the agent-chat feature flag is missing,
213
+ this method returns a structured payload describing token state so the
214
+ UI can render a single, informative warning instead of an error
215
+ cascade. Detects both required conditions:
216
+
217
+ - ``isMasterToken == True`` on /v2/storage/tokens/verify (the
218
+ auto-generated 'owner' token; custom tokens cannot access Kai)
219
+ - ``agent-chat`` in ``owner.features``
220
+
221
+ Returns:
222
+ Dict with keys: ``project_alias``, ``ok`` (bool, both conditions
223
+ true), ``is_master_token``, ``has_agent_chat_feature``,
224
+ ``token_description``, ``project_id``, ``project_name``, and
225
+ ``error`` (None on success, string describing the failure).
226
+ """
227
+ projects = self.resolve_projects([alias])
228
+ project = projects[alias]
229
+ result: dict[str, Any] = {
230
+ "project_alias": alias,
231
+ "ok": False,
232
+ "is_master_token": False,
233
+ "has_agent_chat_feature": False,
234
+ "token_description": None,
235
+ "project_id": None,
236
+ "project_name": None,
237
+ "error": None,
238
+ }
239
+ client = self._client_factory(project.stack_url, project.token)
240
+ try:
241
+ raw = client.get_project_info()
242
+ except KeboolaApiError as exc:
243
+ result["error"] = exc.message
244
+ return result
245
+ finally:
246
+ client.close()
247
+
248
+ owner = raw.get("owner", {}) or {}
249
+ features = owner.get("features", []) or []
250
+ result["is_master_token"] = bool(raw.get("isMasterToken", False))
251
+ result["has_agent_chat_feature"] = KAI_FEATURE_FLAG in features
252
+ result["token_description"] = raw.get("description")
253
+ result["project_id"] = owner.get("id")
254
+ result["project_name"] = owner.get("name")
255
+ result["ok"] = result["is_master_token"] and result["has_agent_chat_feature"]
256
+ if not result["ok"]:
257
+ reasons: list[str] = []
258
+ if not result["is_master_token"]:
259
+ reasons.append("the configured token is not the project's master ('owner') token")
260
+ if not result["has_agent_chat_feature"]:
261
+ reasons.append("the project lacks the 'AI Agent Chat' feature flag")
262
+ result["error"] = "; ".join(reasons)
263
+ return result
264
+
265
+ def get_chat_detail(self, alias: str, chat_id: str) -> dict[str, Any]:
266
+ """Fetch full message history for a single chat.
267
+
268
+ Args:
269
+ alias: Project alias.
270
+ chat_id: Kai chat ID (UUID).
271
+
272
+ Returns:
273
+ Dict with chat_id, title, created_at and a flat list of messages.
274
+ Each message is ``{"role": str, "content": str}`` where ``content``
275
+ is the concatenated text of all ``text`` parts (tool calls and
276
+ other non-text parts are skipped — they are an implementation
277
+ detail of Kai's streaming protocol, not user-facing content).
278
+ """
279
+ self._check_kai_enabled(alias)
280
+
281
+ async def _detail() -> dict[str, Any]:
282
+ client = await self._create_kai_client(alias)
283
+ async with client:
284
+ chat_detail = await client.get_chat(chat_id)
285
+
286
+ messages: list[dict[str, Any]] = []
287
+ for msg in chat_detail.messages:
288
+ parts = msg.parts or []
289
+ text_segments = [
290
+ p.get("text", "")
291
+ for p in parts
292
+ if isinstance(p, dict) and p.get("type") == "text"
293
+ ]
294
+ content = "".join(text_segments)
295
+ if not content and msg.role == "user":
296
+ # User messages occasionally arrive with text wrapped in a
297
+ # different shape (legacy chats). Surface the raw parts
298
+ # JSON dump as a fallback so the user at least sees
299
+ # something — better than a silent empty bubble.
300
+ continue
301
+ messages.append(
302
+ {
303
+ "id": msg.id,
304
+ "role": msg.role,
305
+ "content": content,
306
+ "created_at": msg.created_at.isoformat() if msg.created_at else None,
307
+ }
308
+ )
309
+
310
+ return {
311
+ "project_alias": alias,
312
+ "chat_id": chat_detail.id,
313
+ "title": chat_detail.title,
314
+ "created_at": chat_detail.created_at.isoformat()
315
+ if chat_detail.created_at
316
+ else None,
317
+ "messages": messages,
318
+ }
319
+
320
+ try:
321
+ return asyncio.run(_detail())
322
+ except KaiError as exc:
323
+ raise KeboolaApiError(
324
+ message=f"Kai chat detail failed: {exc.message}",
325
+ status_code=0,
326
+ error_code=ErrorCode.KAI_ERROR,
327
+ ) from exc
328
+
329
+ def get_history(self, alias: str, limit: int = 10) -> dict[str, Any]:
330
+ """Get chat history for the current user.
331
+
332
+ Args:
333
+ alias: Project alias.
334
+ limit: Max number of chats to return.
335
+
336
+ Returns:
337
+ Dict with list of chat summaries.
338
+ """
339
+ self._check_kai_enabled(alias)
340
+
341
+ async def _history() -> dict[str, Any]:
342
+ client = await self._create_kai_client(alias)
343
+ async with client:
344
+ history = await client.get_history(limit=limit)
345
+
346
+ return {
347
+ "project_alias": alias,
348
+ "chats": [
349
+ {
350
+ "id": chat.id,
351
+ "title": chat.title or "(untitled)",
352
+ "created_at": chat.created_at.isoformat() if chat.created_at else None,
353
+ "visibility": chat.visibility,
354
+ }
355
+ for chat in history.chats
356
+ ],
357
+ "has_more": history.has_more,
358
+ }
359
+
360
+ try:
361
+ return asyncio.run(_history())
362
+ except KaiError as exc:
363
+ raise KeboolaApiError(
364
+ message=f"Kai history failed: {exc.message}",
365
+ status_code=0,
366
+ error_code=ErrorCode.KAI_ERROR,
367
+ ) from exc
@@ -0,0 +1,274 @@
1
+ """Lineage service - cross-project data flow analysis via bucket sharing.
2
+
3
+ Queries the Storage API for bucket sharing metadata and builds
4
+ a graph of data flow edges between projects. Fetches buckets from
5
+ all projects in parallel using BaseService._run_parallel().
6
+ """
7
+
8
+ import logging
9
+ from typing import Any
10
+
11
+ from ..errors import KeboolaApiError
12
+ from ..models import ProjectConfig
13
+ from .base import BaseService
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class LineageService(BaseService):
19
+ """Business logic for cross-project data lineage via bucket sharing.
20
+
21
+ Queries each project's Storage API for bucket sharing metadata,
22
+ classifies buckets as shared or linked, and builds a deduplicated
23
+ set of data flow edges.
24
+
25
+ Uses BaseService._run_parallel() for concurrent project fetching.
26
+ """
27
+
28
+ def _fetch_project_buckets(
29
+ self, alias: str, project: ProjectConfig
30
+ ) -> tuple[str, ProjectConfig, list[dict[str, Any]]] | tuple[str, dict[str, str]]:
31
+ """Fetch buckets for a single project (runs in a worker thread).
32
+
33
+ Creates its own KeboolaClient, fetches buckets, and closes the client.
34
+ Returns either (alias, project, buckets) on success or (alias, error_dict)
35
+ on failure. The client is always closed in the finally block.
36
+ """
37
+ client = self._client_factory(project.stack_url, project.token)
38
+ try:
39
+ buckets = client.list_buckets(include="linkedBuckets")
40
+ return (alias, project, buckets)
41
+ except KeboolaApiError as exc:
42
+ return (
43
+ alias,
44
+ {
45
+ "project_alias": alias,
46
+ "error_code": exc.error_code,
47
+ "message": exc.message,
48
+ },
49
+ )
50
+ except Exception as exc:
51
+ return (
52
+ alias,
53
+ {
54
+ "project_alias": alias,
55
+ "error_code": "UNEXPECTED_ERROR",
56
+ "message": str(exc),
57
+ },
58
+ )
59
+ finally:
60
+ client.close()
61
+
62
+ def get_lineage(self, aliases: list[str] | None = None) -> dict[str, Any]:
63
+ """Analyze cross-project data lineage via bucket sharing.
64
+
65
+ For each resolved project, fetches buckets with linkedBuckets info
66
+ in parallel using BaseService._run_parallel(), classifies them as
67
+ shared or linked, and builds deduplicated edges.
68
+
69
+ Args:
70
+ aliases: Project aliases to query. None means all projects.
71
+
72
+ Returns:
73
+ Dict with keys:
74
+ - "edges": list of data flow edge dicts (sorted by source/target)
75
+ - "shared_buckets": list of shared bucket dicts
76
+ - "linked_buckets": list of linked bucket dicts
77
+ - "summary": dict with counts
78
+ - "errors": list of error dicts
79
+
80
+ Raises:
81
+ ConfigError: If a specified alias is not found.
82
+ """
83
+ projects = self.resolve_projects(aliases)
84
+
85
+ # Early return for zero projects
86
+ if not projects:
87
+ return {
88
+ "edges": [],
89
+ "shared_buckets": [],
90
+ "linked_buckets": [],
91
+ "summary": {
92
+ "total_shared_buckets": 0,
93
+ "total_linked_buckets": 0,
94
+ "total_edges": 0,
95
+ "projects_queried": 0,
96
+ "projects_with_errors": 0,
97
+ },
98
+ "errors": [],
99
+ }
100
+
101
+ # Build project_id -> alias lookup for cross-referencing
102
+ project_id_to_alias: dict[int, str] = {}
103
+ for alias, project in projects.items():
104
+ if project.project_id is None:
105
+ logger.warning("Project '%s' has no project_id; skipping lineage", alias)
106
+ continue
107
+ project_id_to_alias[project.project_id] = alias
108
+
109
+ all_shared_buckets: list[dict[str, Any]] = []
110
+ all_linked_buckets: list[dict[str, Any]] = []
111
+ edges_by_key: dict[tuple[int, str, int, str], dict[str, Any]] = {}
112
+
113
+ # Fetch buckets from all projects in parallel using BaseService._run_parallel()
114
+ successes, errors = self._run_parallel(projects, self._fetch_project_buckets)
115
+
116
+ for success in successes:
117
+ alias, project, buckets = success
118
+ self._process_buckets(
119
+ buckets=buckets,
120
+ alias=alias,
121
+ project=project,
122
+ project_id_to_alias=project_id_to_alias,
123
+ all_shared_buckets=all_shared_buckets,
124
+ all_linked_buckets=all_linked_buckets,
125
+ edges_by_key=edges_by_key,
126
+ )
127
+
128
+ # Sort results for deterministic output (parallel execution order varies).
129
+ # Use str() in sort keys: source_project_id comes from Pydantic (int) but
130
+ # target_project_id comes from API linkedBy[].project.id which returns str.
131
+ edges = sorted(
132
+ edges_by_key.values(),
133
+ key=lambda e: (
134
+ str(e.get("source_project_id", "")),
135
+ str(e.get("source_bucket_id", "")),
136
+ str(e.get("target_project_id", "")),
137
+ str(e.get("target_bucket_id", "")),
138
+ ),
139
+ )
140
+ all_shared_buckets.sort(
141
+ key=lambda b: (str(b.get("project_id", "")), str(b.get("bucket_id", "")))
142
+ )
143
+ all_linked_buckets.sort(
144
+ key=lambda b: (str(b.get("project_id", "")), str(b.get("bucket_id", "")))
145
+ )
146
+ errors.sort(key=lambda e: e.get("project_alias", ""))
147
+
148
+ summary = {
149
+ "total_shared_buckets": len(all_shared_buckets),
150
+ "total_linked_buckets": len(all_linked_buckets),
151
+ "total_edges": len(edges),
152
+ "projects_queried": len(projects),
153
+ "projects_with_errors": len(errors),
154
+ }
155
+
156
+ return {
157
+ "edges": edges,
158
+ "shared_buckets": all_shared_buckets,
159
+ "linked_buckets": all_linked_buckets,
160
+ "summary": summary,
161
+ "errors": errors,
162
+ }
163
+
164
+ def _process_buckets(
165
+ self,
166
+ buckets: list[dict[str, Any]],
167
+ alias: str,
168
+ project: ProjectConfig,
169
+ project_id_to_alias: dict[int, str],
170
+ all_shared_buckets: list[dict[str, Any]],
171
+ all_linked_buckets: list[dict[str, Any]],
172
+ edges_by_key: dict[tuple[int, str, int, str], dict[str, Any]],
173
+ ) -> None:
174
+ """Process buckets from a single project, extracting shared/linked info and edges."""
175
+ # project_id is required to build edge keys; projects without one are
176
+ # already warned in get_lineage() but still pass through _run_parallel
177
+ # (the skip only prevents them from appearing in project_id_to_alias).
178
+ if project.project_id is None:
179
+ return
180
+ project_id: int = project.project_id
181
+ for bucket in buckets:
182
+ sharing = bucket.get("sharing")
183
+ linked_by = bucket.get("linkedBy", [])
184
+ source_bucket = bucket.get("sourceBucket")
185
+
186
+ # Shared bucket: has sharing field
187
+ if sharing:
188
+ shared_info = {
189
+ "project_alias": alias,
190
+ "project_id": project.project_id,
191
+ "project_name": project.project_name,
192
+ "bucket_id": bucket.get("id", ""),
193
+ "bucket_name": bucket.get("name", bucket.get("displayName", "")),
194
+ "sharing_type": sharing,
195
+ "shared_by": bucket.get("sharedBy", {}),
196
+ }
197
+ all_shared_buckets.append(shared_info)
198
+
199
+ # Each linkedBy entry = one edge from this project to target
200
+ for link in linked_by:
201
+ target_project = link.get("project", {})
202
+ target_project_id = target_project.get("id", 0)
203
+ target_bucket_id = link.get("id", "")
204
+
205
+ edge_key = (
206
+ project_id,
207
+ bucket.get("id", ""),
208
+ int(target_project_id),
209
+ str(target_bucket_id),
210
+ )
211
+
212
+ edge = edges_by_key.get(edge_key, {})
213
+ edge.update(
214
+ {
215
+ "source_project_alias": alias,
216
+ "source_project_id": project.project_id,
217
+ "source_project_name": project.project_name,
218
+ "source_bucket_id": bucket.get("id", ""),
219
+ "source_bucket_name": bucket.get("name", bucket.get("displayName", "")),
220
+ "sharing_type": sharing,
221
+ "target_project_alias": project_id_to_alias.get(target_project_id, ""),
222
+ "target_project_id": target_project_id,
223
+ "target_project_name": target_project.get("name", ""),
224
+ "target_bucket_id": target_bucket_id,
225
+ }
226
+ )
227
+ edges_by_key[edge_key] = edge
228
+
229
+ # Linked bucket: has sourceBucket field
230
+ if source_bucket:
231
+ source_project = source_bucket.get("project", {})
232
+ linked_info = {
233
+ "project_alias": alias,
234
+ "project_id": project.project_id,
235
+ "project_name": project.project_name,
236
+ "bucket_id": bucket.get("id", ""),
237
+ "bucket_name": bucket.get("name", bucket.get("displayName", "")),
238
+ "source_bucket_id": source_bucket.get("id", ""),
239
+ "source_project_id": source_project.get("id", 0),
240
+ "source_project_name": source_project.get("name", ""),
241
+ "is_readonly": bucket.get("isReadonly", False),
242
+ }
243
+ all_linked_buckets.append(linked_info)
244
+
245
+ source_project_id = source_project.get("id", 0)
246
+ source_bucket_id = source_bucket.get("id", "")
247
+
248
+ edge_key = (
249
+ int(source_project_id),
250
+ str(source_bucket_id),
251
+ project_id,
252
+ bucket.get("id", ""),
253
+ )
254
+
255
+ edge = edges_by_key.get(edge_key, {})
256
+ # Only set fields that are empty or missing
257
+ if not edge.get("source_project_alias"):
258
+ edge["source_project_alias"] = project_id_to_alias.get(source_project_id, "")
259
+ if not edge.get("source_project_id"):
260
+ edge["source_project_id"] = source_project_id
261
+ if not edge.get("source_project_name"):
262
+ edge["source_project_name"] = source_project.get("name", "")
263
+ if not edge.get("source_bucket_id"):
264
+ edge["source_bucket_id"] = source_bucket_id
265
+ # Always set target side from linked bucket
266
+ edge["target_project_alias"] = alias
267
+ edge["target_project_id"] = project.project_id
268
+ edge["target_project_name"] = project.project_name
269
+ edge["target_bucket_id"] = bucket.get("id", "")
270
+ # Preserve sharing_type if already set from shared side
271
+ if not edge.get("sharing_type"):
272
+ edge["sharing_type"] = ""
273
+
274
+ edges_by_key[edge_key] = edge