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,968 @@
1
+ """Agent task commands -- CLI parity for the `/agents` REST surface.
2
+
3
+ Mirrors what ``kbagent serve --ui`` exposes: CRUD over scheduled tasks,
4
+ ad-hoc runs (blocking + streaming), run history, cron preview, and an
5
+ AI-assisted prompt helper. Reads/writes the same ``agents.json`` the
6
+ server scheduler uses, so a CLI-created task fires on cron as soon as
7
+ ``kbagent serve`` is running.
8
+
9
+ Thin layer: each command parses arguments, calls AgentService, formats
10
+ output. No business logic lives here. Async service methods are bridged
11
+ through ``asyncio.run`` at the command boundary so Typer stays sync.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import asyncio
17
+ import json
18
+ import sys
19
+ from collections.abc import AsyncIterator
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ import typer
24
+
25
+ from ..errors import ConfigError, ErrorCode
26
+ from ..output import OutputFormatter
27
+ from ..server.agents_store import AgentAction, Trigger
28
+ from ..services.agent_service import AgentService
29
+ from ._helpers import check_cli_permission, get_formatter, get_service
30
+
31
+ agent_app = typer.Typer(help="Scheduled agent tasks (cron / manual / chained)")
32
+
33
+
34
+ @agent_app.callback(invoke_without_command=True)
35
+ def _agent_permission_check(ctx: typer.Context) -> None:
36
+ check_cli_permission(ctx, "agent")
37
+
38
+
39
+ # ── Shared parsing helpers ────────────────────────────────────────────
40
+
41
+
42
+ def _read_payload(value: str, formatter: OutputFormatter) -> str:
43
+ """Resolve --input style strings: inline JSON, ``@file``, or ``-`` (stdin)."""
44
+ if value == "-":
45
+ return sys.stdin.read()
46
+ if value.startswith("@"):
47
+ path = Path(value[1:])
48
+ if not path.is_file():
49
+ formatter.error(
50
+ message=f"Input file not found: {path}",
51
+ error_code=ErrorCode.INVALID_ARGUMENT,
52
+ )
53
+ raise typer.Exit(code=2) from None
54
+ return path.read_text(encoding="utf-8")
55
+ return value
56
+
57
+
58
+ def _parse_json(value: str, formatter: OutputFormatter, *, label: str) -> dict[str, Any]:
59
+ """Decode a JSON string into a dict, exiting cleanly on bad JSON."""
60
+ try:
61
+ parsed = json.loads(value)
62
+ except json.JSONDecodeError as exc:
63
+ formatter.error(
64
+ message=f"Invalid JSON in {label}: {exc}",
65
+ error_code=ErrorCode.INVALID_FORMAT,
66
+ )
67
+ raise typer.Exit(code=2) from None
68
+ if not isinstance(parsed, dict):
69
+ formatter.error(
70
+ message=f"{label} must be a JSON object",
71
+ error_code=ErrorCode.INVALID_FORMAT,
72
+ )
73
+ raise typer.Exit(code=2) from None
74
+ return parsed
75
+
76
+
77
+ def _action_from_flags(
78
+ formatter: OutputFormatter,
79
+ *,
80
+ action_type: str | None,
81
+ from_file: str | None,
82
+ cli: str | None,
83
+ prompt: str | None,
84
+ extra_arg: list[str],
85
+ argv: list[str],
86
+ tool: str | None,
87
+ mcp_project: str | None,
88
+ mcp_branch: int | None,
89
+ input_payload: str | None,
90
+ timeout: int | None,
91
+ ) -> AgentAction:
92
+ """Build an AgentAction from CLI flags or ``--from-file PATH|-``.
93
+
94
+ ``--from-file`` wins outright -- the file is expected to contain the
95
+ full ``{type, params}`` envelope, mirroring the REST POST body. The
96
+ convenience flags are for the common "single ai_agent" / "single
97
+ cli_command" / "single mcp_tool" cases where typing a JSON file would
98
+ be overkill.
99
+ """
100
+ if from_file is not None:
101
+ raw = _read_payload(from_file, formatter)
102
+ payload = _parse_json(raw, formatter, label="--from-file")
103
+ try:
104
+ return AgentAction.model_validate(payload)
105
+ except Exception as exc:
106
+ formatter.error(
107
+ message=f"Invalid action payload: {exc}",
108
+ error_code=ErrorCode.VALIDATION_ERROR,
109
+ )
110
+ raise typer.Exit(code=2) from None
111
+
112
+ if action_type is None:
113
+ formatter.error(
114
+ message="--type is required (one of: ai_agent, cli_command, mcp_tool) "
115
+ "or pass --from-file PATH|- with a full {type, params} JSON.",
116
+ error_code=ErrorCode.MISSING_PARAMETER,
117
+ )
118
+ raise typer.Exit(code=2) from None
119
+
120
+ if action_type == "ai_agent":
121
+ if not cli or not prompt:
122
+ formatter.error(
123
+ message="ai_agent action requires --cli {claude|codex|gemini} and --prompt TEXT.",
124
+ error_code=ErrorCode.MISSING_PARAMETER,
125
+ )
126
+ raise typer.Exit(code=2) from None
127
+ params: dict[str, Any] = {"cli": cli, "prompt": prompt}
128
+ if extra_arg:
129
+ params["extra_args"] = list(extra_arg)
130
+ if timeout is not None:
131
+ params["timeout"] = timeout
132
+ return AgentAction(type="ai_agent", params=params)
133
+
134
+ if action_type == "cli_command":
135
+ if not argv:
136
+ formatter.error(
137
+ message="cli_command action requires --argv ARG (repeatable) -- e.g. "
138
+ "--argv job --argv list --argv --project=padak.",
139
+ error_code=ErrorCode.MISSING_PARAMETER,
140
+ )
141
+ raise typer.Exit(code=2) from None
142
+ params: dict[str, Any] = {"argv": list(argv)}
143
+ if timeout is not None:
144
+ params["timeout"] = timeout
145
+ return AgentAction(type="cli_command", params=params)
146
+
147
+ if action_type == "mcp_tool":
148
+ if not tool:
149
+ formatter.error(
150
+ message="mcp_tool action requires --tool NAME.",
151
+ error_code=ErrorCode.MISSING_PARAMETER,
152
+ )
153
+ raise typer.Exit(code=2) from None
154
+ params: dict[str, Any] = {"tool": tool}
155
+ if mcp_project:
156
+ params["project"] = mcp_project
157
+ if mcp_branch is not None:
158
+ params["branch_id"] = mcp_branch
159
+ if input_payload:
160
+ raw = _read_payload(input_payload, formatter)
161
+ params["input"] = _parse_json(raw, formatter, label="--input")
162
+ return AgentAction(type="mcp_tool", params=params)
163
+
164
+ formatter.error(
165
+ message=f"Unknown --type {action_type!r}; expected ai_agent|cli_command|mcp_tool.",
166
+ error_code=ErrorCode.INVALID_ARGUMENT,
167
+ )
168
+ raise typer.Exit(code=2) from None
169
+
170
+
171
+ def _trigger_from_flags(
172
+ trigger_task_id: str | None,
173
+ trigger_on: str,
174
+ ) -> Trigger | None:
175
+ """Build a Trigger from --trigger-task-id / --trigger-on flags."""
176
+ if not trigger_task_id:
177
+ return None
178
+ # ty: trigger_on is a plain str here; Trigger.on is a Literal[success|error|always].
179
+ # The value is constrained to that set by the CLI/REST boundary before reaching here.
180
+ return Trigger(on=trigger_on, task_id=trigger_task_id) # ty: ignore[invalid-argument-type]
181
+
182
+
183
+ def _resolve_id(
184
+ formatter: OutputFormatter,
185
+ positional: str | None,
186
+ option: str | None,
187
+ *,
188
+ label: str,
189
+ flag: str,
190
+ ) -> str:
191
+ """Resolve an ID passed either positionally or via a named flag.
192
+
193
+ Every agent subcommand accepts its task/run ID both ways: positionally
194
+ (``agent show TASK_ID``) for terse interactive use, and via a named flag
195
+ (``--id`` / ``--task-id`` / ``--run-id``) for consistency with the rest of
196
+ the CLI, which identifies entities by flag everywhere else (``--job-id``,
197
+ ``--config-id``, ``--app-id``, ...). Exactly one must be supplied; passing
198
+ both with conflicting values is a usage error rather than a silent pick.
199
+ """
200
+ if positional is not None and option is not None and positional != option:
201
+ formatter.error(
202
+ message=f"{label} given both positionally ({positional!r}) and via {flag} "
203
+ f"({option!r}) -- pass it only one way.",
204
+ error_code=ErrorCode.INVALID_ARGUMENT,
205
+ )
206
+ raise typer.Exit(code=2) from None
207
+ resolved = positional if positional is not None else option
208
+ if not resolved:
209
+ formatter.error(
210
+ message=f"{label} is required: pass it positionally or via {flag}.",
211
+ error_code=ErrorCode.MISSING_PARAMETER,
212
+ )
213
+ raise typer.Exit(code=2) from None
214
+ return resolved
215
+
216
+
217
+ # ── Output renderers ──────────────────────────────────────────────────
218
+
219
+
220
+ def _render_tasks_table(console: Any, data: dict[str, Any]) -> None:
221
+ """Plain-text table of tasks: id / name / cron / state."""
222
+ tasks = data.get("tasks") or []
223
+ if not tasks:
224
+ console.print("[dim]No agent tasks registered.[/dim]")
225
+ return
226
+ from rich.table import Table
227
+
228
+ table = Table(title="Agent Tasks", show_lines=False)
229
+ table.add_column("ID", style="cyan")
230
+ table.add_column("Name")
231
+ table.add_column("Schedule")
232
+ table.add_column("Type")
233
+ table.add_column("State")
234
+ table.add_column("Last run", style="dim")
235
+ table.add_column("Next run", style="dim")
236
+ for task in tasks:
237
+ state_bits = []
238
+ if not task.get("enabled", True):
239
+ state_bits.append("[yellow]disabled[/yellow]")
240
+ if task.get("manual"):
241
+ state_bits.append("[blue]manual[/blue]")
242
+ elif task.get("enabled", True):
243
+ state_bits.append("[green]enabled[/green]")
244
+ state = " ".join(state_bits) or "-"
245
+ action = task.get("action") or {}
246
+ table.add_row(
247
+ str(task.get("id", "")),
248
+ task.get("name", ""),
249
+ "" if task.get("manual") else task.get("cron", "") or "-",
250
+ action.get("type", "?"),
251
+ state,
252
+ task.get("last_run_at") or "-",
253
+ task.get("next_run_at") or "-",
254
+ )
255
+ console.print(table)
256
+
257
+
258
+ def _render_task_detail(console: Any, task: dict[str, Any]) -> None:
259
+ """Pretty single-task panel with the action payload pretty-printed."""
260
+ from rich.panel import Panel
261
+ from rich.syntax import Syntax
262
+
263
+ header_lines = [
264
+ f"[cyan]{task.get('id', '')}[/cyan] [bold]{task.get('name', '')}[/bold]",
265
+ ]
266
+ if task.get("description"):
267
+ header_lines.append(task["description"])
268
+ state_bits = []
269
+ if not task.get("enabled", True):
270
+ state_bits.append("[yellow]disabled[/yellow]")
271
+ if task.get("manual"):
272
+ state_bits.append("[blue]manual[/blue]")
273
+ else:
274
+ state_bits.append(f"cron=[green]{task.get('cron', '')}[/green]")
275
+ header_lines.append("State: " + " ".join(state_bits))
276
+ if task.get("last_run_at"):
277
+ header_lines.append(f"Last run: [dim]{task['last_run_at']}[/dim]")
278
+ if task.get("next_run_at"):
279
+ header_lines.append(f"Next run: [dim]{task['next_run_at']}[/dim]")
280
+ if task.get("trigger"):
281
+ trig = task["trigger"]
282
+ header_lines.append(
283
+ f"Trigger: on=[magenta]{trig.get('on')}[/magenta] -> {trig.get('task_id')}"
284
+ )
285
+ action_json = json.dumps(task.get("action") or {}, indent=2, ensure_ascii=False)
286
+ console.print(Panel("\n".join(header_lines), title="Task", border_style="blue"))
287
+ console.print(Syntax(action_json, "json", theme="ansi_dark", word_wrap=True))
288
+
289
+
290
+ def _render_created_task(console: Any, task: dict[str, Any]) -> None:
291
+ """Confirmation line + full detail panel after `agent create`."""
292
+ console.print(f"[bold green]Created[/bold green] task [cyan]{task['id']}[/cyan]")
293
+ _render_task_detail(console, task)
294
+
295
+
296
+ def _render_updated_task(console: Any, task: dict[str, Any]) -> None:
297
+ """Confirmation line + full detail panel after `agent update`."""
298
+ console.print(f"[bold green]Updated[/bold green] task [cyan]{task['id']}[/cyan]")
299
+ _render_task_detail(console, task)
300
+
301
+
302
+ def _render_deleted_task(console: Any, data: dict[str, Any]) -> None:
303
+ """Confirmation line after `agent delete`."""
304
+ console.print(f"[bold green]Deleted[/bold green] task [cyan]{data['id']}[/cyan]")
305
+
306
+
307
+ def _render_run_result(console: Any, run: dict[str, Any]) -> None:
308
+ """One-line run status + timing after `agent run`."""
309
+ status = run.get("status")
310
+ status_styled = f"[green]{status}[/green]" if status == "ok" else f"[red]{status}[/red]"
311
+ console.print(f"[bold]Run[/bold] [cyan]{run['run_id']}[/cyan] status={status_styled}")
312
+ console.print(f"started: [dim]{run['started_at']}[/dim]")
313
+ console.print(f"ended: [dim]{run.get('ended_at') or '-'}[/dim]")
314
+ if run.get("error"):
315
+ console.print(f"[red]error:[/red] {run['error']}")
316
+
317
+
318
+ def _render_test_result(console: Any, run: dict[str, Any]) -> None:
319
+ """Ad-hoc `agent test` preview: status + optional output / error."""
320
+ console.print(f"[bold]Preview[/bold] status={run.get('status')}")
321
+ if run.get("output"):
322
+ console.print(json.dumps(run["output"], indent=2, ensure_ascii=False))
323
+ if run.get("error"):
324
+ console.print(f"[red]error:[/red] {run['error']}")
325
+
326
+
327
+ def _render_improved_prompt(console: Any, data: dict[str, Any]) -> None:
328
+ """`agent prompt-improve` result: header + the cleaned prompt body."""
329
+ console.print(f"[bold]Cleaned prompt[/bold] (status={data.get('status', '?')}):")
330
+ console.print(data.get("prompt") or "[dim]<empty>[/dim]")
331
+
332
+
333
+ def _render_runs_table(console: Any, data: dict[str, Any]) -> None:
334
+ runs = data.get("runs") or []
335
+ if not runs:
336
+ console.print("[dim]No run history.[/dim]")
337
+ return
338
+ from rich.table import Table
339
+
340
+ table = Table(title="Run history", show_lines=False)
341
+ table.add_column("Run ID", style="cyan")
342
+ table.add_column("Started", style="dim")
343
+ table.add_column("Ended", style="dim")
344
+ table.add_column("Status")
345
+ table.add_column("Summary")
346
+ for run in runs:
347
+ status = run.get("status", "?")
348
+ status_styled = (
349
+ f"[green]{status}[/green]"
350
+ if status == "ok"
351
+ else f"[red]{status}[/red]"
352
+ if status == "error"
353
+ else f"[yellow]{status}[/yellow]"
354
+ )
355
+ summary = ""
356
+ if run.get("error"):
357
+ summary = f"[red]{run['error'][:80]}[/red]"
358
+ elif run.get("summary"):
359
+ s = run["summary"]
360
+ bits = []
361
+ if s.get("model"):
362
+ bits.append(str(s["model"]))
363
+ if s.get("tokens"):
364
+ bits.append(f"tokens={s['tokens']}")
365
+ if s.get("cost_usd") is not None:
366
+ bits.append(f"${s['cost_usd']:.4f}")
367
+ summary = " ".join(bits)
368
+ table.add_row(
369
+ run.get("run_id", ""),
370
+ run.get("started_at") or "",
371
+ run.get("ended_at") or "-",
372
+ status_styled,
373
+ summary,
374
+ )
375
+ console.print(table)
376
+
377
+
378
+ def _render_stream_event(formatter: OutputFormatter, evt: dict[str, Any]) -> None:
379
+ """Single event renderer for ``--stream`` mode.
380
+
381
+ JSON: one NDJSON line per event (the same shape SSE consumers see).
382
+ Human: short labeled line per event; the ``done`` event gets a
383
+ multi-line summary with exit code, elapsed time, response preview.
384
+ """
385
+ if formatter.json_mode:
386
+ sys.stdout.write(json.dumps(evt, ensure_ascii=False) + "\n")
387
+ sys.stdout.flush()
388
+ return
389
+ event = evt.get("event", "?")
390
+ data = evt.get("data") or {}
391
+ console = formatter.console
392
+ if event == "init":
393
+ info_bits = ["[bold blue]Init[/bold blue]"]
394
+ for key in ("name", "task_id", "action_type", "cli"):
395
+ if data.get(key):
396
+ info_bits.append(f"{key}=[cyan]{data[key]}[/cyan]")
397
+ console.print(" ".join(info_bits))
398
+ return
399
+ if event == "stdout":
400
+ # claude JSONL: try to extract the most useful field
401
+ if isinstance(data, dict) and "type" in data and "raw" not in data:
402
+ kind = data.get("type")
403
+ if kind == "assistant":
404
+ content = data.get("message", {}).get("content") or []
405
+ texts = [
406
+ c.get("text", "")
407
+ for c in content
408
+ if isinstance(c, dict) and c.get("type") == "text"
409
+ ]
410
+ if texts:
411
+ console.print(f"[bold]assistant:[/bold] {' '.join(texts)}")
412
+ else:
413
+ tool_uses = [
414
+ c.get("name")
415
+ for c in content
416
+ if isinstance(c, dict) and c.get("type") == "tool_use"
417
+ ]
418
+ if tool_uses:
419
+ console.print(
420
+ f"[magenta]tool_use:[/magenta] {', '.join(filter(None, tool_uses))}"
421
+ )
422
+ else:
423
+ console.print("[dim]event=assistant (no text)[/dim]")
424
+ elif kind == "result":
425
+ # final result line carries the summary text
426
+ pass
427
+ elif kind:
428
+ console.print(f"[dim]event={kind}[/dim]")
429
+ return
430
+ # codex/gemini raw lines or unparseable json
431
+ raw = data.get("raw") if isinstance(data, dict) else None
432
+ if raw:
433
+ console.print(raw)
434
+ return
435
+ if event == "stderr":
436
+ raw = data.get("raw") if isinstance(data, dict) else str(data)
437
+ console.print(f"[dim red]{raw}[/dim red]")
438
+ return
439
+ if event == "done":
440
+ status = data.get("status", "?")
441
+ status_styled = (
442
+ "[green]ok[/green]"
443
+ if status == "ok"
444
+ else "[red]error[/red]"
445
+ if status == "error"
446
+ else f"[yellow]{status}[/yellow]"
447
+ )
448
+ bits = [f"[bold]Done[/bold] status={status_styled}"]
449
+ if data.get("exit_code") is not None:
450
+ bits.append(f"exit_code={data['exit_code']}")
451
+ if data.get("elapsed_seconds") is not None:
452
+ bits.append(f"elapsed={data['elapsed_seconds']}s")
453
+ console.print(" ".join(bits))
454
+ if data.get("error"):
455
+ console.print(f"[red]error:[/red] {data['error']}")
456
+ if data.get("response"):
457
+ preview = str(data["response"])[:500]
458
+ console.print(f"[dim]response preview:[/dim] {preview}")
459
+ return
460
+ # Unknown event type; dump as-is
461
+ console.print(f"[dim]{event}:[/dim] {json.dumps(data, ensure_ascii=False)[:200]}")
462
+
463
+
464
+ def _stream_to_stdout(formatter: OutputFormatter, agen: AsyncIterator[dict[str, Any]]) -> None:
465
+ """Drive an async event generator from sync code and render each event."""
466
+
467
+ async def _drive() -> None:
468
+ async for evt in agen:
469
+ _render_stream_event(formatter, evt)
470
+
471
+ try:
472
+ asyncio.run(_drive())
473
+ except KeyboardInterrupt:
474
+ # The async generator's finally block kills any spawned subprocess.
475
+ # Print a short marker so the user knows the partial output is theirs.
476
+ if not formatter.json_mode:
477
+ formatter.console.print("[yellow]Interrupted by user.[/yellow]")
478
+ raise typer.Exit(code=130) from None
479
+
480
+
481
+ # ── Commands ───────────────────────────────────────────────────────────
482
+
483
+
484
+ @agent_app.command("list")
485
+ def agent_list(ctx: typer.Context) -> None:
486
+ """List all registered agent tasks."""
487
+ formatter = get_formatter(ctx)
488
+ service: AgentService = get_service(ctx, "agent_service")
489
+ try:
490
+ tasks = service.list_tasks()
491
+ except ConfigError as exc:
492
+ formatter.error(message=exc.message, error_code=ErrorCode.CONFIG_ERROR)
493
+ raise typer.Exit(code=5) from None
494
+ payload = {"tasks": [t.model_dump(mode="json") for t in tasks]}
495
+ formatter.output(payload, _render_tasks_table)
496
+
497
+
498
+ @agent_app.command("show")
499
+ def agent_show(
500
+ ctx: typer.Context,
501
+ task_id: str | None = typer.Argument(None, help="Task ID (12-char hex). Or use --id."),
502
+ task_id_opt: str | None = typer.Option(
503
+ None, "--id", "--task-id", help="Task ID (alias for the positional argument)."
504
+ ),
505
+ ) -> None:
506
+ """Show one task's full configuration."""
507
+ formatter = get_formatter(ctx)
508
+ service: AgentService = get_service(ctx, "agent_service")
509
+ task_id = _resolve_id(formatter, task_id, task_id_opt, label="Task ID", flag="--id/--task-id")
510
+ try:
511
+ task = service.get_task(task_id)
512
+ except ConfigError as exc:
513
+ formatter.error(message=exc.message, error_code=ErrorCode.NOT_FOUND)
514
+ raise typer.Exit(code=1) from None
515
+ formatter.output(task.model_dump(mode="json"), _render_task_detail)
516
+
517
+
518
+ @agent_app.command("create")
519
+ def agent_create(
520
+ ctx: typer.Context,
521
+ name: str = typer.Option(..., "--name", help="Human-readable task name"),
522
+ description: str = typer.Option("", "--description", help="Free-form description"),
523
+ cron: str = typer.Option("0 * * * *", "--cron", help="Cron expression (UTC)"),
524
+ manual: bool = typer.Option(
525
+ False,
526
+ "--manual",
527
+ help="Skip cron firing -- only run when triggered manually or as downstream.",
528
+ ),
529
+ enabled: bool = typer.Option(True, "--enabled/--disabled", help="Initial enabled state."),
530
+ action_type: str | None = typer.Option(
531
+ None,
532
+ "--type",
533
+ help="Action type when not using --from-file: ai_agent|cli_command|mcp_tool",
534
+ ),
535
+ from_file: str | None = typer.Option(
536
+ None,
537
+ "--from-file",
538
+ help='Full action JSON ({"type": "...", "params": {...}}). PATH, @path, or - for stdin.',
539
+ ),
540
+ cli: str | None = typer.Option(None, "--cli", help="ai_agent: claude|codex|gemini"),
541
+ prompt: str | None = typer.Option(None, "--prompt", help="ai_agent: prompt body"),
542
+ extra_arg: list[str] = typer.Option(
543
+ [],
544
+ "--extra-arg",
545
+ help="ai_agent: extra CLI arg (repeatable). Forwarded to claude/codex/gemini.",
546
+ ),
547
+ argv: list[str] = typer.Option(
548
+ [],
549
+ "--argv",
550
+ help="cli_command: argv element (repeatable). 'kbagent' prefix is auto-added.",
551
+ ),
552
+ tool: str | None = typer.Option(None, "--tool", help="mcp_tool: tool name (e.g. get_jobs)"),
553
+ mcp_project: str | None = typer.Option(
554
+ None, "--mcp-project", help="mcp_tool: project alias to dispatch into."
555
+ ),
556
+ mcp_branch: int | None = typer.Option(
557
+ None, "--mcp-branch", help="mcp_tool: branch ID (optional)."
558
+ ),
559
+ input_payload: str | None = typer.Option(
560
+ None,
561
+ "--input",
562
+ help="mcp_tool: JSON input. Inline, @path, or -.",
563
+ ),
564
+ timeout: int | None = typer.Option(None, "--timeout", help="Action timeout in seconds."),
565
+ trigger_task_id: str | None = typer.Option(
566
+ None, "--trigger-task-id", help="Chain: ID of downstream task to fire after this one."
567
+ ),
568
+ trigger_on: str = typer.Option(
569
+ "success",
570
+ "--trigger-on",
571
+ help="Chain filter: success|error|always.",
572
+ ),
573
+ ) -> None:
574
+ """Register a new scheduled task.
575
+
576
+ Two ways to specify the action:
577
+ 1. ``--from-file PATH|-`` with the full {type, params} JSON envelope.
578
+ 2. ``--type TYPE`` plus the type-specific flags (ai_agent: --cli/--prompt,
579
+ cli_command: --argv ..., mcp_tool: --tool/--input/--mcp-project).
580
+ """
581
+ formatter = get_formatter(ctx)
582
+ service: AgentService = get_service(ctx, "agent_service")
583
+ action = _action_from_flags(
584
+ formatter,
585
+ action_type=action_type,
586
+ from_file=from_file,
587
+ cli=cli,
588
+ prompt=prompt,
589
+ extra_arg=list(extra_arg),
590
+ argv=list(argv),
591
+ tool=tool,
592
+ mcp_project=mcp_project,
593
+ mcp_branch=mcp_branch,
594
+ input_payload=input_payload,
595
+ timeout=timeout,
596
+ )
597
+ trigger = _trigger_from_flags(trigger_task_id, trigger_on)
598
+ try:
599
+ task = service.create_task(
600
+ name=name,
601
+ action=action,
602
+ description=description,
603
+ cron=cron,
604
+ manual=manual,
605
+ enabled=enabled,
606
+ trigger=trigger,
607
+ )
608
+ except ConfigError as exc:
609
+ formatter.error(message=exc.message, error_code=ErrorCode.CONFIG_ERROR)
610
+ raise typer.Exit(code=5) from None
611
+ formatter.output(task.model_dump(mode="json"), _render_created_task)
612
+
613
+
614
+ @agent_app.command("update")
615
+ def agent_update(
616
+ ctx: typer.Context,
617
+ task_id: str | None = typer.Argument(None, help="Task ID to update. Or use --id."),
618
+ task_id_opt: str | None = typer.Option(
619
+ None, "--id", "--task-id", help="Task ID (alias for the positional argument)."
620
+ ),
621
+ name: str | None = typer.Option(None, "--name"),
622
+ description: str | None = typer.Option(None, "--description"),
623
+ cron: str | None = typer.Option(None, "--cron"),
624
+ enabled: bool | None = typer.Option(
625
+ None, "--enabled/--disabled", help="Toggle scheduler firing."
626
+ ),
627
+ manual: bool | None = typer.Option(
628
+ None,
629
+ "--manual/--auto",
630
+ help="--manual disables cron loop; --auto re-enables it (and recomputes next_run_at).",
631
+ ),
632
+ clear_trigger: bool = typer.Option(
633
+ False, "--clear-trigger", help="Remove any chained downstream trigger."
634
+ ),
635
+ trigger_task_id: str | None = typer.Option(
636
+ None, "--trigger-task-id", help="Set/replace the downstream chain target."
637
+ ),
638
+ trigger_on: str = typer.Option(
639
+ "success", "--trigger-on", help="Chain filter when --trigger-task-id is set."
640
+ ),
641
+ ) -> None:
642
+ """Patch one or more fields on a task. Omitted flags leave the field unchanged."""
643
+ formatter = get_formatter(ctx)
644
+ service: AgentService = get_service(ctx, "agent_service")
645
+ task_id = _resolve_id(formatter, task_id, task_id_opt, label="Task ID", flag="--id/--task-id")
646
+ trigger = _trigger_from_flags(trigger_task_id, trigger_on)
647
+ try:
648
+ task = service.update_task(
649
+ task_id,
650
+ name=name,
651
+ description=description,
652
+ cron=cron,
653
+ manual=manual,
654
+ enabled=enabled,
655
+ trigger=trigger,
656
+ clear_trigger=clear_trigger,
657
+ )
658
+ except ConfigError as exc:
659
+ formatter.error(message=exc.message, error_code=ErrorCode.CONFIG_ERROR)
660
+ raise typer.Exit(code=5) from None
661
+ formatter.output(task.model_dump(mode="json"), _render_updated_task)
662
+
663
+
664
+ @agent_app.command("delete")
665
+ def agent_delete(
666
+ ctx: typer.Context,
667
+ task_id: str | None = typer.Argument(None, help="Task ID to delete. Or use --id."),
668
+ task_id_opt: str | None = typer.Option(
669
+ None, "--id", "--task-id", help="Task ID (alias for the positional argument)."
670
+ ),
671
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
672
+ ) -> None:
673
+ """Remove a task. Run history on disk is preserved.
674
+
675
+ Disabled tasks should be deleted with this command (not just disabled),
676
+ when they will never run again -- a long history of disabled tasks
677
+ clutters the UI / list output.
678
+ """
679
+ formatter = get_formatter(ctx)
680
+ service: AgentService = get_service(ctx, "agent_service")
681
+ task_id = _resolve_id(formatter, task_id, task_id_opt, label="Task ID", flag="--id/--task-id")
682
+ if not yes and not formatter.json_mode:
683
+ confirm = typer.confirm(f"Delete task '{task_id}'?")
684
+ if not confirm:
685
+ formatter.console.print("[yellow]Aborted.[/yellow]")
686
+ raise typer.Exit(code=0)
687
+ try:
688
+ service.delete_task(task_id)
689
+ except ConfigError as exc:
690
+ formatter.error(message=exc.message, error_code=ErrorCode.NOT_FOUND)
691
+ raise typer.Exit(code=1) from None
692
+ formatter.output({"status": "deleted", "id": task_id}, _render_deleted_task)
693
+
694
+
695
+ @agent_app.command("run")
696
+ def agent_run(
697
+ ctx: typer.Context,
698
+ task_id: str | None = typer.Argument(None, help="Task ID to run. Or use --id."),
699
+ task_id_opt: str | None = typer.Option(
700
+ None, "--id", "--task-id", help="Task ID (alias for the positional argument)."
701
+ ),
702
+ stream: bool = typer.Option(
703
+ False,
704
+ "--stream",
705
+ help="Stream events live (each event on its own line / NDJSON in --json mode).",
706
+ ),
707
+ runtime_prompt: str | None = typer.Option(
708
+ None,
709
+ "--runtime-prompt",
710
+ help="ai_agent: ad-hoc text appended to the persisted prompt for this run only.",
711
+ ),
712
+ runtime_input: str | None = typer.Option(
713
+ None,
714
+ "--runtime-input",
715
+ help="JSON input merged into the action params for this run only. Inline, @path, or -.",
716
+ ),
717
+ ) -> None:
718
+ """Trigger a task immediately (does not wait for the next cron firing).
719
+
720
+ By default blocks until the run finishes and prints the AgentRun
721
+ record. Use ``--stream`` to render live events as they arrive (one
722
+ line per event in human mode, NDJSON in --json mode).
723
+
724
+ ``--runtime-prompt`` is a shortcut for ai_agent tasks (most common
725
+ use case for manual tasks). For full control over the runtime merge,
726
+ pass ``--runtime-input '{"key": "value"}'``.
727
+ """
728
+ formatter = get_formatter(ctx)
729
+ service: AgentService = get_service(ctx, "agent_service")
730
+ task_id = _resolve_id(formatter, task_id, task_id_opt, label="Task ID", flag="--id/--task-id")
731
+ runtime: dict[str, Any] | None = None
732
+ if runtime_input:
733
+ raw = _read_payload(runtime_input, formatter)
734
+ runtime = _parse_json(raw, formatter, label="--runtime-input")
735
+ if runtime_prompt:
736
+ runtime = dict(runtime or {})
737
+ runtime["prompt"] = runtime_prompt
738
+ try:
739
+ if stream:
740
+ _stream_to_stdout(formatter, service.stream_run(task_id, runtime_input=runtime))
741
+ return
742
+ run = asyncio.run(service.run_task(task_id, runtime_input=runtime))
743
+ except ConfigError as exc:
744
+ formatter.error(message=exc.message, error_code=ErrorCode.NOT_FOUND)
745
+ raise typer.Exit(code=1) from None
746
+ formatter.output(run.model_dump(mode="json"), _render_run_result)
747
+
748
+
749
+ @agent_app.command("runs")
750
+ def agent_runs(
751
+ ctx: typer.Context,
752
+ task_id: str | None = typer.Argument(None, help="Task ID whose history to show. Or use --id."),
753
+ task_id_opt: str | None = typer.Option(
754
+ None, "--id", "--task-id", help="Task ID (alias for the positional argument)."
755
+ ),
756
+ limit: int = typer.Option(50, "--limit", help="Max rows to return."),
757
+ ) -> None:
758
+ """Show the run history of a task (most recent first)."""
759
+ formatter = get_formatter(ctx)
760
+ service: AgentService = get_service(ctx, "agent_service")
761
+ task_id = _resolve_id(formatter, task_id, task_id_opt, label="Task ID", flag="--id/--task-id")
762
+ try:
763
+ runs = service.list_runs(task_id, limit=limit)
764
+ except ConfigError as exc:
765
+ formatter.error(message=exc.message, error_code=ErrorCode.NOT_FOUND)
766
+ raise typer.Exit(code=1) from None
767
+ payload = {"runs": [r.model_dump(mode="json") for r in runs]}
768
+ formatter.output(payload, _render_runs_table)
769
+
770
+
771
+ @agent_app.command("run-detail")
772
+ def agent_run_detail(
773
+ ctx: typer.Context,
774
+ task_id: str | None = typer.Argument(None, help="Task ID. Or use --id/--task-id."),
775
+ run_id: str | None = typer.Argument(None, help="Run ID (12-char hex). Or use --run-id."),
776
+ task_id_opt: str | None = typer.Option(
777
+ None, "--id", "--task-id", help="Task ID (alias for the positional argument)."
778
+ ),
779
+ run_id_opt: str | None = typer.Option(
780
+ None, "--run-id", help="Run ID (alias for the positional argument)."
781
+ ),
782
+ ) -> None:
783
+ """Show a single AgentRun record (status, summary, output, error)."""
784
+ formatter = get_formatter(ctx)
785
+ service: AgentService = get_service(ctx, "agent_service")
786
+ task_id = _resolve_id(formatter, task_id, task_id_opt, label="Task ID", flag="--id/--task-id")
787
+ run_id = _resolve_id(formatter, run_id, run_id_opt, label="Run ID", flag="--run-id")
788
+ try:
789
+ run = service.get_run(task_id, run_id)
790
+ except ConfigError as exc:
791
+ formatter.error(message=exc.message, error_code=ErrorCode.NOT_FOUND)
792
+ raise typer.Exit(code=1) from None
793
+
794
+ def _render(c: Any, d: dict[str, Any]) -> None:
795
+ from rich.syntax import Syntax
796
+
797
+ c.print(f"[bold]Run[/bold] [cyan]{d['run_id']}[/cyan] status={d.get('status', '?')}")
798
+ c.print(f"task_id: {d.get('task_id')}")
799
+ c.print(f"started: [dim]{d.get('started_at')}[/dim]")
800
+ c.print(f"ended: [dim]{d.get('ended_at') or '-'}[/dim]")
801
+ if d.get("error"):
802
+ c.print(f"[red]error:[/red] {d['error']}")
803
+ if d.get("summary"):
804
+ c.print(Syntax(json.dumps(d["summary"], indent=2), "json", theme="ansi_dark"))
805
+ if d.get("output"):
806
+ c.print("[bold]output:[/bold]")
807
+ c.print(Syntax(json.dumps(d["output"], indent=2), "json", theme="ansi_dark"))
808
+
809
+ formatter.output(run.model_dump(mode="json"), _render)
810
+
811
+
812
+ @agent_app.command("run-events")
813
+ def agent_run_events(
814
+ ctx: typer.Context,
815
+ task_id: str | None = typer.Argument(None, help="Task ID. Or use --id/--task-id."),
816
+ run_id: str | None = typer.Argument(None, help="Run ID. Or use --run-id."),
817
+ task_id_opt: str | None = typer.Option(
818
+ None, "--id", "--task-id", help="Task ID (alias for the positional argument)."
819
+ ),
820
+ run_id_opt: str | None = typer.Option(
821
+ None, "--run-id", help="Run ID (alias for the positional argument)."
822
+ ),
823
+ ) -> None:
824
+ """Replay the persisted event timeline of an ai_agent run (line-by-line)."""
825
+ formatter = get_formatter(ctx)
826
+ service: AgentService = get_service(ctx, "agent_service")
827
+ task_id = _resolve_id(formatter, task_id, task_id_opt, label="Task ID", flag="--id/--task-id")
828
+ run_id = _resolve_id(formatter, run_id, run_id_opt, label="Run ID", flag="--run-id")
829
+ try:
830
+ events = service.get_run_events(task_id, run_id)
831
+ except ConfigError as exc:
832
+ formatter.error(message=exc.message, error_code=ErrorCode.NOT_FOUND)
833
+ raise typer.Exit(code=1) from None
834
+ if formatter.json_mode:
835
+ formatter.output({"events": events, "count": len(events)})
836
+ return
837
+ for evt in events:
838
+ _render_stream_event(formatter, evt)
839
+
840
+
841
+ @agent_app.command("test")
842
+ def agent_test(
843
+ ctx: typer.Context,
844
+ name: str = typer.Option("[preview]", "--name", help="Name shown in event init payload."),
845
+ stream: bool = typer.Option(
846
+ False, "--stream", help="Stream events live instead of returning the final run record."
847
+ ),
848
+ action_type: str | None = typer.Option(None, "--type", help="ai_agent|cli_command|mcp_tool"),
849
+ from_file: str | None = typer.Option(None, "--from-file", help="Action JSON (or @path / -)."),
850
+ cli: str | None = typer.Option(None, "--cli"),
851
+ prompt: str | None = typer.Option(None, "--prompt"),
852
+ extra_arg: list[str] = typer.Option([], "--extra-arg"),
853
+ argv: list[str] = typer.Option([], "--argv"),
854
+ tool: str | None = typer.Option(None, "--tool"),
855
+ mcp_project: str | None = typer.Option(None, "--mcp-project"),
856
+ mcp_branch: int | None = typer.Option(None, "--mcp-branch"),
857
+ input_payload: str | None = typer.Option(None, "--input"),
858
+ timeout: int | None = typer.Option(None, "--timeout"),
859
+ ) -> None:
860
+ """Execute an action ad-hoc (no persistence, no scheduling).
861
+
862
+ Exact dispatch logic as the cron scheduler -- useful for sanity-checking
863
+ a prompt / tool / cli_command before saving a task.
864
+ """
865
+ formatter = get_formatter(ctx)
866
+ service: AgentService = get_service(ctx, "agent_service")
867
+ action = _action_from_flags(
868
+ formatter,
869
+ action_type=action_type,
870
+ from_file=from_file,
871
+ cli=cli,
872
+ prompt=prompt,
873
+ extra_arg=list(extra_arg),
874
+ argv=list(argv),
875
+ tool=tool,
876
+ mcp_project=mcp_project,
877
+ mcp_branch=mcp_branch,
878
+ input_payload=input_payload,
879
+ timeout=timeout,
880
+ )
881
+ if stream:
882
+ _stream_to_stdout(formatter, service.stream_test_action(action, name=name))
883
+ return
884
+ run = asyncio.run(service.test_action(action, name=name))
885
+ formatter.output(run.model_dump(mode="json"), _render_test_result)
886
+
887
+
888
+ @agent_app.command("cron-preview")
889
+ def agent_cron_preview(
890
+ ctx: typer.Context,
891
+ cron: str = typer.Option(..., "--cron", help="Cron expression to evaluate."),
892
+ count: int = typer.Option(5, "--count", help="How many firings to return (1-20)."),
893
+ ) -> None:
894
+ """Show the next N firings of a cron expression.
895
+
896
+ Useful when authoring a task: paste the cron, eyeball the next few
897
+ times, then save. Works offline (no network calls).
898
+ """
899
+ formatter = get_formatter(ctx)
900
+ service: AgentService = get_service(ctx, "agent_service")
901
+ try:
902
+ firings = service.cron_preview(cron, count=count)
903
+ except ConfigError as exc:
904
+ formatter.error(message=exc.message, error_code=ErrorCode.VALIDATION_ERROR)
905
+ raise typer.Exit(code=2) from None
906
+
907
+ def _render(c: Any, d: dict[str, Any]) -> None:
908
+ c.print(f"[bold]Cron[/bold] [cyan]{d['cron']}[/cyan]")
909
+ for ts in d["firings"]:
910
+ c.print(f" - [dim]{ts}[/dim]")
911
+
912
+ formatter.output({"cron": cron, "firings": firings}, _render)
913
+
914
+
915
+ @agent_app.command("prompt-improve")
916
+ def agent_prompt_improve(
917
+ ctx: typer.Context,
918
+ goal: str = typer.Option(..., "--goal", help="Plain-English goal to polish."),
919
+ draft: str = typer.Option("", "--draft", help="Optional half-baked prompt to refine."),
920
+ cli: str = typer.Option("claude", "--cli", help="AI CLI to invoke: claude|codex|gemini."),
921
+ project: str | None = typer.Option(None, "--project", help="Pinned project alias hint."),
922
+ extra_arg: list[str] = typer.Option([], "--extra-arg", help="Extra args for the AI CLI."),
923
+ stream: bool = typer.Option(
924
+ True, "--stream/--no-stream", help="Stream events as they arrive (default: on)."
925
+ ),
926
+ ) -> None:
927
+ """Polish a plain-English goal into an unattended-agent-ready prompt.
928
+
929
+ Spawns the chosen AI CLI exactly the way an ai_agent run does, with a
930
+ meta-prompt that asks for a single polished prompt body. The final
931
+ ``done`` event carries the cleaned prompt under ``data.prompt``.
932
+ """
933
+ formatter = get_formatter(ctx)
934
+ service: AgentService = get_service(ctx, "agent_service")
935
+
936
+ async def _drive_no_stream() -> dict[str, Any] | None:
937
+ last_done: dict[str, Any] | None = None
938
+ async for evt in service.improve_prompt(
939
+ cli=cli, goal=goal, draft=draft, project=project, extra_args=list(extra_arg)
940
+ ):
941
+ if evt.get("event") == "done":
942
+ last_done = evt.get("data")
943
+ return last_done
944
+
945
+ try:
946
+ if stream:
947
+ _stream_to_stdout(
948
+ formatter,
949
+ service.improve_prompt(
950
+ cli=cli,
951
+ goal=goal,
952
+ draft=draft,
953
+ project=project,
954
+ extra_args=list(extra_arg),
955
+ ),
956
+ )
957
+ return
958
+ final = asyncio.run(_drive_no_stream())
959
+ except ConfigError as exc:
960
+ formatter.error(message=exc.message, error_code=ErrorCode.VALIDATION_ERROR)
961
+ raise typer.Exit(code=2) from None
962
+ if final is None:
963
+ formatter.error(
964
+ message="Prompt helper produced no output.",
965
+ error_code=ErrorCode.UNKNOWN_ERROR,
966
+ )
967
+ raise typer.Exit(code=1) from None
968
+ formatter.output(final, _render_improved_prompt)