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,226 @@
|
|
|
1
|
+
"""Lineage endpoints (cross-project sharing graph + deep column lineage).
|
|
2
|
+
|
|
3
|
+
The ``/browser`` family mirrors what ``kbagent lineage server`` does as a
|
|
4
|
+
standalone CLI subcommand -- HTML browser + data.json + query API +
|
|
5
|
+
Mermaid renderer -- but mounted into the FastAPI app so the React UI can
|
|
6
|
+
``<iframe>`` it instead of duplicating the rendering logic.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
from urllib.parse import quote
|
|
15
|
+
|
|
16
|
+
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
17
|
+
from fastapi.responses import HTMLResponse, PlainTextResponse
|
|
18
|
+
from pydantic import BaseModel
|
|
19
|
+
|
|
20
|
+
from ..dependencies import ServiceRegistry, get_registry
|
|
21
|
+
|
|
22
|
+
router = APIRouter(prefix="/lineage", tags=["lineage"])
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LineageBuild(BaseModel):
|
|
26
|
+
directory: str
|
|
27
|
+
output: str
|
|
28
|
+
use_ai: bool = False
|
|
29
|
+
refresh: bool = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LineageQuery(BaseModel):
|
|
33
|
+
load: str
|
|
34
|
+
upstream: str | None = None
|
|
35
|
+
downstream: str | None = None
|
|
36
|
+
project: str | None = None
|
|
37
|
+
depth: int = 10
|
|
38
|
+
format: str = "text"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@router.get("/edges", summary="List cross-project lineage edges")
|
|
42
|
+
def edges(
|
|
43
|
+
project: list[str] | None = Query(None),
|
|
44
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
45
|
+
) -> dict[str, Any]:
|
|
46
|
+
"""Cross-project bucket-sharing edges (LineageService)."""
|
|
47
|
+
return registry.lineage.get_lineage(aliases=project)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@router.post("/build", summary="Build deep column-level lineage")
|
|
51
|
+
def build(body: LineageBuild, registry: ServiceRegistry = Depends(get_registry)) -> dict[str, Any]:
|
|
52
|
+
"""Build deep column-level lineage and write JSON cache to ``output``.
|
|
53
|
+
|
|
54
|
+
Auto-creates the working directory if missing -- it's empty before the
|
|
55
|
+
first ``sync pull`` anyway, so 404'ing on absence was just user-hostile.
|
|
56
|
+
"""
|
|
57
|
+
directory = Path(body.directory).resolve()
|
|
58
|
+
output = Path(body.output).resolve()
|
|
59
|
+
try:
|
|
60
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
except OSError as exc:
|
|
62
|
+
raise HTTPException(
|
|
63
|
+
status_code=400,
|
|
64
|
+
detail=f"Cannot create working directory '{body.directory}': {exc}",
|
|
65
|
+
) from exc
|
|
66
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
|
|
68
|
+
if body.refresh:
|
|
69
|
+
registry.sync.pull_all(base_dir=directory)
|
|
70
|
+
result = registry.deep_lineage.build_lineage(directory, generate_ai_tasks=body.use_ai)
|
|
71
|
+
try:
|
|
72
|
+
with output.open("w") as fh:
|
|
73
|
+
json.dump(result, fh, indent=2)
|
|
74
|
+
except OSError as exc:
|
|
75
|
+
raise HTTPException(status_code=500, detail=f"Cannot write '{body.output}': {exc}") from exc
|
|
76
|
+
# Tag the result with the resolved output path so the React UI can
|
|
77
|
+
# auto-load it without re-typing.
|
|
78
|
+
return {**result, "output_path": str(output)}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@router.post("/show", summary="Query a built lineage graph")
|
|
82
|
+
def show(body: LineageQuery, registry: ServiceRegistry = Depends(get_registry)) -> dict[str, Any]:
|
|
83
|
+
"""Query a built lineage graph (upstream / downstream walk)."""
|
|
84
|
+
load_path = Path(body.load)
|
|
85
|
+
if not load_path.exists():
|
|
86
|
+
raise HTTPException(status_code=400, detail=f"Lineage cache '{body.load}' not found.")
|
|
87
|
+
graph = registry.deep_lineage.load_from_cache(load_path)
|
|
88
|
+
if body.upstream:
|
|
89
|
+
return registry.deep_lineage.query_upstream(
|
|
90
|
+
graph, body.upstream, body.project or "", body.depth
|
|
91
|
+
)
|
|
92
|
+
if body.downstream:
|
|
93
|
+
return registry.deep_lineage.query_downstream(
|
|
94
|
+
graph, body.downstream, body.project or "", body.depth
|
|
95
|
+
)
|
|
96
|
+
raise HTTPException(status_code=400, detail="Provide --upstream or --downstream.")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@router.get("/info", summary="Show lineage cache summary")
|
|
100
|
+
def info(
|
|
101
|
+
load: str = Query(...), registry: ServiceRegistry = Depends(get_registry)
|
|
102
|
+
) -> dict[str, Any]:
|
|
103
|
+
"""Return a summary of nodes/edges in a built lineage cache. Mirrors `kbagent lineage info`."""
|
|
104
|
+
load_path = Path(load)
|
|
105
|
+
if not load_path.exists():
|
|
106
|
+
raise HTTPException(status_code=400, detail=f"Lineage cache '{load}' not found.")
|
|
107
|
+
graph = registry.deep_lineage.load_from_cache(load_path)
|
|
108
|
+
return graph.summary()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ── Browser surface ───────────────────────────────────────────────
|
|
112
|
+
# Reuses the HTML template + handler logic from `kbagent lineage server`
|
|
113
|
+
# (commands/lineage.py) so we don't have a second copy of the renderer.
|
|
114
|
+
# The CLI command serves them on a fresh http.server thread; here we
|
|
115
|
+
# re-host them inside FastAPI so the React UI can iframe the result.
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _rewrite_browser_html(load_path: str) -> str:
|
|
119
|
+
"""Return the lineage browser HTML with fetch URLs rewritten.
|
|
120
|
+
|
|
121
|
+
The original template (from commands/lineage.py) hard-codes
|
|
122
|
+
``/data.json`` / ``/api/query`` / ``/api/mermaid`` paths because
|
|
123
|
+
``kbagent lineage server`` is the only thing serving the request.
|
|
124
|
+
Inside FastAPI we live under ``/lineage/...`` and the BFF proxies
|
|
125
|
+
that under ``/api/lineage/...`` for the browser. Rewrite the three
|
|
126
|
+
fetch endpoints so the same HTML works inside an iframe.
|
|
127
|
+
|
|
128
|
+
The ``load`` query param is baked into every rewritten URL so the
|
|
129
|
+
browser never has to know which JSON cache to read -- one HTML
|
|
130
|
+
response = one cache file.
|
|
131
|
+
"""
|
|
132
|
+
from ...commands.lineage import _LINEAGE_HTML_TEMPLATE
|
|
133
|
+
|
|
134
|
+
enc = quote(load_path, safe="")
|
|
135
|
+
return (
|
|
136
|
+
_LINEAGE_HTML_TEMPLATE
|
|
137
|
+
# /data.json -> /api/lineage/data?load=...
|
|
138
|
+
.replace('"/data.json"', f'"/api/lineage/data?load={enc}"')
|
|
139
|
+
# /api/query?node=X... -> /api/lineage/walk?load=...&node=X...
|
|
140
|
+
.replace('"/api/query?node="', f'"/api/lineage/walk?load={enc}&node="')
|
|
141
|
+
# /api/mermaid?node=X... -> /api/lineage/mermaid?load=...&node=X...
|
|
142
|
+
.replace('"/api/mermaid?node="', f'"/api/lineage/mermaid?load={enc}&node="')
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@router.get("/browser", response_class=HTMLResponse, summary="Open lineage browser UI")
|
|
147
|
+
def browser(load: str = Query(...)) -> HTMLResponse:
|
|
148
|
+
"""Serve the interactive lineage browser HTML for a JSON cache file."""
|
|
149
|
+
load_path = Path(load)
|
|
150
|
+
if not load_path.exists():
|
|
151
|
+
raise HTTPException(status_code=400, detail=f"Lineage cache '{load}' not found.")
|
|
152
|
+
return HTMLResponse(content=_rewrite_browser_html(load))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@router.get("/data", summary="Return raw lineage JSON")
|
|
156
|
+
def data(load: str = Query(...)) -> Any:
|
|
157
|
+
"""Raw lineage JSON, served verbatim from disk for the browser."""
|
|
158
|
+
load_path = Path(load)
|
|
159
|
+
if not load_path.exists():
|
|
160
|
+
raise HTTPException(status_code=400, detail=f"Lineage cache '{load}' not found.")
|
|
161
|
+
try:
|
|
162
|
+
raw = load_path.read_text(encoding="utf-8")
|
|
163
|
+
except OSError as exc:
|
|
164
|
+
raise HTTPException(status_code=500, detail=f"Cannot read '{load}': {exc}") from exc
|
|
165
|
+
# Hand the parsed dict back so FastAPI sets the right content-type and
|
|
166
|
+
# the browser fetch().json() call works without surprises.
|
|
167
|
+
return json.loads(raw)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@router.get("/walk", summary="Walk lineage graph from a node")
|
|
171
|
+
def walk(
|
|
172
|
+
load: str = Query(...),
|
|
173
|
+
node: str = Query(...),
|
|
174
|
+
direction: str = Query("downstream"),
|
|
175
|
+
depth: int = Query(3, ge=1, le=20),
|
|
176
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
177
|
+
) -> dict[str, Any]:
|
|
178
|
+
"""Walk the lineage graph upstream / downstream from a node FQN.
|
|
179
|
+
|
|
180
|
+
Used by the in-browser query bar (``/api/query`` in the standalone
|
|
181
|
+
server). Mirrors the CLI ``lineage show`` semantics but is GET-only
|
|
182
|
+
so the HTML's ``fetch()`` calls work without preflight.
|
|
183
|
+
"""
|
|
184
|
+
load_path = Path(load)
|
|
185
|
+
if not load_path.exists():
|
|
186
|
+
raise HTTPException(status_code=400, detail=f"Lineage cache '{load}' not found.")
|
|
187
|
+
graph = registry.deep_lineage.load_from_cache(load_path)
|
|
188
|
+
if direction == "upstream":
|
|
189
|
+
return registry.deep_lineage.query_upstream(graph, node, "", depth)
|
|
190
|
+
return registry.deep_lineage.query_downstream(graph, node, "", depth)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@router.get("/mermaid", response_class=PlainTextResponse, summary="Render lineage as Mermaid")
|
|
194
|
+
def mermaid(
|
|
195
|
+
load: str = Query(...),
|
|
196
|
+
node: str = Query(...),
|
|
197
|
+
direction: str = Query("downstream"),
|
|
198
|
+
depth: int = Query(3, ge=1, le=20),
|
|
199
|
+
view: str = Query("flow"),
|
|
200
|
+
columns: str = Query("false"),
|
|
201
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
202
|
+
) -> PlainTextResponse:
|
|
203
|
+
"""Return a Mermaid diagram for the requested upstream/downstream walk."""
|
|
204
|
+
from ...services.deep_lineage_service import DeepLineageService
|
|
205
|
+
|
|
206
|
+
load_path = Path(load)
|
|
207
|
+
if not load_path.exists():
|
|
208
|
+
raise HTTPException(status_code=400, detail=f"Lineage cache '{load}' not found.")
|
|
209
|
+
graph = registry.deep_lineage.load_from_cache(load_path)
|
|
210
|
+
|
|
211
|
+
if direction == "upstream":
|
|
212
|
+
result = registry.deep_lineage.query_upstream(graph, node, "", depth)
|
|
213
|
+
else:
|
|
214
|
+
result = registry.deep_lineage.query_downstream(graph, node, "", depth)
|
|
215
|
+
|
|
216
|
+
if "error" in result:
|
|
217
|
+
return PlainTextResponse("graph LR\n error[" + result["error"].replace('"', "'") + "]")
|
|
218
|
+
edges = result.get("edges", [])
|
|
219
|
+
show_cols = columns == "true"
|
|
220
|
+
if view == "er":
|
|
221
|
+
code = DeepLineageService.render_er_diagram(edges, graph, node, show_columns=show_cols)
|
|
222
|
+
else:
|
|
223
|
+
code = DeepLineageService.render_mermaid(
|
|
224
|
+
edges, graph, direction, node, show_columns=show_cols
|
|
225
|
+
)
|
|
226
|
+
return PlainTextResponse(code)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""MCP tool endpoints (list, call across projects)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from ..dependencies import ServiceRegistry, get_registry
|
|
11
|
+
|
|
12
|
+
router = APIRouter(prefix="/mcp", tags=["mcp"])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ToolCall(BaseModel):
|
|
16
|
+
input: dict[str, Any] | None = None
|
|
17
|
+
project: str | None = None
|
|
18
|
+
branch_id: str | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@router.get("/tools", summary="List MCP tools")
|
|
22
|
+
def list_tools(
|
|
23
|
+
project: str | None = None,
|
|
24
|
+
branch_id: str | None = None,
|
|
25
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
26
|
+
) -> dict[str, Any]:
|
|
27
|
+
"""Discover every MCP tool exposed by `keboola-mcp-server` for one or
|
|
28
|
+
all projects. Omit `project` to fan out across every registered alias.
|
|
29
|
+
"""
|
|
30
|
+
aliases = [project] if project else None
|
|
31
|
+
return registry.mcp.list_tools(aliases=aliases, branch_id=branch_id)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@router.get("/tools/{tool_name}/schema", summary="Fetch a tool's input schema")
|
|
35
|
+
def tool_schema(
|
|
36
|
+
tool_name: str,
|
|
37
|
+
project: str | None = None,
|
|
38
|
+
branch_id: str | None = None,
|
|
39
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
40
|
+
) -> dict[str, Any]:
|
|
41
|
+
"""JSON Schema for one MCP tool's `input` -- useful when wiring a form
|
|
42
|
+
or pre-validating before `POST /mcp/tools/{name}/call`.
|
|
43
|
+
"""
|
|
44
|
+
aliases = [project] if project else None
|
|
45
|
+
schema = registry.mcp.get_tool_schema(tool_name=tool_name, aliases=aliases, branch_id=branch_id)
|
|
46
|
+
return {"tool": tool_name, "input_schema": schema or {}}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.post("/tools/{tool_name}/call", summary="Call an MCP tool")
|
|
50
|
+
def call_tool(
|
|
51
|
+
tool_name: str, body: ToolCall, registry: ServiceRegistry = Depends(get_registry)
|
|
52
|
+
) -> dict[str, Any]:
|
|
53
|
+
"""Invoke one MCP tool. Input is validated against the tool's schema
|
|
54
|
+
server-side before the MCP subprocess is spawned, so obvious mistakes
|
|
55
|
+
fail fast with a 4xx instead of a cryptic MCP error.
|
|
56
|
+
"""
|
|
57
|
+
return registry.mcp.validate_and_call_tool(
|
|
58
|
+
tool_name=tool_name,
|
|
59
|
+
tool_input=body.input,
|
|
60
|
+
alias=body.project,
|
|
61
|
+
branch_id=body.branch_id,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@router.get("/server-status", summary="Check MCP server availability")
|
|
66
|
+
def server_status(registry: ServiceRegistry = Depends(get_registry)) -> dict[str, Any]:
|
|
67
|
+
"""Whether the bundled `keboola-mcp-server` subprocess is reachable
|
|
68
|
+
(HTTP transport or stdio fallback, depending on env vars).
|
|
69
|
+
"""
|
|
70
|
+
return registry.mcp.check_server_available()
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Project members endpoints (invite/list/remove/role) -- all require manage token."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import asdict, is_dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from ..dependencies import ServiceRegistry, get_manage_token, get_registry
|
|
12
|
+
|
|
13
|
+
router = APIRouter(prefix="/members", tags=["members"])
|
|
14
|
+
|
|
15
|
+
# Member endpoints all hit the Manage API, so every operation needs the
|
|
16
|
+
# per-request Manage token alongside the standard bearer token. Declaring
|
|
17
|
+
# the joint requirement here surfaces it as a separate scheme in the
|
|
18
|
+
# Swagger UI "Authorize" dialog.
|
|
19
|
+
_NEEDS_MANAGE_TOKEN: dict[str, Any] = {"security": [{"BearerAuth": [], "ManageToken": []}]}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class InviteOne(BaseModel):
|
|
23
|
+
email: str
|
|
24
|
+
role: str
|
|
25
|
+
reason: str | None = None
|
|
26
|
+
dry_run: bool = False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CancelInvitation(BaseModel):
|
|
30
|
+
email: str
|
|
31
|
+
invitation_id: int | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RemoveMember(BaseModel):
|
|
35
|
+
email: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SetRole(BaseModel):
|
|
39
|
+
email: str
|
|
40
|
+
role: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _require_manage(token: str | None) -> str:
|
|
44
|
+
if not token:
|
|
45
|
+
raise HTTPException(status_code=401, detail="Missing X-Manage-Token header.")
|
|
46
|
+
return token
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _serialize(value: Any) -> Any:
|
|
50
|
+
if is_dataclass(value):
|
|
51
|
+
return asdict(value)
|
|
52
|
+
return value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@router.get("/{project}", summary="List members", openapi_extra=_NEEDS_MANAGE_TOKEN)
|
|
56
|
+
def list_members(
|
|
57
|
+
project: str,
|
|
58
|
+
include_pending: bool = False,
|
|
59
|
+
manage_token: str | None = Depends(get_manage_token),
|
|
60
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
61
|
+
) -> dict[str, Any]:
|
|
62
|
+
"""Active members of `project`. Pass `include_pending=true` to also include
|
|
63
|
+
invitations that have not yet been accepted. Mirrors
|
|
64
|
+
`kbagent project member-list`.
|
|
65
|
+
"""
|
|
66
|
+
return registry.member.list_members(
|
|
67
|
+
manage_token=_require_manage(manage_token),
|
|
68
|
+
alias=project,
|
|
69
|
+
include_pending=include_pending,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@router.get(
|
|
74
|
+
"/{project}/invitations",
|
|
75
|
+
summary="List pending invitations",
|
|
76
|
+
openapi_extra=_NEEDS_MANAGE_TOKEN,
|
|
77
|
+
)
|
|
78
|
+
def list_invitations(
|
|
79
|
+
project: str,
|
|
80
|
+
manage_token: str | None = Depends(get_manage_token),
|
|
81
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
82
|
+
) -> dict[str, Any]:
|
|
83
|
+
"""Outstanding invitations for `project`. Mirrors
|
|
84
|
+
`kbagent project invitation-list`.
|
|
85
|
+
"""
|
|
86
|
+
return registry.member.list_invitations(
|
|
87
|
+
manage_token=_require_manage(manage_token), alias=project
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@router.post("/{project}/invite", summary="Invite a single user", openapi_extra=_NEEDS_MANAGE_TOKEN)
|
|
92
|
+
def invite(
|
|
93
|
+
project: str,
|
|
94
|
+
body: InviteOne,
|
|
95
|
+
manage_token: str | None = Depends(get_manage_token),
|
|
96
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
97
|
+
) -> dict[str, Any]:
|
|
98
|
+
"""Send a single invitation. Pass `dry_run=true` to validate without
|
|
99
|
+
actually sending. Mirrors `kbagent project invite --email --role`.
|
|
100
|
+
"""
|
|
101
|
+
return registry.member.invite(
|
|
102
|
+
manage_token=_require_manage(manage_token),
|
|
103
|
+
alias=project,
|
|
104
|
+
email=body.email,
|
|
105
|
+
role=body.role,
|
|
106
|
+
reason=body.reason,
|
|
107
|
+
dry_run=body.dry_run,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@router.post(
|
|
112
|
+
"/{project}/invitations/cancel",
|
|
113
|
+
summary="Cancel invitation",
|
|
114
|
+
openapi_extra=_NEEDS_MANAGE_TOKEN,
|
|
115
|
+
)
|
|
116
|
+
def cancel_invitation(
|
|
117
|
+
project: str,
|
|
118
|
+
body: CancelInvitation,
|
|
119
|
+
manage_token: str | None = Depends(get_manage_token),
|
|
120
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
121
|
+
) -> dict[str, Any]:
|
|
122
|
+
"""Revoke an outstanding invitation. Identified by email (the API picks
|
|
123
|
+
the right invitation), or pin a specific `invitation_id`. Mirrors
|
|
124
|
+
`kbagent project invitation-cancel`.
|
|
125
|
+
"""
|
|
126
|
+
return registry.member.cancel_invitation(
|
|
127
|
+
manage_token=_require_manage(manage_token),
|
|
128
|
+
alias=project,
|
|
129
|
+
email=body.email,
|
|
130
|
+
invitation_id=body.invitation_id,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@router.post("/{project}/remove", summary="Remove member", openapi_extra=_NEEDS_MANAGE_TOKEN)
|
|
135
|
+
def remove(
|
|
136
|
+
project: str,
|
|
137
|
+
body: RemoveMember,
|
|
138
|
+
manage_token: str | None = Depends(get_manage_token),
|
|
139
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
140
|
+
) -> dict[str, Any]:
|
|
141
|
+
"""Revoke a user's access to the project. Mirrors
|
|
142
|
+
`kbagent project member-remove`.
|
|
143
|
+
"""
|
|
144
|
+
return registry.member.remove_member(
|
|
145
|
+
manage_token=_require_manage(manage_token),
|
|
146
|
+
alias=project,
|
|
147
|
+
email=body.email,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@router.post(
|
|
152
|
+
"/{project}/set-role",
|
|
153
|
+
summary="Change member role",
|
|
154
|
+
openapi_extra=_NEEDS_MANAGE_TOKEN,
|
|
155
|
+
)
|
|
156
|
+
def set_role(
|
|
157
|
+
project: str,
|
|
158
|
+
body: SetRole,
|
|
159
|
+
manage_token: str | None = Depends(get_manage_token),
|
|
160
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
161
|
+
) -> dict[str, Any]:
|
|
162
|
+
"""Promote / demote a member. Role is one of `admin`, `guest`,
|
|
163
|
+
`readOnly`, `share`. Mirrors `kbagent project member-set-role`.
|
|
164
|
+
"""
|
|
165
|
+
return registry.member.set_member_role(
|
|
166
|
+
manage_token=_require_manage(manage_token),
|
|
167
|
+
alias=project,
|
|
168
|
+
email=body.email,
|
|
169
|
+
role=body.role,
|
|
170
|
+
)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Organization bulk-onboarding endpoints (require manage token)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from ..dependencies import ServiceRegistry, get_manage_token, get_registry
|
|
11
|
+
|
|
12
|
+
router = APIRouter(prefix="/org", tags=["org"])
|
|
13
|
+
|
|
14
|
+
# Every endpoint in this router requires BOTH the standard bearer token AND
|
|
15
|
+
# the per-request Manage API token. Declaring it here means Swagger UI's
|
|
16
|
+
# "Authorize" dialog will list both schemes so users can paste each one
|
|
17
|
+
# once instead of guessing why a request 401s.
|
|
18
|
+
_NEEDS_MANAGE_TOKEN: dict[str, Any] = {"security": [{"BearerAuth": [], "ManageToken": []}]}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OrgSetup(BaseModel):
|
|
22
|
+
stack_url: str
|
|
23
|
+
org_id: int | None = None
|
|
24
|
+
project_ids: list[int] | None = None
|
|
25
|
+
token_description: str = "kbagent"
|
|
26
|
+
dry_run: bool = False
|
|
27
|
+
token_expires_in: int | None = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class OrgRefresh(BaseModel):
|
|
31
|
+
aliases: list[str] | None = None
|
|
32
|
+
refresh_all: bool = False
|
|
33
|
+
token_description: str = "kbagent"
|
|
34
|
+
dry_run: bool = False
|
|
35
|
+
force: bool = False
|
|
36
|
+
token_expires_in: int | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@router.post("/setup", summary="Onboard org / project list", openapi_extra=_NEEDS_MANAGE_TOKEN)
|
|
40
|
+
def setup(
|
|
41
|
+
body: OrgSetup,
|
|
42
|
+
manage_token: str | None = Depends(get_manage_token),
|
|
43
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
44
|
+
) -> dict[str, Any]:
|
|
45
|
+
"""Bulk-register every project in an organization (or a fixed project ID list).
|
|
46
|
+
|
|
47
|
+
Issues a fresh storage token per project via the Manage API and persists
|
|
48
|
+
each project under a slug-style alias. Idempotent: existing aliases that
|
|
49
|
+
already point at the same project ID are skipped. Mirrors
|
|
50
|
+
`kbagent org setup`.
|
|
51
|
+
"""
|
|
52
|
+
if not manage_token:
|
|
53
|
+
raise HTTPException(
|
|
54
|
+
status_code=401,
|
|
55
|
+
detail="Missing X-Manage-Token header. Org setup requires a manage token.",
|
|
56
|
+
)
|
|
57
|
+
return registry.org.setup_organization(
|
|
58
|
+
stack_url=body.stack_url,
|
|
59
|
+
manage_token=manage_token,
|
|
60
|
+
org_id=body.org_id,
|
|
61
|
+
token_description=body.token_description,
|
|
62
|
+
dry_run=body.dry_run,
|
|
63
|
+
token_expires_in=body.token_expires_in,
|
|
64
|
+
project_ids=body.project_ids,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@router.post("/refresh", summary="Re-issue storage tokens", openapi_extra=_NEEDS_MANAGE_TOKEN)
|
|
69
|
+
def refresh(
|
|
70
|
+
body: OrgRefresh,
|
|
71
|
+
manage_token: str | None = Depends(get_manage_token),
|
|
72
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
73
|
+
) -> dict[str, Any]:
|
|
74
|
+
"""Refresh the storage token for one alias, several aliases, or every project.
|
|
75
|
+
|
|
76
|
+
Useful after rotating a Manage API token or when a per-project token is
|
|
77
|
+
near expiry. Pass `refresh_all=true` to refresh every persisted alias,
|
|
78
|
+
or a list of aliases to scope the refresh. Mirrors
|
|
79
|
+
`kbagent project refresh --project` and `kbagent project refresh --all`.
|
|
80
|
+
"""
|
|
81
|
+
if not manage_token:
|
|
82
|
+
raise HTTPException(
|
|
83
|
+
status_code=401,
|
|
84
|
+
detail="Missing X-Manage-Token header. Refresh requires a manage token.",
|
|
85
|
+
)
|
|
86
|
+
# Service has no refresh_all param: pass aliases=None to refresh all,
|
|
87
|
+
# or aliases=body.aliases to scope the refresh.
|
|
88
|
+
effective_aliases = None if body.refresh_all else body.aliases
|
|
89
|
+
return registry.org.refresh_tokens(
|
|
90
|
+
manage_token=manage_token,
|
|
91
|
+
aliases=effective_aliases,
|
|
92
|
+
token_description=body.token_description,
|
|
93
|
+
dry_run=body.dry_run,
|
|
94
|
+
force=body.force,
|
|
95
|
+
token_expires_in=body.token_expires_in,
|
|
96
|
+
)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Project CRUD + status endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from ..dependencies import ServiceRegistry, get_registry
|
|
11
|
+
|
|
12
|
+
router = APIRouter(prefix="/projects", tags=["projects"])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ProjectCreate(BaseModel):
|
|
16
|
+
alias: str
|
|
17
|
+
stack_url: str
|
|
18
|
+
token: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ProjectEdit(BaseModel):
|
|
22
|
+
stack_url: str | None = None
|
|
23
|
+
token: str | None = None
|
|
24
|
+
new_alias: str | None = None
|
|
25
|
+
dry_run: bool = False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ProjectDescription(BaseModel):
|
|
29
|
+
description: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@router.get("", summary="List registered projects")
|
|
33
|
+
def list_projects(registry: ServiceRegistry = Depends(get_registry)) -> dict[str, Any]:
|
|
34
|
+
"""All registered project aliases."""
|
|
35
|
+
return {"projects": registry.project.list_projects()}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@router.post("", summary="Add a project")
|
|
39
|
+
def add_project(
|
|
40
|
+
body: ProjectCreate, registry: ServiceRegistry = Depends(get_registry)
|
|
41
|
+
) -> dict[str, Any]:
|
|
42
|
+
"""Add a project. Verifies the storage token before persisting."""
|
|
43
|
+
return registry.project.add_project(body.alias, body.stack_url, body.token)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@router.delete("/{alias}", summary="Remove a project")
|
|
47
|
+
def remove_project(alias: str, registry: ServiceRegistry = Depends(get_registry)) -> dict[str, Any]:
|
|
48
|
+
"""Remove a project by alias. Mirrors `kbagent project remove`."""
|
|
49
|
+
return registry.project.remove_project(alias)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@router.patch("/{alias}", summary="Edit a project")
|
|
53
|
+
def edit_project(
|
|
54
|
+
alias: str, body: ProjectEdit, registry: ServiceRegistry = Depends(get_registry)
|
|
55
|
+
) -> dict[str, Any]:
|
|
56
|
+
"""Update stack URL, token, or alias of a project. Mirrors `kbagent project edit`."""
|
|
57
|
+
return registry.project.edit_project(
|
|
58
|
+
alias=alias,
|
|
59
|
+
stack_url=body.stack_url,
|
|
60
|
+
token=body.token,
|
|
61
|
+
new_alias=body.new_alias,
|
|
62
|
+
dry_run=body.dry_run,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@router.get("/status", summary="Check project connectivity")
|
|
67
|
+
def status(
|
|
68
|
+
alias: str | None = None, registry: ServiceRegistry = Depends(get_registry)
|
|
69
|
+
) -> dict[str, Any]:
|
|
70
|
+
"""Connectivity check; pass ``?alias=`` to limit to one project."""
|
|
71
|
+
aliases = [alias] if alias else None
|
|
72
|
+
return {"status": registry.project.get_status(aliases=aliases)}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@router.get("/current", summary="Get the active project")
|
|
76
|
+
def current(registry: ServiceRegistry = Depends(get_registry)) -> dict[str, Any]:
|
|
77
|
+
"""Currently pinned project alias. Mirrors `kbagent project current`."""
|
|
78
|
+
return registry.project.current_project()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@router.post("/use/{alias}", summary="Switch the active project")
|
|
82
|
+
def use_project(alias: str, registry: ServiceRegistry = Depends(get_registry)) -> dict[str, Any]:
|
|
83
|
+
"""Pin a project as the current default. Mirrors `kbagent project use`."""
|
|
84
|
+
return registry.project.use_project(alias)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@router.get("/{alias}/info", summary="Get project metadata")
|
|
88
|
+
def info(alias: str, registry: ServiceRegistry = Depends(get_registry)) -> dict[str, Any]:
|
|
89
|
+
"""Project metadata (stack, owner, tokens). Mirrors `kbagent project info`."""
|
|
90
|
+
return registry.project.get_info(alias)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@router.get("/{alias}/description", summary="Get the project description")
|
|
94
|
+
def get_description(
|
|
95
|
+
alias: str, registry: ServiceRegistry = Depends(get_registry)
|
|
96
|
+
) -> dict[str, Any]:
|
|
97
|
+
return registry.branch.get_project_description(alias)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@router.put("/{alias}/description", summary="Set the project description")
|
|
101
|
+
def set_description(
|
|
102
|
+
alias: str,
|
|
103
|
+
body: ProjectDescription,
|
|
104
|
+
registry: ServiceRegistry = Depends(get_registry),
|
|
105
|
+
) -> dict[str, Any]:
|
|
106
|
+
return registry.branch.set_project_description(alias, body.description)
|