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,292 @@
1
+ """Organization management commands - bulk project onboarding.
2
+
3
+ Thin CLI layer: parses arguments, calls OrgService, formats output.
4
+ No business logic belongs here.
5
+ """
6
+
7
+ import typer
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from ..constants import DEFAULT_TOKEN_DESCRIPTION, ENV_KBC_STORAGE_API_URL
12
+ from ..errors import ErrorCode, KeboolaApiError
13
+ from ._helpers import (
14
+ check_cli_permission,
15
+ get_formatter,
16
+ get_service,
17
+ map_error_to_exit_code,
18
+ resolve_manage_token,
19
+ )
20
+
21
+ org_app = typer.Typer(help="Organization management")
22
+
23
+
24
+ @org_app.callback(invoke_without_command=True)
25
+ def _org_permission_check(ctx: typer.Context) -> None:
26
+ check_cli_permission(ctx, "org")
27
+
28
+
29
+ def _parse_project_ids(value: str | None) -> list[int] | None:
30
+ """Parse comma-separated project IDs string into a list of ints."""
31
+ if not value:
32
+ return None
33
+ try:
34
+ return [int(pid.strip()) for pid in value.split(",") if pid.strip()]
35
+ except ValueError as exc:
36
+ msg = f"Invalid project ID (must be integers): {exc}"
37
+ raise typer.BadParameter(msg) from exc
38
+
39
+
40
+ def _format_setup_result(console: Console, data: dict) -> None:
41
+ """Render org setup results as Rich tables with summary."""
42
+ org_id = data.get("organization_id")
43
+ stack_url = data.get("stack_url", "")
44
+ dry_run = data.get("dry_run", False)
45
+ projects_found = data.get("projects_found", 0)
46
+ added = data.get("projects_added", [])
47
+ refreshed = data.get("projects_refreshed", [])
48
+ skipped = data.get("projects_skipped", [])
49
+ failed = data.get("projects_failed", [])
50
+
51
+ token_expires_in = data.get("token_expires_in")
52
+ mode_label = "[bold yellow]DRY RUN[/bold yellow] " if dry_run else ""
53
+ expiry_label = (
54
+ f", token expiration: [bold]{token_expires_in}s[/bold]" if token_expires_in else ""
55
+ )
56
+ org_label = f"Organization [bold]{org_id}[/bold] on " if org_id else ""
57
+ console.print(
58
+ f"\n{mode_label}{org_label}{stack_url} -- {projects_found} project(s) found{expiry_label}\n"
59
+ )
60
+
61
+ # Added / would-add table
62
+ if added:
63
+ action_label = "Projects to Add" if dry_run else "Projects Added"
64
+ table = Table(title=action_label)
65
+ table.add_column("Alias", style="bold cyan")
66
+ table.add_column("Project ID", justify="right")
67
+ table.add_column("Project Name")
68
+ if not dry_run:
69
+ table.add_column("Token", style="dim")
70
+
71
+ for p in added:
72
+ if dry_run:
73
+ table.add_row(p["alias"], str(p["project_id"]), p["project_name"])
74
+ else:
75
+ table.add_row(
76
+ p["alias"], str(p["project_id"]), p["project_name"], p.get("token", "")
77
+ )
78
+
79
+ console.print(table)
80
+ console.print()
81
+
82
+ # Refreshed tokens table
83
+ if refreshed:
84
+ action_label = "Tokens to Refresh" if dry_run else "Tokens Refreshed"
85
+ table = Table(title=action_label)
86
+ table.add_column("Alias", style="bold cyan")
87
+ table.add_column("Project ID", justify="right")
88
+ table.add_column("Project Name")
89
+ if not dry_run:
90
+ table.add_column("Token", style="dim")
91
+
92
+ for p in refreshed:
93
+ if dry_run:
94
+ table.add_row(p["alias"], str(p["project_id"]), p["project_name"])
95
+ else:
96
+ table.add_row(
97
+ p["alias"], str(p["project_id"]), p["project_name"], p.get("token", "")
98
+ )
99
+
100
+ console.print(table)
101
+ console.print()
102
+
103
+ # Skipped table
104
+ if skipped:
105
+ table = Table(title="Projects Skipped")
106
+ table.add_column("Project ID", justify="right")
107
+ table.add_column("Project Name")
108
+ table.add_column("Reason", style="dim")
109
+
110
+ for p in skipped:
111
+ table.add_row(str(p["project_id"]), p["project_name"], p["reason"])
112
+
113
+ console.print(table)
114
+ console.print()
115
+
116
+ # Failed table
117
+ if failed:
118
+ table = Table(title="Projects Failed")
119
+ table.add_column("Project ID", justify="right")
120
+ table.add_column("Project Name")
121
+ table.add_column("Error", style="bold red")
122
+
123
+ for p in failed:
124
+ table.add_row(str(p["project_id"]), p["project_name"], p["error"])
125
+
126
+ console.print(table)
127
+ console.print()
128
+
129
+ # Summary line
130
+ summary_parts = []
131
+ if added:
132
+ verb = "to add" if dry_run else "added"
133
+ summary_parts.append(f"[bold green]{len(added)}[/bold green] {verb}")
134
+ if refreshed:
135
+ verb = "to refresh" if dry_run else "refreshed"
136
+ summary_parts.append(f"[bold cyan]{len(refreshed)}[/bold cyan] {verb}")
137
+ if skipped:
138
+ summary_parts.append(f"[dim]{len(skipped)} skipped[/dim]")
139
+ if failed:
140
+ summary_parts.append(f"[bold red]{len(failed)} failed[/bold red]")
141
+
142
+ console.print("Summary: " + ", ".join(summary_parts) if summary_parts else "No changes.")
143
+
144
+
145
+ @org_app.command("setup")
146
+ def org_setup(
147
+ ctx: typer.Context,
148
+ org_id: int | None = typer.Option(
149
+ None,
150
+ "--org-id",
151
+ help="Organization ID (requires org-admin manage token)",
152
+ ),
153
+ project_ids_raw: str | None = typer.Option(
154
+ None,
155
+ "--project-ids",
156
+ help="Comma-separated project IDs (works with Personal Access Token)",
157
+ ),
158
+ url: str = typer.Option(
159
+ ...,
160
+ "--url",
161
+ envvar=ENV_KBC_STORAGE_API_URL,
162
+ help="Keboola stack URL (e.g. https://connection.keboola.com)",
163
+ ),
164
+ dry_run: bool = typer.Option(
165
+ False,
166
+ "--dry-run",
167
+ help="Preview what would happen without making changes",
168
+ ),
169
+ yes: bool = typer.Option(
170
+ False,
171
+ "--yes",
172
+ "-y",
173
+ help="Skip confirmation prompt",
174
+ ),
175
+ token_description: str = typer.Option(
176
+ DEFAULT_TOKEN_DESCRIPTION,
177
+ "--token-description",
178
+ help="Description prefix for created Storage API tokens",
179
+ ),
180
+ token_expires_in: int | None = typer.Option(
181
+ None,
182
+ "--token-expires-in",
183
+ min=1,
184
+ help="Token lifetime in seconds (e.g. 3600 for 1 hour). If not set, tokens never expire.",
185
+ ),
186
+ refresh: bool = typer.Option(
187
+ False,
188
+ "--refresh",
189
+ help="Refresh tokens for already-registered projects with invalid tokens",
190
+ ),
191
+ ) -> None:
192
+ """Set up projects and register them in the kbagent config.
193
+
194
+ Two modes:
195
+
196
+ \b
197
+ 1. Org admin: --org-id 123 (lists all projects in the org)
198
+ 2. Project member: --project-ids 1,2,3 (fetches specific projects)
199
+
200
+ Creates Storage API tokens and registers projects. Safe to re-run --
201
+ already registered projects are skipped.
202
+
203
+ The Manage API token is read from an interactive hidden prompt by
204
+ default (since 0.28.0). Pass the top-level --allow-env-manage-token
205
+ flag to read KBC_MANAGE_API_TOKEN from env (CI/CD). Never passed as
206
+ a CLI argument.
207
+ """
208
+ formatter = get_formatter(ctx)
209
+ service = get_service(ctx, "org_service")
210
+
211
+ # Validate: need at least one of --org-id or --project-ids
212
+ project_ids = _parse_project_ids(project_ids_raw)
213
+ if not org_id and not project_ids:
214
+ formatter.error(
215
+ message="Provide --org-id (org admin) or --project-ids (project member)",
216
+ error_code=ErrorCode.USAGE_ERROR,
217
+ )
218
+ raise typer.Exit(code=2)
219
+
220
+ manage_token = resolve_manage_token(allow_env=ctx.obj["allow_env_manage_token"])
221
+
222
+ # Build kwargs shared by preview and real call
223
+ setup_kwargs: dict = {
224
+ "stack_url": url,
225
+ "manage_token": manage_token,
226
+ "org_id": org_id,
227
+ "token_description": token_description,
228
+ "token_expires_in": token_expires_in,
229
+ "project_ids": project_ids,
230
+ }
231
+
232
+ # Interactive safety: show preview first, then confirm
233
+ interactive = not formatter.json_mode and not yes and not dry_run
234
+ if interactive:
235
+ try:
236
+ preview = service.setup_organization(**setup_kwargs, dry_run=True)
237
+ except KeboolaApiError as exc:
238
+ _handle_api_error(formatter, exc)
239
+ return
240
+
241
+ _format_setup_result(formatter.console, preview)
242
+
243
+ would_add = len(preview.get("projects_added", []))
244
+ would_skip = len(preview.get("projects_skipped", []))
245
+ if would_add == 0 and not (refresh and would_skip > 0):
246
+ formatter.console.print("\nNo new projects to add.")
247
+ return
248
+
249
+ if would_add > 0 and not typer.confirm(f"\nProceed to add {would_add} project(s)?"):
250
+ formatter.console.print("Aborted.")
251
+ raise typer.Exit(code=0)
252
+
253
+ # Execute the actual setup
254
+ try:
255
+ result = service.setup_organization(**setup_kwargs, dry_run=dry_run)
256
+ except KeboolaApiError as exc:
257
+ _handle_api_error(formatter, exc)
258
+ return
259
+
260
+ # Refresh tokens for skipped (already-registered) projects if requested
261
+ if refresh and result.get("projects_skipped"):
262
+ config = ctx.obj["config_store"].load()
263
+ skipped_ids = {p["project_id"] for p in result["projects_skipped"]}
264
+ skipped_aliases = [
265
+ alias for alias, proj in config.projects.items() if proj.project_id in skipped_ids
266
+ ]
267
+ if skipped_aliases:
268
+ try:
269
+ refresh_result = service.refresh_tokens(
270
+ manage_token=manage_token,
271
+ aliases=skipped_aliases,
272
+ token_description=token_description,
273
+ token_expires_in=token_expires_in,
274
+ dry_run=dry_run,
275
+ )
276
+ result["projects_refreshed"] = refresh_result.get("projects_refreshed", [])
277
+ except KeboolaApiError as exc:
278
+ _handle_api_error(formatter, exc)
279
+ return
280
+
281
+ formatter.output(result, _format_setup_result)
282
+
283
+
284
+ def _handle_api_error(formatter, exc: KeboolaApiError) -> None:
285
+ """Handle a KeboolaApiError by outputting it and raising Exit."""
286
+ exit_code = map_error_to_exit_code(exc)
287
+ formatter.error(
288
+ message=exc.message,
289
+ error_code=exc.error_code,
290
+ retryable=exc.retryable,
291
+ )
292
+ raise typer.Exit(code=exit_code)
@@ -0,0 +1,360 @@
1
+ """Permission management commands - list, show, set, reset, check.
2
+
3
+ Thin CLI layer for managing the firewall-style permission policy.
4
+ No business logic belongs here -- the PermissionEngine handles evaluation.
5
+
6
+ Security: set and reset require interactive confirmation (type a random code)
7
+ so that an AI agent constrained by the policy cannot bypass it programmatically.
8
+ """
9
+
10
+ from typing import Any
11
+
12
+ import typer
13
+ from rich.console import Console
14
+ from rich.table import Table
15
+
16
+ from ..config_store import ConfigStore
17
+ from ..constants import EXIT_PERMISSION_DENIED
18
+ from ..errors import ErrorCode
19
+ from ..models import PermissionPolicy
20
+ from ..permissions import PermissionEngine
21
+ from ._helpers import get_formatter, get_service, require_random_code_confirmation
22
+
23
+ permissions_app = typer.Typer(help="Manage operation permissions (firewall rules)")
24
+
25
+
26
+ def _format_operations_table(
27
+ console: Console,
28
+ operations: list[dict[str, Any]],
29
+ category_filter: str | None = None,
30
+ ) -> None:
31
+ """Render a Rich table of operations with their status."""
32
+ if category_filter:
33
+ operations = [op for op in operations if op["category"] == category_filter]
34
+
35
+ table = Table(title="Operations")
36
+ table.add_column("Operation", style="bold cyan")
37
+ table.add_column("Type", style="dim")
38
+ table.add_column("Category")
39
+ table.add_column("Status", justify="center")
40
+ table.add_column("Description", style="dim")
41
+
42
+ category_styles = {
43
+ "read": "green",
44
+ "write": "yellow",
45
+ "destructive": "red",
46
+ "admin": "bold red",
47
+ }
48
+
49
+ for op in operations:
50
+ cat = op["category"]
51
+ cat_styled = f"[{category_styles.get(cat, '')}]{cat}[/{category_styles.get(cat, '')}]"
52
+ status = op["status"]
53
+ status_styled = (
54
+ f"[green]{status}[/green]" if status == "allowed" else f"[red]{status}[/red]"
55
+ )
56
+ desc = op.get("description", "")
57
+ table.add_row(op["name"], op["type"], cat_styled, status_styled, desc)
58
+
59
+ console.print(table)
60
+
61
+
62
+ @permissions_app.command("list")
63
+ def permissions_list(
64
+ ctx: typer.Context,
65
+ category: str | None = typer.Option(
66
+ None,
67
+ "--category",
68
+ "-c",
69
+ help="Filter by risk category: read, write, destructive, admin",
70
+ ),
71
+ ) -> None:
72
+ """List all operations with their risk category and current allowed/denied status.
73
+
74
+ The allowed/denied column reflects the EFFECTIVE policy for this
75
+ invocation -- i.e. the persisted policy merged with any top-level
76
+ session flags like ``--deny-writes`` / ``--deny-destructive``. This
77
+ matches what a command will actually do right now.
78
+ """
79
+ from ..cli import apply_firewall_flags
80
+
81
+ formatter = get_formatter(ctx)
82
+ config_store: ConfigStore = get_service(ctx, "config_store")
83
+ config = config_store.load()
84
+
85
+ deny_writes = bool(ctx.obj.get("deny_writes")) if ctx.obj else False
86
+ deny_destructive = bool(ctx.obj.get("deny_destructive")) if ctx.obj else False
87
+ effective_policy = apply_firewall_flags(
88
+ config.permissions,
89
+ deny_writes=deny_writes,
90
+ deny_destructive=deny_destructive,
91
+ )
92
+
93
+ engine = PermissionEngine(effective_policy)
94
+ ops = engine.list_operations()
95
+
96
+ if formatter.json_mode:
97
+ if category:
98
+ ops = [op for op in ops if op["category"] == category]
99
+ formatter.output(ops)
100
+ else:
101
+ _format_operations_table(formatter.console, ops, category_filter=category)
102
+ if not engine.active:
103
+ formatter.err_console.print(
104
+ "\n[dim]No permission policy active. All operations are allowed.[/dim]"
105
+ )
106
+ elif deny_writes or deny_destructive:
107
+ active_flags = []
108
+ if deny_writes:
109
+ active_flags.append("--deny-writes")
110
+ if deny_destructive:
111
+ active_flags.append("--deny-destructive")
112
+ formatter.err_console.print(
113
+ f"\n[dim]Session firewall active: {' '.join(active_flags)} (not persisted).[/dim]"
114
+ )
115
+
116
+
117
+ @permissions_app.command("show")
118
+ def permissions_show(
119
+ ctx: typer.Context,
120
+ ) -> None:
121
+ """Show the current active permission policy.
122
+
123
+ Reports both the PERSISTED policy (from config.json) and any SESSION
124
+ firewall layered on top via top-level ``--deny-writes`` /
125
+ ``--deny-destructive`` flags. Session flags are shown but are never
126
+ written to config.json -- they apply to this invocation only.
127
+ """
128
+ formatter = get_formatter(ctx)
129
+ config_store: ConfigStore = get_service(ctx, "config_store")
130
+ config = config_store.load()
131
+
132
+ deny_writes = bool(ctx.obj.get("deny_writes")) if ctx.obj else False
133
+ deny_destructive = bool(ctx.obj.get("deny_destructive")) if ctx.obj else False
134
+ session_flags: list[str] = []
135
+ if deny_writes:
136
+ session_flags.append("--deny-writes")
137
+ if deny_destructive:
138
+ session_flags.append("--deny-destructive")
139
+
140
+ persisted = config.permissions
141
+
142
+ if persisted is None and not session_flags:
143
+ if formatter.json_mode:
144
+ formatter.output(
145
+ {
146
+ "active": False,
147
+ "message": "No permission policy configured",
148
+ "session_flags": [],
149
+ }
150
+ )
151
+ else:
152
+ formatter.console.print("No permission policy configured. All operations are allowed.")
153
+ return
154
+
155
+ policy_data: dict[str, Any] = {
156
+ "active": persisted is not None or bool(session_flags),
157
+ "persisted": (
158
+ None
159
+ if persisted is None
160
+ else {
161
+ "mode": persisted.mode,
162
+ "allow": persisted.allow,
163
+ "deny": persisted.deny,
164
+ }
165
+ ),
166
+ "session_flags": session_flags,
167
+ }
168
+
169
+ # Keep legacy top-level keys when a persisted policy exists so existing
170
+ # JSON consumers that read policy_data["mode"] / ["allow"] / ["deny"]
171
+ # remain compatible. Clients that need the new session-layer view read
172
+ # ``session_flags`` and ``persisted``.
173
+ if persisted is not None:
174
+ policy_data["mode"] = persisted.mode
175
+ policy_data["allow"] = persisted.allow
176
+ policy_data["deny"] = persisted.deny
177
+
178
+ if formatter.json_mode:
179
+ formatter.output(policy_data)
180
+ return
181
+
182
+ if persisted is not None:
183
+ mode_desc = (
184
+ "default-allow (everything allowed unless denied)"
185
+ if persisted.mode == "allow"
186
+ else "default-deny (everything denied unless allowed)"
187
+ )
188
+ formatter.console.print(f"[bold]Mode:[/bold] {mode_desc}")
189
+ if persisted.allow:
190
+ formatter.console.print(f"[bold]Allow:[/bold] {', '.join(persisted.allow)}")
191
+ if persisted.deny:
192
+ formatter.console.print(f"[bold]Deny:[/bold] {', '.join(persisted.deny)}")
193
+ else:
194
+ formatter.console.print("[dim]No persisted permission policy (config.json is clean).[/dim]")
195
+
196
+ if session_flags:
197
+ formatter.console.print(
198
+ f"[bold yellow]Session firewall:[/bold yellow] {' '.join(session_flags)} "
199
+ "[dim](active for this invocation only; not persisted)[/dim]"
200
+ )
201
+
202
+
203
+ @permissions_app.command("set")
204
+ def permissions_set(
205
+ ctx: typer.Context,
206
+ mode: str = typer.Option(
207
+ ...,
208
+ "--mode",
209
+ "-m",
210
+ help="Base mode: 'allow' (default-allow) or 'deny' (default-deny)",
211
+ ),
212
+ allow: list[str] | None = typer.Option(
213
+ None,
214
+ "--allow",
215
+ "-a",
216
+ help="Allowed operation patterns (repeatable)",
217
+ ),
218
+ deny: list[str] | None = typer.Option(
219
+ None,
220
+ "--deny",
221
+ "-d",
222
+ help="Denied operation patterns (repeatable)",
223
+ ),
224
+ ) -> None:
225
+ """Set the permission policy (firewall rules).
226
+
227
+ Requires interactive confirmation (type a random code) to prevent
228
+ AI agents from modifying permissions programmatically.
229
+
230
+ Examples:
231
+ # Block all write operations (Vojta's use case):
232
+ kbagent permissions set --mode allow --deny "cli:write" --deny "tool:write"
233
+
234
+ # Allow only read operations:
235
+ kbagent permissions set --mode deny --allow "cli:read" --allow "tool:read"
236
+
237
+ # Block specific operations:
238
+ kbagent permissions set --mode allow --deny "branch.delete" --deny "tool:delete_*"
239
+ """
240
+ formatter = get_formatter(ctx)
241
+
242
+ if mode not in ("allow", "deny"):
243
+ formatter.error(
244
+ message="Mode must be 'allow' or 'deny'",
245
+ error_code=ErrorCode.VALIDATION_ERROR,
246
+ )
247
+ raise typer.Exit(code=2) from None
248
+
249
+ require_random_code_confirmation("update permission policy")
250
+
251
+ config_store: ConfigStore = get_service(ctx, "config_store")
252
+ config = config_store.load()
253
+
254
+ policy = PermissionPolicy(
255
+ mode=mode,
256
+ allow=allow or [],
257
+ deny=deny or [],
258
+ )
259
+ config.permissions = policy
260
+ config_store.save(config)
261
+
262
+ if formatter.json_mode:
263
+ formatter.output(
264
+ {
265
+ "status": "ok",
266
+ "mode": mode,
267
+ "allow": policy.allow,
268
+ "deny": policy.deny,
269
+ }
270
+ )
271
+ else:
272
+ formatter.console.print("[green]Permission policy updated.[/green]")
273
+ mode_desc = (
274
+ "default-allow (everything allowed unless denied)"
275
+ if mode == "allow"
276
+ else "default-deny (everything denied unless allowed)"
277
+ )
278
+ formatter.console.print(f" Mode: {mode_desc}")
279
+ if policy.allow:
280
+ formatter.console.print(f" Allow: {', '.join(policy.allow)}")
281
+ if policy.deny:
282
+ formatter.console.print(f" Deny: {', '.join(policy.deny)}")
283
+
284
+
285
+ @permissions_app.command("reset")
286
+ def permissions_reset(
287
+ ctx: typer.Context,
288
+ ) -> None:
289
+ """Remove all permission restrictions.
290
+
291
+ Requires interactive confirmation (type a random code) to prevent
292
+ AI agents from removing the policy programmatically.
293
+ """
294
+ formatter = get_formatter(ctx)
295
+
296
+ require_random_code_confirmation("remove permission policy")
297
+
298
+ config_store: ConfigStore = get_service(ctx, "config_store")
299
+ config = config_store.load()
300
+
301
+ config.permissions = None
302
+ config_store.save(config)
303
+
304
+ if formatter.json_mode:
305
+ formatter.output({"status": "ok", "message": "Permission policy removed"})
306
+ else:
307
+ formatter.console.print(
308
+ "[green]Permission policy removed. All operations are allowed.[/green]"
309
+ )
310
+
311
+
312
+ @permissions_app.command("check")
313
+ def permissions_check(
314
+ ctx: typer.Context,
315
+ operation: str = typer.Argument(
316
+ help="Operation to check, e.g. 'branch.delete', 'tool:create_config'",
317
+ ),
318
+ ) -> None:
319
+ """Check if a specific operation is allowed.
320
+
321
+ Reflects the EFFECTIVE policy for this invocation: the persisted
322
+ policy merged with any top-level session flags like ``--deny-writes``
323
+ or ``--deny-destructive`` (issue #269 sec-19). Pre-fix, ``permissions
324
+ check`` only consulted the persisted policy, so an AI agent reading
325
+ its own self-imposed firewall flag would get a misleading answer.
326
+
327
+ Exit code 0 = allowed, 6 = denied.
328
+ """
329
+ from ..cli import apply_firewall_flags
330
+
331
+ formatter = get_formatter(ctx)
332
+ config_store: ConfigStore = get_service(ctx, "config_store")
333
+ config = config_store.load()
334
+
335
+ deny_writes = bool(ctx.obj.get("deny_writes")) if ctx.obj else False
336
+ deny_destructive = bool(ctx.obj.get("deny_destructive")) if ctx.obj else False
337
+ effective_policy = apply_firewall_flags(
338
+ config.permissions,
339
+ deny_writes=deny_writes,
340
+ deny_destructive=deny_destructive,
341
+ )
342
+
343
+ engine = PermissionEngine(effective_policy)
344
+ allowed = engine.is_allowed(operation)
345
+
346
+ if formatter.json_mode:
347
+ formatter.output(
348
+ {
349
+ "operation": operation,
350
+ "allowed": allowed,
351
+ }
352
+ )
353
+ else:
354
+ if allowed:
355
+ formatter.console.print(f"[green]ALLOWED[/green] {operation}")
356
+ else:
357
+ formatter.console.print(f"[red]DENIED[/red] {operation}")
358
+
359
+ if not allowed:
360
+ raise typer.Exit(code=EXIT_PERMISSION_DENIED) from None