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.
- keboola_agent_cli/__init__.py +34 -0
- keboola_agent_cli/__main__.py +5 -0
- keboola_agent_cli/_ui_dist/assets/arc-DhFYIddx.js +2 -0
- keboola_agent_cli/_ui_dist/assets/arc-DhFYIddx.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/architecture-7EHR7CIX-hNCijx_H.js +1 -0
- keboola_agent_cli/_ui_dist/assets/architectureDiagram-3BPJPVTR-C6hUlprM.js +37 -0
- keboola_agent_cli/_ui_dist/assets/architectureDiagram-3BPJPVTR-C6hUlprM.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/array-BifhSqXX.js +2 -0
- keboola_agent_cli/_ui_dist/assets/array-BifhSqXX.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/blockDiagram-GPEHLZMM-DC7qY9i4.js +133 -0
- keboola_agent_cli/_ui_dist/assets/blockDiagram-GPEHLZMM-DC7qY9i4.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/c4Diagram-AAUBKEIU-5Lh44evt.js +11 -0
- keboola_agent_cli/_ui_dist/assets/c4Diagram-AAUBKEIU-5Lh44evt.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/channel-DBMrXlxx.js +2 -0
- keboola_agent_cli/_ui_dist/assets/channel-DBMrXlxx.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-2J33WTMH-Coy82EBh.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-2J33WTMH-Coy82EBh.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-3OPIFGDE-BQC5CRHI.js +63 -0
- keboola_agent_cli/_ui_dist/assets/chunk-3OPIFGDE-BQC5CRHI.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-4BX2VUAB-DUuEt70o.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-4BX2VUAB-DUuEt70o.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-55IACEB6-BvR-6chF.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-55IACEB6-BvR-6chF.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-5ZQYHXKU-BjcTN7ul.js +3 -0
- keboola_agent_cli/_ui_dist/assets/chunk-5ZQYHXKU-BjcTN7ul.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-727SXJPM-C0zxqqRN.js +207 -0
- keboola_agent_cli/_ui_dist/assets/chunk-727SXJPM-C0zxqqRN.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-AQP2D5EJ-CXf7rIlZ.js +232 -0
- keboola_agent_cli/_ui_dist/assets/chunk-AQP2D5EJ-CXf7rIlZ.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-BSJP7CBP-Oj_FO9Q7.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-BSJP7CBP-Oj_FO9Q7.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-CSCIHK7Q-CcTsLrFc.js +124 -0
- keboola_agent_cli/_ui_dist/assets/chunk-CSCIHK7Q-CcTsLrFc.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-FMBD7UC4-FH-zLkkW.js +16 -0
- keboola_agent_cli/_ui_dist/assets/chunk-FMBD7UC4-FH-zLkkW.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-L5ZTLDWV-B1Ky_e7O.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-L5ZTLDWV-B1Ky_e7O.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-ND2GUHAM-BHz1rpbm.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-ND2GUHAM-BHz1rpbm.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-NNHCCRGN-DlpIbxXb.js +160 -0
- keboola_agent_cli/_ui_dist/assets/chunk-NNHCCRGN-DlpIbxXb.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-NZK2D7GU-tnrSoegS.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-NZK2D7GU-tnrSoegS.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-O5CBEL6O-DxxqDH0l.js +71 -0
- keboola_agent_cli/_ui_dist/assets/chunk-O5CBEL6O-DxxqDH0l.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-QZHKN3VN-CSjc2gjj.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-QZHKN3VN-CSjc2gjj.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/classDiagram-4FO5ZUOK-BuZcZu85.js +2 -0
- keboola_agent_cli/_ui_dist/assets/classDiagram-4FO5ZUOK-BuZcZu85.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/classDiagram-v2-Q7XG4LA2-BuZcZu85.js +2 -0
- keboola_agent_cli/_ui_dist/assets/classDiagram-v2-Q7XG4LA2-BuZcZu85.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/cose-bilkent-S5V4N54A-Y0L8LDMa.js +2 -0
- keboola_agent_cli/_ui_dist/assets/cose-bilkent-S5V4N54A-Y0L8LDMa.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/cytoscape.esm-C8YCVR3_.js +322 -0
- keboola_agent_cli/_ui_dist/assets/cytoscape.esm-C8YCVR3_.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/dagre-BM42HDAG-UZ-9BTqF.js +5 -0
- keboola_agent_cli/_ui_dist/assets/dagre-BM42HDAG-UZ-9BTqF.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/dagre-Bx709z4p.js +2 -0
- keboola_agent_cli/_ui_dist/assets/dagre-Bx709z4p.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/defaultLocale-C8Fc0cco.js +2 -0
- keboola_agent_cli/_ui_dist/assets/defaultLocale-C8Fc0cco.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/diagram-2AECGRRQ-DoDQ60wi.js +44 -0
- keboola_agent_cli/_ui_dist/assets/diagram-2AECGRRQ-DoDQ60wi.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/diagram-5GNKFQAL-CMGFxpUs.js +11 -0
- keboola_agent_cli/_ui_dist/assets/diagram-5GNKFQAL-CMGFxpUs.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/diagram-KO2AKTUF-1uGDa-Iu.js +4 -0
- keboola_agent_cli/_ui_dist/assets/diagram-KO2AKTUF-1uGDa-Iu.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/diagram-LMA3HP47-XtFH7B51.js +25 -0
- keboola_agent_cli/_ui_dist/assets/diagram-LMA3HP47-XtFH7B51.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/diagram-OG6HWLK6-B4_Te1T5.js +25 -0
- keboola_agent_cli/_ui_dist/assets/diagram-OG6HWLK6-B4_Te1T5.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/dist-Di6zmlv0.js +2 -0
- keboola_agent_cli/_ui_dist/assets/dist-Di6zmlv0.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/erDiagram-TEJ5UH35-NjQkrdFt.js +86 -0
- keboola_agent_cli/_ui_dist/assets/erDiagram-TEJ5UH35-NjQkrdFt.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/eventmodeling-FCH6USID-BrJMIks8.js +1 -0
- keboola_agent_cli/_ui_dist/assets/flowDiagram-I6XJVG4X-CIr8DWl7.js +163 -0
- keboola_agent_cli/_ui_dist/assets/flowDiagram-I6XJVG4X-CIr8DWl7.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/ganttDiagram-6RSMTGT7-C1VY_xbQ.js +293 -0
- keboola_agent_cli/_ui_dist/assets/ganttDiagram-6RSMTGT7-C1VY_xbQ.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/gitGraph-WXDBUCRP-COacYjo-.js +1 -0
- keboola_agent_cli/_ui_dist/assets/gitGraphDiagram-PVQCEYII-DQT8-kg2.js +107 -0
- keboola_agent_cli/_ui_dist/assets/gitGraphDiagram-PVQCEYII-DQT8-kg2.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/graphlib-B8gBHxth.js +2 -0
- keboola_agent_cli/_ui_dist/assets/graphlib-B8gBHxth.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/index-CMq50kkV.css +1 -0
- keboola_agent_cli/_ui_dist/assets/index-D8W97DAz.js +118 -0
- keboola_agent_cli/_ui_dist/assets/index-D8W97DAz.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/info-J43DQDTF-DdCTRIzU.js +1 -0
- keboola_agent_cli/_ui_dist/assets/infoDiagram-5YYISTIA-C77rsoTp.js +3 -0
- keboola_agent_cli/_ui_dist/assets/infoDiagram-5YYISTIA-C77rsoTp.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/init-D6jRqBbL.js +2 -0
- keboola_agent_cli/_ui_dist/assets/init-D6jRqBbL.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/ishikawaDiagram-YF4QCWOH-BcTbXaLy.js +71 -0
- keboola_agent_cli/_ui_dist/assets/ishikawaDiagram-YF4QCWOH-BcTbXaLy.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/journeyDiagram-JHISSGLW-BejeAJQ_.js +140 -0
- keboola_agent_cli/_ui_dist/assets/journeyDiagram-JHISSGLW-BejeAJQ_.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/kanban-definition-UN3LZRKU-BRNz_UrH.js +90 -0
- keboola_agent_cli/_ui_dist/assets/kanban-definition-UN3LZRKU-BRNz_UrH.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/katex-C4eR7coU.js +258 -0
- keboola_agent_cli/_ui_dist/assets/katex-C4eR7coU.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/line-CzAQKFbJ.js +2 -0
- keboola_agent_cli/_ui_dist/assets/line-CzAQKFbJ.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/linear-DUNFFdck.js +2 -0
- keboola_agent_cli/_ui_dist/assets/linear-DUNFFdck.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/mermaid-parser.core-CpuBOkFa.js +5 -0
- keboola_agent_cli/_ui_dist/assets/mermaid-parser.core-CpuBOkFa.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/mindmap-definition-RKZ34NQL-9EJQNjH0.js +97 -0
- keboola_agent_cli/_ui_dist/assets/mindmap-definition-RKZ34NQL-9EJQNjH0.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/ordinal-hYBb2elL.js +2 -0
- keboola_agent_cli/_ui_dist/assets/ordinal-hYBb2elL.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/packet-YPE3B663-DLiiw_B2.js +1 -0
- keboola_agent_cli/_ui_dist/assets/path-BWPyau1x.js +2 -0
- keboola_agent_cli/_ui_dist/assets/path-BWPyau1x.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/pie-LRSECV5Y-CRoO8G1g.js +1 -0
- keboola_agent_cli/_ui_dist/assets/pieDiagram-4H26LBE5-XH4cy6Cb.js +31 -0
- keboola_agent_cli/_ui_dist/assets/pieDiagram-4H26LBE5-XH4cy6Cb.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/quadrantDiagram-W4KKPZXB-fdhc93U8.js +8 -0
- keboola_agent_cli/_ui_dist/assets/quadrantDiagram-W4KKPZXB-fdhc93U8.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/radar-GUYGQ44K-DAlLVJHm.js +1 -0
- keboola_agent_cli/_ui_dist/assets/requirementDiagram-4Y6WPE33-a94eP3R9.js +85 -0
- keboola_agent_cli/_ui_dist/assets/requirementDiagram-4Y6WPE33-a94eP3R9.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/rough.esm-CSKSodPl.js +2 -0
- keboola_agent_cli/_ui_dist/assets/rough.esm-CSKSodPl.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/sankeyDiagram-5OEKKPKP-jcBa02sp.js +41 -0
- keboola_agent_cli/_ui_dist/assets/sankeyDiagram-5OEKKPKP-jcBa02sp.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/sequenceDiagram-3UESZ5HK-A5-GGM-e.js +163 -0
- keboola_agent_cli/_ui_dist/assets/sequenceDiagram-3UESZ5HK-A5-GGM-e.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/src-ZI-V_AF0.js +2 -0
- keboola_agent_cli/_ui_dist/assets/src-ZI-V_AF0.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/stateDiagram-AJRCARHV-BKAA5rqE.js +2 -0
- keboola_agent_cli/_ui_dist/assets/stateDiagram-AJRCARHV-BKAA5rqE.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/stateDiagram-v2-BHNVJYJU-DnJwJBsE.js +2 -0
- keboola_agent_cli/_ui_dist/assets/stateDiagram-v2-BHNVJYJU-DnJwJBsE.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/timeline-definition-PNZ67QCA-Cy39jp8b.js +121 -0
- keboola_agent_cli/_ui_dist/assets/timeline-definition-PNZ67QCA-Cy39jp8b.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/treeView-BLDUP644-DbLYl23-.js +1 -0
- keboola_agent_cli/_ui_dist/assets/treemap-LRROVOQU-Bp0eGlOt.js +1 -0
- keboola_agent_cli/_ui_dist/assets/vennDiagram-CIIHVFJN-BGECKubd.js +35 -0
- keboola_agent_cli/_ui_dist/assets/vennDiagram-CIIHVFJN-BGECKubd.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/wardley-L42UT6IY-D4yH4jqS.js +1 -0
- keboola_agent_cli/_ui_dist/assets/wardleyDiagram-YWT4CUSO-D6XRG3cZ.js +79 -0
- keboola_agent_cli/_ui_dist/assets/wardleyDiagram-YWT4CUSO-D6XRG3cZ.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/xychartDiagram-2RQKCTM6-DRre-pfZ.js +8 -0
- keboola_agent_cli/_ui_dist/assets/xychartDiagram-2RQKCTM6-DRre-pfZ.js.map +1 -0
- keboola_agent_cli/_ui_dist/index.html +50 -0
- keboola_agent_cli/ai_client.py +83 -0
- keboola_agent_cli/auto_update.py +550 -0
- keboola_agent_cli/changelog.py +1198 -0
- keboola_agent_cli/cli.py +448 -0
- keboola_agent_cli/client.py +3422 -0
- keboola_agent_cli/commands/__init__.py +0 -0
- keboola_agent_cli/commands/_data_app_git.py +343 -0
- keboola_agent_cli/commands/_helpers.py +377 -0
- keboola_agent_cli/commands/_metadata_input.py +49 -0
- keboola_agent_cli/commands/_semantic_layer_crud.py +632 -0
- keboola_agent_cli/commands/_semantic_layer_helpers.py +44 -0
- keboola_agent_cli/commands/_semantic_layer_reference_data.py +247 -0
- keboola_agent_cli/commands/agent.py +968 -0
- keboola_agent_cli/commands/branch.py +423 -0
- keboola_agent_cli/commands/changelog.py +168 -0
- keboola_agent_cli/commands/component.py +216 -0
- keboola_agent_cli/commands/config.py +2442 -0
- keboola_agent_cli/commands/context.py +1481 -0
- keboola_agent_cli/commands/data_app.py +1279 -0
- keboola_agent_cli/commands/dev_portal.py +584 -0
- keboola_agent_cli/commands/doctor.py +37 -0
- keboola_agent_cli/commands/encrypt.py +145 -0
- keboola_agent_cli/commands/feature.py +311 -0
- keboola_agent_cli/commands/flow.py +948 -0
- keboola_agent_cli/commands/http_client.py +157 -0
- keboola_agent_cli/commands/init.py +279 -0
- keboola_agent_cli/commands/job.py +661 -0
- keboola_agent_cli/commands/kai.py +301 -0
- keboola_agent_cli/commands/lineage.py +1464 -0
- keboola_agent_cli/commands/org.py +292 -0
- keboola_agent_cli/commands/permissions.py +360 -0
- keboola_agent_cli/commands/project.py +1192 -0
- keboola_agent_cli/commands/repl.py +243 -0
- keboola_agent_cli/commands/schedule.py +340 -0
- keboola_agent_cli/commands/search.py +178 -0
- keboola_agent_cli/commands/semantic_layer.py +939 -0
- keboola_agent_cli/commands/serve.py +272 -0
- keboola_agent_cli/commands/sharing.py +340 -0
- keboola_agent_cli/commands/storage.py +2630 -0
- keboola_agent_cli/commands/stream.py +266 -0
- keboola_agent_cli/commands/sync.py +1277 -0
- keboola_agent_cli/commands/tool.py +206 -0
- keboola_agent_cli/commands/version.py +186 -0
- keboola_agent_cli/commands/workspace.py +635 -0
- keboola_agent_cli/config_store.py +582 -0
- keboola_agent_cli/constants.py +528 -0
- keboola_agent_cli/data_science_client.py +342 -0
- keboola_agent_cli/dev_portal_client.py +323 -0
- keboola_agent_cli/errors.py +248 -0
- keboola_agent_cli/http_base.py +315 -0
- keboola_agent_cli/json_utils.py +126 -0
- keboola_agent_cli/lib.py +536 -0
- keboola_agent_cli/manage_client.py +324 -0
- keboola_agent_cli/metastore_client.py +214 -0
- keboola_agent_cli/models.py +427 -0
- keboola_agent_cli/output.py +1084 -0
- keboola_agent_cli/permissions.py +469 -0
- keboola_agent_cli/py.typed +3 -0
- keboola_agent_cli/result_models.py +271 -0
- keboola_agent_cli/server/__init__.py +34 -0
- keboola_agent_cli/server/agent_runner.py +1289 -0
- keboola_agent_cli/server/agents_store.py +325 -0
- keboola_agent_cli/server/app.py +764 -0
- keboola_agent_cli/server/auth.py +117 -0
- keboola_agent_cli/server/dependencies.py +149 -0
- keboola_agent_cli/server/pricing.py +303 -0
- keboola_agent_cli/server/routers/__init__.py +1 -0
- keboola_agent_cli/server/routers/agents.py +616 -0
- keboola_agent_cli/server/routers/ai_chat.py +129 -0
- keboola_agent_cli/server/routers/branches.py +133 -0
- keboola_agent_cli/server/routers/components.py +48 -0
- keboola_agent_cli/server/routers/configs.py +507 -0
- keboola_agent_cli/server/routers/data_apps.py +384 -0
- keboola_agent_cli/server/routers/dev_portal.py +67 -0
- keboola_agent_cli/server/routers/encrypt.py +35 -0
- keboola_agent_cli/server/routers/feature.py +179 -0
- keboola_agent_cli/server/routers/flows.py +204 -0
- keboola_agent_cli/server/routers/health.py +53 -0
- keboola_agent_cli/server/routers/jobs.py +175 -0
- keboola_agent_cli/server/routers/kai.py +80 -0
- keboola_agent_cli/server/routers/lineage.py +226 -0
- keboola_agent_cli/server/routers/mcp.py +70 -0
- keboola_agent_cli/server/routers/members.py +170 -0
- keboola_agent_cli/server/routers/org.py +96 -0
- keboola_agent_cli/server/routers/projects.py +106 -0
- keboola_agent_cli/server/routers/schedules.py +54 -0
- keboola_agent_cli/server/routers/search.py +30 -0
- keboola_agent_cli/server/routers/semantic_layer.py +650 -0
- keboola_agent_cli/server/routers/sharing.py +86 -0
- keboola_agent_cli/server/routers/storage.py +574 -0
- keboola_agent_cli/server/routers/stream.py +100 -0
- keboola_agent_cli/server/routers/workspaces.py +302 -0
- keboola_agent_cli/server/run_broadcaster.py +329 -0
- keboola_agent_cli/server/sse.py +25 -0
- keboola_agent_cli/services/__init__.py +0 -0
- keboola_agent_cli/services/_encryption.py +217 -0
- keboola_agent_cli/services/_semantic_layer_cascade.py +147 -0
- keboola_agent_cli/services/_semantic_layer_crud.py +382 -0
- keboola_agent_cli/services/_semantic_layer_internals.py +1078 -0
- keboola_agent_cli/services/_semantic_layer_lookup.py +181 -0
- keboola_agent_cli/services/_semantic_layer_reference_data.py +217 -0
- keboola_agent_cli/services/_sync_bindings.py +456 -0
- keboola_agent_cli/services/_sync_branch.py +191 -0
- keboola_agent_cli/services/_sync_bulk.py +228 -0
- keboola_agent_cli/services/_sync_clone.py +163 -0
- keboola_agent_cli/services/_sync_models.py +97 -0
- keboola_agent_cli/services/_sync_push_ops.py +369 -0
- keboola_agent_cli/services/_sync_storage.py +376 -0
- keboola_agent_cli/services/_sync_writeback.py +167 -0
- keboola_agent_cli/services/agent_service.py +458 -0
- keboola_agent_cli/services/base.py +175 -0
- keboola_agent_cli/services/branch_service.py +588 -0
- keboola_agent_cli/services/component_service.py +694 -0
- keboola_agent_cli/services/config_service.py +2099 -0
- keboola_agent_cli/services/data_app_git_service.py +224 -0
- keboola_agent_cli/services/data_app_service.py +2082 -0
- keboola_agent_cli/services/deep_lineage_service.py +1322 -0
- keboola_agent_cli/services/dev_portal_service.py +345 -0
- keboola_agent_cli/services/doctor_service.py +445 -0
- keboola_agent_cli/services/encrypt_service.py +87 -0
- keboola_agent_cli/services/feature_service.py +268 -0
- keboola_agent_cli/services/flow_service.py +769 -0
- keboola_agent_cli/services/flow_validation.py +188 -0
- keboola_agent_cli/services/http_forwarder_service.py +236 -0
- keboola_agent_cli/services/job_idempotency_store.py +285 -0
- keboola_agent_cli/services/job_service.py +797 -0
- keboola_agent_cli/services/kai_service.py +367 -0
- keboola_agent_cli/services/lineage_service.py +274 -0
- keboola_agent_cli/services/mcp_service.py +1498 -0
- keboola_agent_cli/services/mcp_transport.py +259 -0
- keboola_agent_cli/services/member_service.py +593 -0
- keboola_agent_cli/services/org_service.py +619 -0
- keboola_agent_cli/services/project_service.py +947 -0
- keboola_agent_cli/services/repo_validate_service.py +767 -0
- keboola_agent_cli/services/schedule_service.py +731 -0
- keboola_agent_cli/services/search_service.py +331 -0
- keboola_agent_cli/services/semantic_layer_service.py +1497 -0
- keboola_agent_cli/services/sharing_service.py +307 -0
- keboola_agent_cli/services/storage_service.py +2524 -0
- keboola_agent_cli/services/stream_service.py +395 -0
- keboola_agent_cli/services/sync_service.py +2244 -0
- keboola_agent_cli/services/variables_service.py +447 -0
- keboola_agent_cli/services/version_service.py +1038 -0
- keboola_agent_cli/services/workspace_service.py +1103 -0
- keboola_agent_cli/stream_client.py +217 -0
- keboola_agent_cli/sync/__init__.py +1 -0
- keboola_agent_cli/sync/branch_mapping.py +174 -0
- keboola_agent_cli/sync/clone.py +211 -0
- keboola_agent_cli/sync/code_extraction.py +655 -0
- keboola_agent_cli/sync/config_format.py +290 -0
- keboola_agent_cli/sync/diff_engine.py +566 -0
- keboola_agent_cli/sync/git_utils.py +93 -0
- keboola_agent_cli/sync/manifest.py +162 -0
- keboola_agent_cli/sync/naming.py +90 -0
- keboola_agent_cli/sync/secrets.py +62 -0
- keboola_agent_cli/sync/sql_split.py +134 -0
- keboola_cli-0.63.4.dist-info/METADATA +308 -0
- keboola_cli-0.63.4.dist-info/RECORD +306 -0
- keboola_cli-0.63.4.dist-info/WHEEL +4 -0
- 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)
|