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