#
tokens: 49953/50000 79/898 files (page 3/97)
lines: off (toggle) GitHub
raw markdown copy
This is page 3 of 97. Use http://codebase.md/controlplaneio-fluxcd/flux-operator?page={x} to view the full context.

# Directory Structure

```
├── .github
│   ├── actions
│   │   └── runner-cleanup
│   │       └── action.yml
│   ├── copilot-instructions.md
│   ├── dependabot.yaml
│   └── workflows
│       ├── actions-test.yaml
│       ├── e2e-olm.yaml
│       ├── preview.yaml
│       ├── push-manifests.yaml
│       ├── release.yaml
│       └── test.yaml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── actions
│   └── setup
│       ├── action.yaml
│       └── README.md
├── AGENTS.md
├── api
│   └── v1
│       ├── common_types_test.go
│       ├── common_types.go
│       ├── fluxinstance_types.go
│       ├── fluxreport_types.go
│       ├── groupversion_info.go
│       ├── history_types_test.go
│       ├── history_types.go
│       ├── resourceset_types.go
│       ├── resourcesetinputprovider_types.go
│       ├── schedule_types.go
│       └── zz_generated.deepcopy.go
├── cmd
│   ├── cli
│   │   ├── build_instance.go
│   │   ├── build_resourceset_test.go
│   │   ├── build_resourceset.go
│   │   ├── build.go
│   │   ├── client.go
│   │   ├── completion_bash.go
│   │   ├── completion_fish.go
│   │   ├── completion_powershell.go
│   │   ├── completion_zsh.go
│   │   ├── completion.go
│   │   ├── create_secret_basicauth_test.go
│   │   ├── create_secret_basicauth.go
│   │   ├── create_secret_githubapp.go
│   │   ├── create_secret_proxy_test.go
│   │   ├── create_secret_proxy.go
│   │   ├── create_secret_registry_test.go
│   │   ├── create_secret_registry.go
│   │   ├── create_secret_sops_test.go
│   │   ├── create_secret_sops.go
│   │   ├── create_secret_ssh.go
│   │   ├── create_secret_tls.go
│   │   ├── create_secret.go
│   │   ├── create.go
│   │   ├── debug_web_cookie.go
│   │   ├── debug_web.go
│   │   ├── debug.go
│   │   ├── delete_inputprovider_test.go
│   │   ├── delete_inputprovider.go
│   │   ├── delete_instance_test.go
│   │   ├── delete_instance.go
│   │   ├── delete_resourceset_test.go
│   │   ├── delete_resourceset.go
│   │   ├── delete.go
│   │   ├── distro_decrypt_manifests_test.go
│   │   ├── distro_decrypt_manifests.go
│   │   ├── distro_decrypt_token_test.go
│   │   ├── distro_decrypt_token.go
│   │   ├── distro_decrypt.go
│   │   ├── distro_encrypt_manifests_test.go
│   │   ├── distro_encrypt_manifests.go
│   │   ├── distro_encrypt_token_test.go
│   │   ├── distro_encrypt_token.go
│   │   ├── distro_encrypt.go
│   │   ├── distro_keygen_enc_test.go
│   │   ├── distro_keygen_enc.go
│   │   ├── distro_keygen_sig_test.go
│   │   ├── distro_keygen_sig.go
│   │   ├── distro_keygen.go
│   │   ├── distro_revoke_license_key_test.go
│   │   ├── distro_revoke_license_key.go
│   │   ├── distro_revoke.go
│   │   ├── distro_sign_artifacts_test.go
│   │   ├── distro_sign_artifacts.go
│   │   ├── distro_sign_license_key_test.go
│   │   ├── distro_sign_license_key.go
│   │   ├── distro_sign_manifests_test.go
│   │   ├── distro_sign_manifests.go
│   │   ├── distro_sign.go
│   │   ├── distro_verify_artifacts_test.go
│   │   ├── distro_verify_artifacts.go
│   │   ├── distro_verify_license_key_test.go
│   │   ├── distro_verify_license_key.go
│   │   ├── distro_verify_manifests_test.go
│   │   ├── distro_verify_manifests.go
│   │   ├── distro_verify.go
│   │   ├── distro.go
│   │   ├── Dockerfile
│   │   ├── export_report_test.go
│   │   ├── export_report.go
│   │   ├── export_resource_test.go
│   │   ├── export_resource.go
│   │   ├── export.go
│   │   ├── get_inputprovider_test.go
│   │   ├── get_inputprovider.go
│   │   ├── get_instance.go
│   │   ├── get_resources.go
│   │   ├── get_resourceset_test.go
│   │   ├── get_resourceset.go
│   │   ├── get.go
│   │   ├── install.go
│   │   ├── main.go
│   │   ├── README.md
│   │   ├── reconcile_inputprovider.go
│   │   ├── reconcile_instance.go
│   │   ├── reconcile_resource.go
│   │   ├── reconcile_resources.go
│   │   ├── reconcile_resourceset.go
│   │   ├── reconcile.go
│   │   ├── resume_inputprovider.go
│   │   ├── resume_instance.go
│   │   ├── resume_resource.go
│   │   ├── resume_resourceset.go
│   │   ├── resume.go
│   │   ├── stats.go
│   │   ├── suite_test.go
│   │   ├── suspend_inputprovider.go
│   │   ├── suspend_instance.go
│   │   ├── suspend_resource.go
│   │   ├── suspend_resourceset.go
│   │   ├── suspend.go
│   │   ├── testdata
│   │   │   └── build_resourceset
│   │   │       ├── golden-labeled.yaml
│   │   │       ├── golden-named.yaml
│   │   │       ├── golden-permuted.yaml
│   │   │       ├── golden.yaml
│   │   │       ├── inputs.yaml
│   │   │       ├── rset-standalone.yaml
│   │   │       ├── rset-with-rsip-labeled.yaml
│   │   │       ├── rset-with-rsip-named.yaml
│   │   │       ├── rset-with-rsip-permuted.yaml
│   │   │       ├── rset-with-rsip.yaml
│   │   │       ├── rsip-labeled.yaml
│   │   │       ├── rsip-named.yaml
│   │   │       └── rsip.yaml
│   │   ├── trace_test.go
│   │   ├── trace_types.go
│   │   ├── trace.go
│   │   ├── tree_helmrelease.go
│   │   ├── tree_kustomization.go
│   │   ├── tree_resourceset_test.go
│   │   ├── tree_resourceset.go
│   │   ├── tree.go
│   │   ├── uninstall.go
│   │   ├── version_test.go
│   │   ├── version.go
│   │   ├── wait_inputprovider_test.go
│   │   ├── wait_inputprovider.go
│   │   ├── wait_instance_test.go
│   │   ├── wait_instance.go
│   │   ├── wait_resourceset_test.go
│   │   ├── wait_resourceset.go
│   │   └── wait.go
│   ├── mcp
│   │   ├── Dockerfile
│   │   ├── k8s
│   │   │   ├── actions_test.go
│   │   │   ├── actions.go
│   │   │   ├── client_test.go
│   │   │   ├── client.go
│   │   │   ├── config.go
│   │   │   ├── events_test.go
│   │   │   ├── events.go
│   │   │   ├── export_test.go
│   │   │   ├── export.go
│   │   │   ├── helm.go
│   │   │   ├── logs.go
│   │   │   ├── metrics.go
│   │   │   └── suite_test.go
│   │   ├── main.go
│   │   ├── prompter
│   │   │   ├── debug_helmrelease_test.go
│   │   │   ├── debug_helmrelease.go
│   │   │   ├── debug_kustomization_test.go
│   │   │   ├── debug_kustomization.go
│   │   │   ├── index.go
│   │   │   └── manager.go
│   │   ├── README.md
│   │   └── toolbox
│   │       ├── apply_manifest_test.go
│   │       ├── apply_manifest.go
│   │       ├── delete_resource_test.go
│   │       ├── delete_resource.go
│   │       ├── get_apis_test.go
│   │       ├── get_apis.go
│   │       ├── get_contexts_test.go
│   │       ├── get_contexts.go
│   │       ├── get_instance_test.go
│   │       ├── get_instance.go
│   │       ├── get_logs_test.go
│   │       ├── get_logs.go
│   │       ├── get_metrics_test.go
│   │       ├── get_metrics.go
│   │       ├── get_resource_test.go
│   │       ├── get_resource.go
│   │       ├── helpers.go
│   │       ├── indexer
│   │       │   └── main.go
│   │       ├── install_instance_test.go
│   │       ├── install_instance.go
│   │       ├── library
│   │       │   ├── bm25_test.go
│   │       │   ├── bm25.go
│   │       │   ├── index.go
│   │       │   ├── index.gob
│   │       │   ├── library.go
│   │       │   ├── search_test.go
│   │       │   ├── search.go
│   │       │   ├── tokenizer_test.go
│   │       │   └── tokenizer.go
│   │       ├── manager_test.go
│   │       ├── manager.go
│   │       ├── reconcile_helmrelease_test.go
│   │       ├── reconcile_helmrelease.go
│   │       ├── reconcile_kustomization_test.go
│   │       ├── reconcile_kustomization.go
│   │       ├── reconcile_resourceset_test.go
│   │       ├── reconcile_resourceset.go
│   │       ├── reconcile_source_test.go
│   │       ├── reconcile_source.go
│   │       ├── resume_reconciliation_test.go
│   │       ├── resume_reconciliation.go
│   │       ├── scopes_test.go
│   │       ├── scopes.go
│   │       ├── search_flux_docs_test.go
│   │       ├── search_flux_docs.go
│   │       ├── set_context_test.go
│   │       ├── set_context.go
│   │       ├── suspend_reconciliation_test.go
│   │       ├── suspend_reconciliation.go
│   │       └── testdata
│   │           ├── kubeconfig_golden.yaml
│   │           └── kubeconfig.yaml
│   └── operator
│       └── main.go
├── config
│   ├── crd
│   │   ├── bases
│   │   │   ├── fluxcd.controlplane.io_fluxinstances.yaml
│   │   │   ├── fluxcd.controlplane.io_fluxreports.yaml
│   │   │   ├── fluxcd.controlplane.io_resourcesetinputproviders.yaml
│   │   │   └── fluxcd.controlplane.io_resourcesets.yaml
│   │   ├── kustomization.yaml
│   │   └── kustomizeconfig.yaml
│   ├── data
│   │   ├── flux
│   │   │   ├── v2.2.3
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   └── source-controller.yaml
│   │   │   ├── v2.3.0
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   └── source-controller.yaml
│   │   │   ├── v2.4.0
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   └── source-controller.yaml
│   │   │   ├── v2.5.0
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   └── source-controller.yaml
│   │   │   ├── v2.5.1
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   └── source-controller.yaml
│   │   │   ├── v2.6.0
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   └── source-controller.yaml
│   │   │   ├── v2.6.1
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   └── source-controller.yaml
│   │   │   ├── v2.6.2
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   └── source-controller.yaml
│   │   │   ├── v2.6.3
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   └── source-controller.yaml
│   │   │   ├── v2.6.4
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   └── source-controller.yaml
│   │   │   ├── v2.7.0
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   ├── source-controller.yaml
│   │   │   │   └── source-watcher.yaml
│   │   │   ├── v2.7.1
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   ├── source-controller.yaml
│   │   │   │   └── source-watcher.yaml
│   │   │   ├── v2.7.2
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   ├── source-controller.yaml
│   │   │   │   └── source-watcher.yaml
│   │   │   ├── v2.7.3
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   ├── source-controller.yaml
│   │   │   │   └── source-watcher.yaml
│   │   │   ├── v2.7.4
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   ├── source-controller.yaml
│   │   │   │   └── source-watcher.yaml
│   │   │   └── v2.7.5
│   │   │       ├── helm-controller.yaml
│   │   │       ├── image-automation-controller.yaml
│   │   │       ├── image-reflector-controller.yaml
│   │   │       ├── kustomize-controller.yaml
│   │   │       ├── notification-controller.yaml
│   │   │       ├── policies.yaml
│   │   │       ├── rbac.yaml
│   │   │       ├── source-controller.yaml
│   │   │       └── source-watcher.yaml
│   │   ├── flux-images
│   │   │   ├── v2.2.0
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.2.1
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.2.2
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.2.3
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.3.0
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.4.0
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.5.0
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.5.1
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless-fips.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.6.0
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.6.1
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.6.2
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.6.3
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.6.4
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless-fips.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.7.0
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.7.1
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.7.2
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.7.3
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.7.4
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   ├── v2.7.5
│   │   │   │   ├── enterprise-alpine.yaml
│   │   │   │   ├── enterprise-distroless-fips.yaml
│   │   │   │   ├── enterprise-distroless.yaml
│   │   │   │   └── upstream-alpine.yaml
│   │   │   └── VERSION
│   │   └── flux-vex
│   │       ├── v2.2.json
│   │       ├── v2.3.json
│   │       ├── v2.4.json
│   │       ├── v2.5.json
│   │       ├── v2.6.json
│   │       └── v2.7.json
│   ├── default
│   │   ├── kustomization.yaml
│   │   ├── namespace.yaml
│   │   └── rbac.yaml
│   ├── manager
│   │   ├── account.yaml
│   │   ├── deployment.yaml
│   │   ├── kustomization.yaml
│   │   └── service.yaml
│   ├── mcp
│   │   ├── deployment.yaml
│   │   ├── kustomization.yaml
│   │   └── service.yaml
│   ├── monitoring
│   │   ├── dashboards
│   │   │   ├── flux-k8s-api-performance.json
│   │   │   └── flux-performance.json
│   │   ├── flux-controllers.yaml
│   │   ├── flux-operator.yaml
│   │   └── kustomization.yaml
│   ├── olm
│   │   ├── build
│   │   │   └── Dockerfile
│   │   ├── bundle
│   │   │   ├── manifests
│   │   │   │   ├── flux-operator.clusterserviceversion.yaml
│   │   │   │   ├── flux-operator.service.yaml
│   │   │   │   ├── fluxinstances.fluxcd.controlplane.io.crd.yaml
│   │   │   │   ├── fluxreports.fluxcd.controlplane.io.crd.yaml
│   │   │   │   ├── resourcesetinputproviders.fluxcd.controlplane.io.crd.yaml
│   │   │   │   └── resourcesets.fluxcd.controlplane.io.crd.yaml
│   │   │   ├── metadata
│   │   │   │   └── annotations.yaml
│   │   │   └── tests
│   │   │       └── scorecard
│   │   │           └── config.yaml
│   │   ├── ci.yaml
│   │   └── test
│   │       ├── bundle.Dockerfile
│   │       ├── olm.yaml
│   │       └── opm.Dockerfile
│   ├── rbac
│   │   ├── fluxinstance_editor_role.yaml
│   │   ├── fluxinstance_viewer_role.yaml
│   │   ├── fluxreport_editor_role.yaml
│   │   ├── fluxreport_viewer_role.yaml
│   │   ├── kustomization.yaml
│   │   ├── leader_election_role_binding.yaml
│   │   ├── leader_election_role.yaml
│   │   ├── resourceset_editor_role.yaml
│   │   ├── resourceset_viewer_role.yaml
│   │   ├── role_binding.yaml
│   │   ├── role.yaml
│   │   └── service_account.yaml
│   ├── samples
│   │   ├── fluxcd_v1_fluxinstance.yaml
│   │   ├── fluxcd_v1_fluxreport.yaml
│   │   ├── fluxcd_v1_resourceset.yaml
│   │   ├── fluxcd_v1_resourcesetinputprovider.yaml
│   │   └── kustomization.yaml
│   └── terraform
│       ├── main.tf
│       ├── outputs.tf
│       ├── providers.tf
│       ├── README.md
│       ├── values
│       │   └── components.yaml
│       ├── variables.tf
│       └── versions.tf
├── CONTRIBUTING.md
├── Dockerfile
├── docs
│   ├── api
│   │   └── v1
│   │       ├── fluxinstance.md
│   │       ├── fluxreport.md
│   │       ├── resourceset.md
│   │       └── resourcesetinputprovider.md
│   ├── dev
│   │   └── README.md
│   ├── guides
│   │   ├── instance
│   │   │   ├── instance-controllers.md
│   │   │   ├── instance-customization.md
│   │   │   ├── instance-monitoring.md
│   │   │   ├── instance-sharding.md
│   │   │   └── instance-sync.md
│   │   ├── operator
│   │   │   ├── operator-install.md
│   │   │   └── operator-migration.md
│   │   └── resourcesets
│   │       ├── rset-app-definition.md
│   │       ├── rset-github-pull-requests.md
│   │       ├── rset-gitlab-environments.md
│   │       ├── rset-gitlab-merge-requests.md
│   │       ├── rset-image-automation.md
│   │       ├── rset-introduction.md
│   │       └── rset-time-based-delivery.md
│   ├── lkm
│   │   └── README.md
│   ├── logo
│   │   ├── flux-operator-banner.png
│   │   ├── flux-operator-banner.svg
│   │   ├── flux-operator-icon.png
│   │   ├── flux-operator-icon.svg
│   │   ├── flux-operator-logo.png
│   │   └── flux-operator-logo.svg
│   ├── mcp
│   │   ├── instructions.md
│   │   ├── mcp-config.md
│   │   ├── mcp-install.md
│   │   ├── mcp-prompting.md
│   │   ├── prompts.md
│   │   └── tools.md
│   └── web
│       ├── web-config-api.md
│       ├── web-ingress.md
│       ├── web-sso-dex.md
│       ├── web-sso-keycloak.md
│       ├── web-sso-openshift.md
│       ├── web-standalone.md
│       └── web-user-management.md
├── go.mod
├── go.sum
├── hack
│   ├── boilerplate.go.txt
│   ├── build-dist-manifests.sh
│   ├── build-olm-images.sh
│   ├── build-olm-manifests.sh
│   ├── install-operator-sdk.sh
│   ├── prep-release.sh
│   ├── vendor-flux-manifests.sh
│   └── web-ui-load-test.sh
├── internal
│   ├── builder
│   │   ├── build_test.go
│   │   ├── build.go
│   │   ├── components.go
│   │   ├── digest.go
│   │   ├── images_test.go
│   │   ├── images.go
│   │   ├── options.go
│   │   ├── preflight_test.go
│   │   ├── preflight.go
│   │   ├── profiles.go
│   │   ├── pull.go
│   │   ├── resourceset_test.go
│   │   ├── resourceset.go
│   │   ├── result.go
│   │   ├── semver_test.go
│   │   ├── semver.go
│   │   ├── templates.go
│   │   ├── testdata
│   │   │   ├── flux
│   │   │   │   ├── v2.2.0
│   │   │   │   │   └── .gitkeep
│   │   │   │   ├── v2.2.1
│   │   │   │   │   └── .gitkeep
│   │   │   │   └── v2.3.0
│   │   │   │       └── .gitkeep
│   │   │   ├── flux-images
│   │   │   │   └── v2.3.0
│   │   │   │       ├── enterprise-alpine.yaml
│   │   │   │       ├── enterprise-distroless.yaml
│   │   │   │       └── upstream-alpine.yaml
│   │   │   ├── resourceset
│   │   │   │   ├── dedup.golden.yaml
│   │   │   │   ├── dedup.yaml
│   │   │   │   ├── empty.yaml
│   │   │   │   ├── exclude.golden.yaml
│   │   │   │   ├── exclude.yaml
│   │   │   │   ├── invalid-output.yaml
│   │   │   │   ├── missing-inputs.yaml
│   │   │   │   ├── multi-doc-template.golden.yaml
│   │   │   │   ├── multi-doc-template.yaml
│   │   │   │   ├── nestedinputs.golden.yaml
│   │   │   │   ├── nestedinputs.yaml
│   │   │   │   ├── noinputs.golden.yaml
│   │   │   │   ├── noinputs.yaml
│   │   │   │   ├── slugify.golden.yaml
│   │   │   │   └── slugify.yaml
│   │   │   ├── v2.3.0
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   └── source-controller.yaml
│   │   │   ├── v2.3.0-golden
│   │   │   │   ├── default.kustomization.yaml
│   │   │   │   ├── patches.kustomization.yaml
│   │   │   │   ├── profiles.kustomization.yaml
│   │   │   │   ├── sharding.kustomization.yaml
│   │   │   │   ├── storage.kustomization.yaml
│   │   │   │   └── sync.kustomization.yaml
│   │   │   ├── v2.6.0
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   └── source-controller.yaml
│   │   │   ├── v2.6.0-golden
│   │   │   │   ├── shard1.kustomization.yaml
│   │   │   │   ├── shard2.kustomization.yaml
│   │   │   │   ├── sharding.kustomization.yaml
│   │   │   │   ├── size.large.kustomization.yaml
│   │   │   │   ├── size.medium.kustomization.yaml
│   │   │   │   └── size.small.kustomization.yaml
│   │   │   ├── v2.7.0
│   │   │   │   ├── helm-controller.yaml
│   │   │   │   ├── image-automation-controller.yaml
│   │   │   │   ├── image-reflector-controller.yaml
│   │   │   │   ├── kustomize-controller.yaml
│   │   │   │   ├── notification-controller.yaml
│   │   │   │   ├── policies.yaml
│   │   │   │   ├── rbac.yaml
│   │   │   │   ├── source-controller.yaml
│   │   │   │   └── source-watcher.yaml
│   │   │   └── v2.7.0-golden
│   │   │       └── source-watcher.kustomization.yaml
│   │   └── workload_identity.go
│   ├── controller
│   │   ├── common.go
│   │   ├── entitlement_controller_test.go
│   │   ├── entitlement_controller.go
│   │   ├── fluxinstance_artifact_controller_test.go
│   │   ├── fluxinstance_artifact_controller.go
│   │   ├── fluxinstance_artifact_manager_test.go
│   │   ├── fluxinstance_artifact_manager.go
│   │   ├── fluxinstance_controller_test.go
│   │   ├── fluxinstance_controller.go
│   │   ├── fluxinstance_manager.go
│   │   ├── fluxinstance_migrator.go
│   │   ├── fluxinstance_uninstaller.go
│   │   ├── fluxreport_controller_test.go
│   │   ├── fluxreport_controller.go
│   │   ├── resourceset_controller_test.go
│   │   ├── resourceset_controller.go
│   │   ├── resourceset_manager_test.go
│   │   ├── resourceset_manager.go
│   │   ├── resourcesetinputprovider_controller_git_test.go
│   │   ├── resourcesetinputprovider_controller_oci_test.go
│   │   ├── resourcesetinputprovider_controller_test.go
│   │   ├── resourcesetinputprovider_controller.go
│   │   ├── resourcesetinputprovider_manager.go
│   │   ├── suite_test.go
│   │   └── testdata
│   │       └── rsa-private-key.pem
│   ├── entitlement
│   │   ├── aws.go
│   │   ├── client_test.go
│   │   ├── client.go
│   │   ├── default_test.go
│   │   └── default.go
│   ├── filtering
│   │   ├── filters_test.go
│   │   └── filters.go
│   ├── gitprovider
│   │   ├── azuredevops_test.go
│   │   ├── azuredevops.go
│   │   ├── github_test.go
│   │   ├── github.go
│   │   ├── gitlab_test.go
│   │   ├── gitlab.go
│   │   ├── interface.go
│   │   ├── options.go
│   │   ├── result_test.go
│   │   └── result.go
│   ├── inputs
│   │   ├── combine_test.go
│   │   ├── combine.go
│   │   ├── flattener.go
│   │   ├── id.go
│   │   ├── json_test.go
│   │   ├── json.go
│   │   ├── keys_test.go
│   │   ├── keys.go
│   │   ├── permuter_test.go
│   │   ├── permuter.go
│   │   └── provider.go
│   ├── install
│   │   ├── autoupdate.go
│   │   ├── client.go
│   │   ├── credentials.go
│   │   ├── deploy.go
│   │   ├── download.go
│   │   ├── events.go
│   │   ├── installer.go
│   │   ├── options.go
│   │   └── uninstall.go
│   ├── inventory
│   │   ├── inventory_test.go
│   │   ├── inventory.go
│   │   ├── reader_test.go
│   │   ├── reader.go
│   │   └── testdata
│   │       ├── inventory1.yaml
│   │       └── inventory2.yaml
│   ├── lkm
│   │   ├── artifacts_attestation_test.go
│   │   ├── artifacts_attestation.go
│   │   ├── attestation_test.go
│   │   ├── attestation.go
│   │   ├── doc.go
│   │   ├── errors.go
│   │   ├── fetch_test.go
│   │   ├── fetch.go
│   │   ├── jwe_test.go
│   │   ├── jwe.go
│   │   ├── jwt_test.go
│   │   ├── jwt.go
│   │   ├── keygen_test.go
│   │   ├── keygen.go
│   │   ├── keyset_test.go
│   │   ├── keyset.go
│   │   ├── license_test.go
│   │   ├── license.go
│   │   ├── licensekey.go
│   │   ├── manifests_attestation_test.go
│   │   ├── manifests_attestation.go
│   │   ├── revocation_test.go
│   │   └── revocation.go
│   ├── notifier
│   │   └── notifier.go
│   ├── reporter
│   │   ├── cluster.go
│   │   ├── components.go
│   │   ├── crds.go
│   │   ├── distribution.go
│   │   ├── metrics_test.go
│   │   ├── metrics.go
│   │   ├── reconcilers.go
│   │   ├── reporter.go
│   │   └── sync.go
│   ├── schedule
│   │   ├── scheduler_test.go
│   │   └── scheduler.go
│   ├── tests
│   │   ├── fluxinstance
│   │   │   ├── health_check_test.go
│   │   │   └── suite_test.go
│   │   └── resourceset
│   │       ├── health_check_test.go
│   │       └── suite_test.go
│   ├── testutils
│   │   ├── log.go
│   │   └── time.go
│   └── web
│       ├── action_test.go
│       ├── action.go
│       ├── auth
│       │   ├── claims_test.go
│       │   ├── claims.go
│       │   ├── cookies_test.go
│       │   ├── cookies.go
│       │   ├── errors_test.go
│       │   ├── errors.go
│       │   ├── middlewares_test.go
│       │   ├── middlewares.go
│       │   ├── oauth2_test.go
│       │   ├── oauth2.go
│       │   └── oidc.go
│       ├── config
│       │   ├── authentication_types_test.go
│       │   ├── authentication_types.go
│       │   ├── config_types_test.go
│       │   ├── config_types.go
│       │   ├── groupversion_info.go
│       │   ├── loader_test.go
│       │   ├── loader.go
│       │   ├── user_actions_types_test.go
│       │   ├── user_actions_types.go
│       │   └── watcher.go
│       ├── events_test.go
│       ├── events.go
│       ├── favorites_test.go
│       ├── favorites.go
│       ├── fs.go
│       ├── handler.go
│       ├── inventory.go
│       ├── kubeclient
│       │   ├── client_test.go
│       │   ├── client.go
│       │   └── suite_test.go
│       ├── middlewares_test.go
│       ├── middlewares.go
│       ├── report_test.go
│       ├── report.go
│       ├── resource_test.go
│       ├── resource.go
│       ├── resources_test.go
│       ├── resources.go
│       ├── search_test.go
│       ├── search.go
│       ├── server_test.go
│       ├── server.go
│       ├── source.go
│       ├── suite_test.go
│       ├── user
│       │   ├── user_test.go
│       │   └── user.go
│       ├── workload_test.go
│       ├── workload.go
│       ├── workloads_test.go
│       └── workloads.go
├── LICENSE
├── Makefile
├── PROJECT
├── README.md
├── SECURITY.md
├── test
│   ├── e2e
│   │   ├── e2e_suite_test.go
│   │   ├── e2e_test.go
│   │   ├── instance_test.go
│   │   └── utils.go
│   └── olm
│       ├── e2e_suite_test.go
│       ├── e2e_test.go
│       ├── instance_test.go
│       └── scorecard_test.go
└── web
    ├── .gitignore
    ├── embed.go
    ├── eslint.config.js
    ├── index.html
    ├── package-lock.json
    ├── package.json
    ├── postcss.config.js
    ├── public
    │   ├── favicon.svg
    │   └── fonts
    │       └── inter.woff2
    ├── README.md
    ├── src
    │   ├── app.jsx
    │   ├── app.test.jsx
    │   ├── components
    │   │   ├── auth
    │   │   │   ├── LoginPage.jsx
    │   │   │   └── LoginPage.test.jsx
    │   │   ├── dashboards
    │   │   │   ├── cluster
    │   │   │   │   ├── ClusterPage.jsx
    │   │   │   │   ├── ClusterPage.test.jsx
    │   │   │   │   ├── ControllersPanel.jsx
    │   │   │   │   ├── ControllersPanel.test.jsx
    │   │   │   │   ├── InfoPanel.jsx
    │   │   │   │   ├── InfoPanel.test.jsx
    │   │   │   │   ├── OverallStatusPanel.jsx
    │   │   │   │   ├── OverallStatusPanel.test.jsx
    │   │   │   │   ├── ReconcilersPanel.jsx
    │   │   │   │   ├── ReconcilersPanel.test.jsx
    │   │   │   │   ├── SyncPanel.jsx
    │   │   │   │   └── SyncPanel.test.jsx
    │   │   │   ├── common
    │   │   │   │   ├── panel.jsx
    │   │   │   │   ├── panel.test.jsx
    │   │   │   │   ├── yaml.jsx
    │   │   │   │   └── yaml.test.jsx
    │   │   │   └── resource
    │   │   │       ├── ActionBar.jsx
    │   │   │       ├── ActionBar.test.jsx
    │   │   │       ├── ArtifactPanel.jsx
    │   │   │       ├── ArtifactPanel.test.jsx
    │   │   │       ├── ExportedInputsPanel.jsx
    │   │   │       ├── ExportedInputsPanel.test.jsx
    │   │   │       ├── GraphTabContent.jsx
    │   │   │       ├── GraphTabContent.test.jsx
    │   │   │       ├── HistoryTimeline.jsx
    │   │   │       ├── HistoryTimeline.test.jsx
    │   │   │       ├── InputsPanel.jsx
    │   │   │       ├── InputsPanel.test.jsx
    │   │   │       ├── InventoryPanel.jsx
    │   │   │       ├── InventoryPanel.test.jsx
    │   │   │       ├── ReconcilerPanel.jsx
    │   │   │       ├── ReconcilerPanel.test.jsx
    │   │   │       ├── ResourcePage.jsx
    │   │   │       ├── ResourcePage.test.jsx
    │   │   │       ├── SourcePanel.jsx
    │   │   │       ├── SourcePanel.test.jsx
    │   │   │       ├── WorkloadsTabContent.jsx
    │   │   │       └── WorkloadsTabContent.test.jsx
    │   │   ├── favorites
    │   │   │   ├── FavoriteCard.jsx
    │   │   │   ├── FavoriteCard.test.jsx
    │   │   │   ├── FavoritesHeader.jsx
    │   │   │   ├── FavoritesHeader.test.jsx
    │   │   │   ├── FavoritesPage.jsx
    │   │   │   ├── FavoritesPage.test.jsx
    │   │   │   ├── FavoritesSearch.jsx
    │   │   │   └── FavoritesSearch.test.jsx
    │   │   ├── layout
    │   │   │   ├── ConnectionStatus.jsx
    │   │   │   ├── ConnectionStatus.test.jsx
    │   │   │   ├── Footer.jsx
    │   │   │   ├── Footer.test.jsx
    │   │   │   ├── Header.jsx
    │   │   │   ├── Header.test.jsx
    │   │   │   ├── Icons.jsx
    │   │   │   ├── NotFoundPage.jsx
    │   │   │   ├── NotFoundPage.test.jsx
    │   │   │   ├── ThemeToggle.jsx
    │   │   │   ├── ThemeToggle.test.jsx
    │   │   │   ├── UserMenu.jsx
    │   │   │   └── UserMenu.test.jsx
    │   │   └── search
    │   │       ├── EventList.jsx
    │   │       ├── EventList.test.jsx
    │   │       ├── FilterForm.jsx
    │   │       ├── FilterForm.test.jsx
    │   │       ├── QuickSearch.jsx
    │   │       ├── QuickSearch.test.jsx
    │   │       ├── ResourceDetailsView.jsx
    │   │       ├── ResourceDetailsView.test.jsx
    │   │       ├── ResourceList.jsx
    │   │       ├── ResourceList.test.jsx
    │   │       ├── StatusChart.jsx
    │   │       └── StatusChart.test.jsx
    │   ├── index.css
    │   ├── main.jsx
    │   ├── mock
    │   │   ├── action.js
    │   │   ├── events.js
    │   │   ├── events.test.js
    │   │   ├── report.js
    │   │   ├── resource.js
    │   │   ├── resources.js
    │   │   ├── resources.test.js
    │   │   ├── workload.js
    │   │   └── workload.test.js
    │   └── utils
    │       ├── constants.js
    │       ├── cookies.js
    │       ├── cookies.test.js
    │       ├── favorites.js
    │       ├── favorites.test.js
    │       ├── fetch.js
    │       ├── fetch.test.js
    │       ├── hash.js
    │       ├── hash.test.js
    │       ├── meta.js
    │       ├── meta.test.js
    │       ├── navHistory.js
    │       ├── navHistory.test.js
    │       ├── routing.js
    │       ├── routing.test.js
    │       ├── scroll.js
    │       ├── scroll.test.js
    │       ├── status.js
    │       ├── status.test.js
    │       ├── theme.js
    │       ├── theme.test.js
    │       ├── time.js
    │       ├── time.test.js
    │       ├── version.js
    │       └── version.test.js
    ├── tailwind.config.js
    ├── vite.config.js
    └── vitest.setup.js
```

# Files

--------------------------------------------------------------------------------
/internal/reporter/crds.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package reporter

import (
	"context"
	"errors"
	"fmt"

	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func (r *FluxStatusReporter) listCRDs(ctx context.Context) ([]metav1.GroupVersionKind, error) {
	var list apiextensionsv1.CustomResourceDefinitionList
	if err := r.List(ctx, &list, client.InNamespace(""), r.labelSelector); err != nil {
		return nil, fmt.Errorf("failed to list CRDs: %w", err)
	}

	if len(list.Items) == 0 {
		return nil, errors.New("no Flux CRDs found")
	}

	gvkList := make([]metav1.GroupVersionKind, len(list.Items))
	for i, crd := range list.Items {
		gvk := metav1.GroupVersionKind{
			Group: crd.Spec.Group,
			Kind:  crd.Spec.Names.Kind,
		}
		versions := crd.Status.StoredVersions
		if len(versions) > 0 {
			gvk.Version = versions[len(versions)-1]
		} else {
			return nil, fmt.Errorf("no stored versions found for CRD %s", crd.Name)
		}
		gvkList[i] = gvk
	}

	return gvkList, nil
}

func gvkFor(kind string, crds []metav1.GroupVersionKind) *metav1.GroupVersionKind {
	for _, gvk := range crds {
		if gvk.Kind == kind {
			return &gvk
		}
	}
	return nil
}

```

--------------------------------------------------------------------------------
/internal/builder/testdata/resourceset/exclude.yaml:
--------------------------------------------------------------------------------

```yaml
apiVersion: fluxcd.controlplane.io/v1
kind: ResourceSet
metadata:
  name: tenants
  namespace: flux-system
spec:
  inputs:
    - tenant: sre
      role: "cluster-admin"
    - tenant: dev
      role: "namespace-admin"
  resources:
    - apiVersion: v1
      kind: Namespace
      metadata:
        name: << inputs.tenant >>
    - apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: flux
        namespace: << inputs.tenant >>
    - apiVersion: rbac.authorization.k8s.io/v1
      kind: RoleBinding
      metadata:
        name: flux
        namespace: << inputs.tenant >>
      subjects:
        - kind: ServiceAccount
          name: flux
          namespace: << inputs.tenant >>
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: ClusterRole
        name: admin
    - apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRoleBinding
      metadata:
        name: flux-<< inputs.tenant >>
        annotations:
          fluxcd.controlplane.io/reconcile: << if eq inputs.role "cluster-admin" >>enabled<< else >>disabled<< end >>
      subjects:
        - kind: ServiceAccount
          name: flux
          namespace: << inputs.tenant >>
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: ClusterRole
        name: cluster-admin

```

--------------------------------------------------------------------------------
/cmd/mcp/k8s/client_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package k8s

import (
	"testing"

	. "github.com/onsi/gomega"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestParseGroupVersionKind(t *testing.T) {
	kubeClient := Client{
		Client: fake.NewClientBuilder().WithScheme(NewTestScheme()).Build(),
	}

	tests := []struct {
		name       string
		apiVersion string
		kind       string
		result     string
		matchErr   string
	}{
		{
			name:       "valid inputs",
			apiVersion: "fluxcd.controlplane.io/v1",
			kind:       "ResourceSet",
			result:     "fluxcd.controlplane.io/v1, Kind=ResourceSet",
		},
		{
			name:       "invalid api version",
			apiVersion: "helm.toolkit.fluxcd.io/v1/v2",
			kind:       "HelmRelease",
			matchErr:   "unexpected",
		},
		{
			name:       "invalid kind",
			apiVersion: "fluxcd.controlplane.io/v1",
			matchErr:   "not specified",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewWithT(t)

			gvk, err := kubeClient.ParseGroupVersionKind(tt.apiVersion, tt.kind)
			if tt.matchErr != "" {
				g.Expect(err).To(HaveOccurred())
				g.Expect(err.Error()).To(ContainSubstring(tt.matchErr))
			} else {
				g.Expect(err).NotTo(HaveOccurred())
				g.Expect(gvk.String()).To(Equal(tt.result))
			}
		})
	}

}

```

--------------------------------------------------------------------------------
/cmd/cli/testdata/build_resourceset/rset-standalone.yaml:
--------------------------------------------------------------------------------

```yaml
---
apiVersion: fluxcd.controlplane.io/v1
kind: ResourceSet
metadata:
  name: apps
  namespace: test
spec:
  inputs:
    - tenantName: team1
      id: 340788154
      applications:
        - name: app1
          envs:
            - name: staging
              version: v1.0.1
            - name: production
              version: v1.0.0
  resourcesTemplate: |
    <<- $id := inputs.id >>    
    <<- $tenant := inputs.tenantName >>
    <<- range $app := inputs.applications >>
    <<- $appName := $app.name >>
    <<- range $env := $app.envs >>
    ---
    apiVersion: source.toolkit.fluxcd.io/v1
    kind: OCIRepository
    metadata:
      name: << $appName >>
      namespace: << $tenant >>-<< $env.name >>
      annotations:
        fluxcd.controlplane.io/id: << $id | quote >>
    spec:
      interval: 10m
      url: oci://registry.example.com/<< $appName >>
      ref:
        tag: << $env.version >>
    ---
    apiVersion: kustomize.toolkit.fluxcd.io/v1
    kind: Kustomization
    metadata:
      name: << $appName >>
      namespace: << $tenant >>-<< $env.name >>
      annotations:
        fluxcd.controlplane.io/id: << $id | quote >>
    spec:
      interval: 1h
      prune: true
      sourceRef:
        kind: OCIRepository
        name: << $appName >>
      path: ./
    <<- end >>
    <<- end >>

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/get_instance_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"
	"testing"
	"time"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
	cli "k8s.io/cli-runtime/pkg/genericclioptions"

	"github.com/controlplaneio-fluxcd/flux-operator/cmd/mcp/k8s"
)

func TestManager_HandleGetFluxInstance(t *testing.T) {
	configFile := "testdata/kubeconfig.yaml"
	t.Setenv("KUBECONFIG", configFile)

	m := &Manager{
		kubeconfig: k8s.NewKubeConfig(),
		kubeClient: k8s.NewClientFactory(cli.NewConfigFlags(false)),
		timeout:    time.Second,
	}

	request := &mcp.CallToolRequest{
		Params: &mcp.CallToolParamsRaw{
			Name: "get_flux_instance",
		},
	}

	tests := []struct {
		testName string
		matchErr string
	}{
		{
			testName: "fails with invalid kubeconfig",
			matchErr: "Failed to get Kubernetes client",
		},
	}

	for _, test := range tests {
		t.Run(test.testName, func(t *testing.T) {
			g := NewWithT(t)

			result, content, err := m.HandleGetFluxInstance(context.Background(), request, struct{}{})
			g.Expect(err).ToNot(HaveOccurred())
			textContent, ok := result.Content[0].(*mcp.TextContent)
			g.Expect(ok).To(BeTrue())

			g.Expect(result.IsError).To(BeTrue())
			g.Expect(textContent.Text).To(ContainSubstring(test.matchErr))
			_ = content
		})
	}
}

```

--------------------------------------------------------------------------------
/test/olm/instance_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package e2eolm

import (
	"os/exec"
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"

	utils "github.com/controlplaneio-fluxcd/flux-operator/test/e2e"
)

var _ = Describe("FluxInstance", Ordered, func() {
	Context("installation", func() {
		It("should run successfully", func() {
			By("reconcile FluxInstance")
			verifyFluxInstanceReconcile := func() error {
				cmd := exec.Command("kubectl", "apply",
					"-f", "config/samples/fluxcd_v1_fluxinstance.yaml", "-n", namespace,
				)
				_, err := utils.Run(cmd, "/test/olm")
				ExpectWithOffset(2, err).NotTo(HaveOccurred())

				cmd = exec.Command("kubectl", "wait", "FluxInstance/flux", "-n", namespace,
					"--for=condition=Ready", "--timeout=5m",
				)
				_, err = utils.Run(cmd, "/test/olm")
				ExpectWithOffset(2, err).NotTo(HaveOccurred())
				return nil
			}
			EventuallyWithOffset(1, verifyFluxInstanceReconcile, 5*time.Minute, 10*time.Second).Should(Succeed())
		})
	})

	Context("uninstallation", func() {
		It("should run successfully", func() {
			By("delete FluxInstance")
			cmd := exec.Command("kubectl", "delete", "FluxInstance/flux",
				"--timeout=30s", "-n", namespace)
			_, err := utils.Run(cmd, "/test/olm")
			Expect(err).NotTo(HaveOccurred())
		})
	})
})

```

--------------------------------------------------------------------------------
/cmd/cli/testdata/build_resourceset/golden-labeled.yaml:
--------------------------------------------------------------------------------

```yaml
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
  labels:
    resourceset.fluxcd.controlplane.io/name: app1
    resourceset.fluxcd.controlplane.io/namespace: apps
  name: app1-tenant1
  namespace: apps
spec:
  interval: 10m
  ref:
    semver: 6.7.x
  url: oci://my.registry/org/charts/app1
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
  labels:
    resourceset.fluxcd.controlplane.io/name: app1
    resourceset.fluxcd.controlplane.io/namespace: apps
  name: app1-tenant2
  namespace: apps
spec:
  interval: 10m
  ref:
    semver: 6.6.x
  url: oci://my.registry/org/charts/app1
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  labels:
    resourceset.fluxcd.controlplane.io/name: app1
    resourceset.fluxcd.controlplane.io/namespace: apps
  name: app1-tenant1
  namespace: apps
spec:
  chartRef:
    kind: OCIRepository
    name: app1-tenant1
  interval: 1h
  releaseName: app1-tenant1
  values:
    replicaCount: 2
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  labels:
    resourceset.fluxcd.controlplane.io/name: app1
    resourceset.fluxcd.controlplane.io/namespace: apps
  name: app1-tenant2
  namespace: apps
spec:
  chartRef:
    kind: OCIRepository
    name: app1-tenant2
  interval: 1h
  releaseName: app1-tenant2
  values:
    replicaCount: 3

```

--------------------------------------------------------------------------------
/cmd/cli/testdata/build_resourceset/golden-named.yaml:
--------------------------------------------------------------------------------

```yaml
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
  labels:
    resourceset.fluxcd.controlplane.io/name: app1
    resourceset.fluxcd.controlplane.io/namespace: apps
  name: app1-tenant1
  namespace: apps
spec:
  interval: 10m
  ref:
    semver: 7.8.x
  url: oci://my.registry/org/charts/app1
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
  labels:
    resourceset.fluxcd.controlplane.io/name: app1
    resourceset.fluxcd.controlplane.io/namespace: apps
  name: app1-tenant2
  namespace: apps
spec:
  interval: 10m
  ref:
    semver: 5.9.x
  url: oci://my.registry/org/charts/app1
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  labels:
    resourceset.fluxcd.controlplane.io/name: app1
    resourceset.fluxcd.controlplane.io/namespace: apps
  name: app1-tenant1
  namespace: apps
spec:
  chartRef:
    kind: OCIRepository
    name: app1-tenant1
  interval: 1h
  releaseName: app1-tenant1
  values:
    replicaCount: 1
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  labels:
    resourceset.fluxcd.controlplane.io/name: app1
    resourceset.fluxcd.controlplane.io/namespace: apps
  name: app1-tenant2
  namespace: apps
spec:
  chartRef:
    kind: OCIRepository
    name: app1-tenant2
  interval: 1h
  releaseName: app1-tenant2
  values:
    replicaCount: 2

```

--------------------------------------------------------------------------------
/config/samples/fluxcd_v1_resourceset.yaml:
--------------------------------------------------------------------------------

```yaml
apiVersion: fluxcd.controlplane.io/v1
kind: ResourceSet
metadata:
  name: podinfo
  namespace: default
  annotations:
    fluxcd.controlplane.io/reconcile: "enabled"
    fluxcd.controlplane.io/reconcileEvery: "30m"
    fluxcd.controlplane.io/reconcileTimeout: "5m"
spec:
  dependsOn:
    - apiVersion: apiextensions.k8s.io/v1
      kind: CustomResourceDefinition
      name: helmreleases.helm.toolkit.fluxcd.io
      ready: true
  commonMetadata:
    labels:
      app.kubernetes.io/name: podinfo
  inputs:
    - tenant: "team1"
      version: "6.7.x"
      replicas: "2"
    - tenant: "team2"
      version: "6.6.x"
      replicas: "3"
  resources:
    - apiVersion: source.toolkit.fluxcd.io/v1beta2
      kind: OCIRepository
      metadata:
        name: podinfo-<< inputs.tenant >>
        namespace: default
      spec:
        interval: 10m
        url: oci://ghcr.io/stefanprodan/charts/podinfo
        ref:
          semver: << inputs.version | quote >>
    - apiVersion: helm.toolkit.fluxcd.io/v2
      kind: HelmRelease
      metadata:
        name: podinfo-<< inputs.tenant >>
        namespace: default
      spec:
        interval: 1h
        releaseName: podinfo-<< inputs.tenant >>
        chartRef:
          kind: OCIRepository
          name: podinfo-<< inputs.tenant >>
        values:
          replicaCount: << inputs.replicas | int >>

```

--------------------------------------------------------------------------------
/cmd/cli/wait_instance.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package main

import (
	"context"
	"fmt"

	"github.com/spf13/cobra"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

var waitInstanceCmd = &cobra.Command{
	Use:   "instance [name]",
	Short: "Wait for FluxInstance to become ready",
	Example: `  # Wait for an instance to become ready
  flux-operator -n flux-system wait instance flux

  # Wait for an instance to become ready with a custom timeout
  flux-operator -n flux-system wait instance flux --timeout=5m
`,
	Args:              cobra.ExactArgs(1),
	RunE:              waitInstanceCmdRun,
	ValidArgsFunction: resourceNamesCompletionFunc(fluxcdv1.GroupVersion.WithKind(fluxcdv1.FluxInstanceKind)),
}

func init() {
	waitCmd.AddCommand(waitInstanceCmd)
}

func waitInstanceCmdRun(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("name is required")
	}

	name := args[0]
	gvk := fluxcdv1.GroupVersion.WithKind(fluxcdv1.FluxInstanceKind)

	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
	defer cancel()

	rootCmd.Println(`◎`, "Waiting for instance to become ready...")
	msg, err := waitForResourceReconciliation(ctx, gvk, name, *kubeconfigArgs.Namespace, "", rootArgs.timeout)
	if err != nil {
		return err
	}

	rootCmd.Println(`✔`, msg)
	return nil
}

```

--------------------------------------------------------------------------------
/hack/build-dist-manifests.sh:
--------------------------------------------------------------------------------

```bash
#!/usr/bin/env bash

# Copyright 2024 Stefan Prodan.
# SPDX-License-Identifier: AGPL-3.0

set -euo pipefail

REPOSITORY_ROOT=$(git rev-parse --show-toplevel)
DEST_DIR="${REPOSITORY_ROOT}/disto"

info() {
    echo '[INFO] ' "$@"
}

fatal() {
    echo '[ERROR] ' "$@" >&2
    exit 1
}

rm -rf ${DEST_DIR}
mkdir -p ${DEST_DIR}/flux-operator
kustomize build config/default > ${DEST_DIR}/flux-operator/install.yaml
info "operator manifests generated to disto/flux-operator"

mkdir -p ${DEST_DIR}/flux-operator-mcp
kustomize build config/mcp > ${DEST_DIR}/flux-operator-mcp/install.yaml
info "MCP server manifests generated to disto/flux-operator-mcp"

mkdir -p ${DEST_DIR}/flux
cp -r config/data/flux/* ${DEST_DIR}/flux/
info "flux manifests copied to disto/flux"

info "downloading distro repository"
curl -sLO https://github.com/controlplaneio-fluxcd/distribution/archive/refs/heads/main.tar.gz
tar xzf main.tar.gz -C "${DEST_DIR}"

mkdir -p "${DEST_DIR}/flux-images"
cp -r ${DEST_DIR}/distribution-main/images/* ${DEST_DIR}/flux-images/
info "flux image manifests copied to disto/flux-images"

mkdir -p "${DEST_DIR}/flux-vex"
cp -r ${DEST_DIR}/distribution-main/vex/* ${DEST_DIR}/flux-vex/
info "flux OpenVEX documents copied to disto/flux-vex"

rm -rf ${DEST_DIR}/distribution-main
rm -rf main.tar.gz

info "all manifests generated to disto/"
tree -d ${DEST_DIR}

```

--------------------------------------------------------------------------------
/internal/inputs/keys_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package inputs_test

import (
	"testing"

	. "github.com/onsi/gomega"

	"github.com/controlplaneio-fluxcd/flux-operator/internal/inputs"
)

func TestNormalizeKeyForTemplate(t *testing.T) {
	for _, tt := range []struct {
		key         string
		expectedKey string
	}{
		{key: "My_ResourceSet", expectedKey: "my_resourceset"},
		{key: "My-ResourceSet", expectedKey: "my_resourceset"},
		{key: "My/ResourceSet", expectedKey: "my_resourceset"},
		{key: "My,ResourceSet", expectedKey: "my_resourceset"},
		{key: "My:ResourceSet", expectedKey: "my_resourceset"},
		{key: "My.ResourceSet", expectedKey: "my_resourceset"},
		{key: "My!ResourceSet", expectedKey: "my_resourceset"},
		{key: "My?ResourceSet", expectedKey: "my_resourceset"},
		{key: "My@ResourceSet", expectedKey: "my_resourceset"},
		{key: "My#ResourceSet", expectedKey: "my_resourceset"},
		{key: "My ResourceSet", expectedKey: "my_resourceset"},
		{key: "_a---b_-_", expectedKey: "a_b"},
		{key: "A0..B", expectedKey: "a0_b"},
		{key: "A  B", expectedKey: "a_b"},
		{key: "A@@B", expectedKey: "a_b"},
		{key: "A##B", expectedKey: "a_b"},
		{key: "__", expectedKey: ""},
	} {
		t.Run(tt.key, func(t *testing.T) {
			g := NewWithT(t)
			key := inputs.NormalizeKeyForTemplate(tt.key)
			g.Expect(string(key)).To(Equal(tt.expectedKey))
		})
	}
}

```

--------------------------------------------------------------------------------
/cmd/cli/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Build the Flux Operator CLI binary using Docker's Debian image.
FROM --platform=${BUILDPLATFORM} golang:1.25 AS builder
ARG TARGETOS
ARG TARGETARCH
ARG VERSION
ARG KUBECTL_VER=1.33.0
WORKDIR /workspace

RUN apt-get -y install curl

# Copy the Go Modules manifests.
COPY go.mod go.mod
COPY go.sum go.sum

# Cache the Go Modules
RUN go mod download

# Copy the Go sources.
COPY cmd/cli/ cmd/cli/
COPY api/ api/
COPY internal/ internal/

# Build the Flux Operator CLI binary.
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
    go build -ldflags="-s -w -X main.VERSION=${VERSION}" \
    -a -o /usr/local/bin/flux-operator ./cmd/cli/

# Verify the Flux Operator CLI binary.
RUN flux-operator version --client

# Download the kubectl binary.
RUN curl -sL https://dl.k8s.io/release/v${KUBECTL_VER}/bin/${TARGETOS}/${TARGETARCH}/kubectl \
    -o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl

# Verify the kubectl binary.
RUN kubectl version --client

# Distribute the binaries using Google's Distroless image.
FROM gcr.io/distroless/static:nonroot

# Copy the license.
COPY LICENSE /licenses/LICENSE

# Copy the binaries.
COPY --from=builder --chmod=777 /usr/local/bin/flux-operator /usr/local/bin/
COPY --from=builder --chmod=777 /usr/local/bin/kubectl /usr/local/bin/

# Run the binaries under a non-root user.
USER 65532:65532
ENTRYPOINT ["flux-operator"]

```

--------------------------------------------------------------------------------
/cmd/cli/suspend_resource.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/spf13/cobra"
)

var suspendResourceCmd = &cobra.Command{
	Use:   "resource [kind/name]",
	Short: "suspend Flux resource reconciliation",
	Example: `  # Suspend the reconciliation of a Flux Kustomization
  flux-operator -n apps suspend resource Kustomization/my-app
`,
	Args:              cobra.ExactArgs(1),
	RunE:              suspendResourceCmdRun,
	ValidArgsFunction: resourceKindNameCompletionFunc(true),
}

func init() {
	suspendCmd.AddCommand(suspendResourceCmd)
}

func suspendResourceCmdRun(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("resource name is required")
	}

	parts := strings.Split(args[0], "/")
	if len(parts) != 2 {
		return fmt.Errorf("resource name must be in the format <kind>/<name>, e.g., HelmRelease/my-app")
	}

	kind := parts[0]
	name := parts[1]
	now := timeNow()

	gvk, err := preferredFluxGVK(kind, kubeconfigArgs)
	if err != nil {
		return fmt.Errorf("unable to get gvk for kind %s : %w", kind, err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
	defer cancel()

	err = toggleSuspension(ctx, *gvk, name, *kubeconfigArgs.Namespace, now, true)
	if err != nil {
		return err
	}

	rootCmd.Println(`✔`, "Reconciliation suspended")
	return nil
}

```

--------------------------------------------------------------------------------
/web/public/favicon.svg:
--------------------------------------------------------------------------------

```
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
    <path id="spiral-left" fill="#326ce5" d="M49.0358 52.966C39.6585 60.5986 26.248 61.0543 16.4252 54.0796C6.13019 46.767 2.30811 33.2284 6.99163 21.7686L13.4211 25.0927C10.4886 32.8982 12.7235 41.7046 19.2592 47.1881C23.9542 51.1254 30.4604 52.5884 36.662 51.101L35.5459 46.438C30.8112 47.5734 25.8768 46.4802 22.3401 43.5158C16.9155 38.9615 15.4012 31.389 18.655 25.1004L19.7584 22.9692L4.83995 15.2598L3.73915 17.3884C-3.50565 31.375 0.756753 48.8303 13.6476 57.99C21.2828 63.4095 30.7536 65.0236 39.4857 62.9129C43.9926 61.8236 48.3036 59.7449 52.063 56.6857C53.174 55.7794 54.2032 54.7746 55.1862 53.7302L50.7484 51.4364C50.1968 51.9689 49.6297 52.4834 49.0358 52.966Z"/>
    <path id="spiral-right" fill="#5fb7ff" d="M50.3696 6.01048C38.9315 -2.1252 23.2451 -1.69128 12.2268 7.06904C10.9865 8.05208 9.84603 9.14776 8.77467 10.2934L13.2099 12.5846C13.8486 11.9689 14.5116 11.375 15.2092 10.8194C24.5686 3.38264 37.8819 3.00888 47.5907 9.9132C57.8742 17.2284 61.6873 30.7618 57.5222 42.4802L51.0928 39.1561C53.5056 31.101 51.2835 22.3049 44.7696 16.8201C39.9516 12.7586 33.2867 11.3506 26.9238 12.9865L28.0694 17.6418C32.9104 16.3593 38.0534 17.4294 41.6835 20.4886C47.0915 25.0428 48.5955 32.605 45.3468 38.8847L44.2435 41.0159L59.1644 48.7254L60.2627 46.5967C67.4985 32.6166 63.2476 15.1714 50.3708 6.0092"/>
</svg>

```

--------------------------------------------------------------------------------
/config/olm/bundle/tests/scorecard/config.yaml:
--------------------------------------------------------------------------------

```yaml
# see https://sdk.operatorframework.io/docs/testing-operators/scorecard/ for more information
apiVersion: scorecard.operatorframework.io/v1alpha3
kind: Configuration
metadata:
  name: config
stages:
  - parallel: true
    tests:
      - entrypoint:
          - scorecard-test
          - basic-check-spec
        image: quay.io/operator-framework/scorecard-test:v1.27.0
        labels:
          suite: basic
          test: basic-check-spec-test
        storage:
          spec:
            mountPath: {}
      - entrypoint:
          - scorecard-test
          - olm-bundle-validation
        image: quay.io/operator-framework/scorecard-test:v1.27.0
        labels:
          suite: olm
          test: olm-bundle-validation-test
        storage:
          spec:
            mountPath: {}
      - entrypoint:
          - scorecard-test
          - olm-crds-have-validation
        image: quay.io/operator-framework/scorecard-test:v1.27.0
        labels:
          suite: olm
          test: olm-crds-have-validation-test
        storage:
          spec:
            mountPath: {}
      - entrypoint:
          - scorecard-test
          - olm-crds-have-validation
        image: quay.io/operator-framework/scorecard-test:v1.7.1
        labels:
          suite: olm
          test: olm-crds-have-validation-test
        storage:
          spec:
            mountPath: {}
storage:
  spec:
    mountPath: {}

```

--------------------------------------------------------------------------------
/web/src/utils/time.js:
--------------------------------------------------------------------------------

```javascript
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

/**
 * Format a timestamp into a human-readable relative time string.
 *
 * @param {string|Date} timestamp - The timestamp to format
 * @returns {string} Formatted time string (e.g., "just now", "5m ago", "3h ago", or absolute date)
 *
 * Examples:
 * - Less than 1 minute: "just now"
 * - Less than 60 minutes: "5m ago"
 * - Less than 24 hours: "3h ago"
 * - Older: "Jan 15, 02:30 PM"
 */
export const formatTimestamp = (timestamp) => {
  const date = new Date(timestamp)
  const now = new Date()
  const diffMs = now - date
  const diffMins = Math.floor(diffMs / 60000)

  if (diffMins < 1) return 'just now'
  if (diffMins < 60) return `${diffMins}m ago`
  if (diffMins < 1440) return `${Math.floor(diffMins / 60)}h ago`
  return date.toLocaleString('en-US', {
    month: 'short',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit'
  })
}

/**
 * Format a date into an absolute time string (HH:MM:SS format).
 *
 * @param {Date|null} date - The date to format
 * @returns {string} Formatted time string (e.g., "02:30:45 PM") or "Never" if date is null
 *
 * Examples:
 * - Valid date: "02:30:45 PM"
 * - Null/undefined: "Never"
 */
export const formatTime = (date) => {
  if (!date) return 'Never'
  return new Intl.DateTimeFormat('en-US', {
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
  }).format(date)
}
```

--------------------------------------------------------------------------------
/cmd/cli/wait_resourceset.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package main

import (
	"context"
	"fmt"

	"github.com/spf13/cobra"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

var waitResourceSetCmd = &cobra.Command{
	Use:     "resourceset [name]",
	Aliases: []string{"rset"},
	Short:   "Wait for ResourceSet to become ready",
	Example: `  # Wait for a resourceset to become ready
  flux-operator -n flux-system wait resourceset my-resourceset

  # Wait for a resourceset to become ready with a custom timeout
  flux-operator -n flux-system wait rset my-resourceset --timeout=5m
`,
	Args:              cobra.ExactArgs(1),
	RunE:              waitResourceSetCmdRun,
	ValidArgsFunction: resourceNamesCompletionFunc(fluxcdv1.GroupVersion.WithKind(fluxcdv1.ResourceSetKind)),
}

func init() {
	waitCmd.AddCommand(waitResourceSetCmd)
}

func waitResourceSetCmdRun(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("name is required")
	}

	name := args[0]
	gvk := fluxcdv1.GroupVersion.WithKind(fluxcdv1.ResourceSetKind)

	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
	defer cancel()

	rootCmd.Println(`◎`, "Waiting for resourceset to become ready...")
	msg, err := waitForResourceReconciliation(ctx, gvk, name, *kubeconfigArgs.Namespace, "", rootArgs.timeout)
	if err != nil {
		return err
	}

	rootCmd.Println(`✔`, msg)
	return nil
}

```

--------------------------------------------------------------------------------
/internal/builder/testdata/resourceset/multi-doc-template.golden.yaml:
--------------------------------------------------------------------------------

```yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
  name: addons
  namespace: flux-system
spec:
  interval: 10m
  ref:
    tag: latest
  url: oci://registry.example.com/addons
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: ingress-nginx
  namespace: flux-system
spec:
  interval: 10m
  path: ./ingress-nginx
  prune: true
  sourceRef:
    kind: OCIRepository
    name: addons
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: cert-manager
  namespace: flux-system
spec:
  interval: 10m
  path: ./cert-manager
  prune: true
  sourceRef:
    kind: OCIRepository
    name: addons
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 10m
  ref:
    tag: latest
  url: oci://registry.example.com/apps
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: frontend
  namespace: flux-system
spec:
  decryption:
    provider: sops
    secretRef:
      name: apps-sops
  interval: 10m
  path: ./frontend
  prune: true
  sourceRef:
    kind: OCIRepository
    name: apps
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: backend
  namespace: flux-system
spec:
  decryption:
    provider: sops
    secretRef:
      name: apps-sops
  interval: 10m
  path: ./backend
  prune: true
  sourceRef:
    kind: OCIRepository
    name: apps
---

```

--------------------------------------------------------------------------------
/internal/builder/pull.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package builder

import (
	"context"
	"fmt"
	"strings"

	untar "github.com/fluxcd/pkg/tar"
	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/crane"
)

// PullArtifact downloads an artifact from an OCI repository and extracts the content
// of the first tgz layer to the given destination directory.
// It returns the digest of the artifact.
func PullArtifact(ctx context.Context, ociURL, dstDir string, keyChain authn.Keychain) (string, error) {
	img, err := crane.Pull(strings.TrimPrefix(ociURL, "oci://"), crane.WithContext(ctx), crane.WithAuthFromKeychain(keyChain))
	if err != nil {
		return "", fmt.Errorf("pulling artifact %s failed: %w", ociURL, err)
	}

	digest, err := img.Digest()
	if err != nil {
		return "", fmt.Errorf("parsing digest for artifact %s failed: %w", ociURL, err)
	}

	layers, err := img.Layers()
	if err != nil {
		return "", fmt.Errorf("listing layers in artifact %s failed: %w", ociURL, err)
	}

	if len(layers) < 1 {
		return "", fmt.Errorf("no layers found in artifact %s", ociURL)
	}

	blob, err := layers[0].Compressed()
	if err != nil {
		return "", fmt.Errorf("extracting layer from artifact %s failed: %w", ociURL, err)
	}

	if err = untar.Untar(blob, dstDir, untar.WithMaxUntarSize(-1)); err != nil {
		return "", fmt.Errorf("extracting layer from artifact %s failed: %w", ociURL, err)
	}

	return digest.String(), nil
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/set_context.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"
	"fmt"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

const (
	// ToolSetKubeConfigContext is the name of the set_kubeconfig_context tool.
	ToolSetKubeConfigContext = "set_kubeconfig_context"
)

func init() {
	systemTools[ToolSetKubeConfigContext] = systemTool{
		readOnly:  true,
		inCluster: false,
	}
}

// setKubeconfigContextInput defines the input parameters for setting the kubeconfig context.
type setKubeconfigContextInput struct {
	Name string `json:"name" jsonschema:"The name of the kubeconfig context."`
}

// HandleSetKubeconfigContext is the handler function for the set_kubeconfig_context tool.
func (m *Manager) HandleSetKubeconfigContext(ctx context.Context, request *mcp.CallToolRequest, input setKubeconfigContextInput) (*mcp.CallToolResult, any, error) {
	if err := CheckScopes(ctx, ToolSetKubeConfigContext, m.readOnly); err != nil {
		return NewToolResultError(err.Error())
	}

	if input.Name == "" {
		return NewToolResultError("name is required")
	}

	err := m.kubeconfig.Load()
	if err != nil {
		return NewToolResultErrorFromErr("error reading kubeconfig contexts", err)
	}

	err = m.kubeconfig.SetCurrentContext(input.Name)
	if err != nil {
		return NewToolResultErrorFromErr("error setting kubeconfig context", err)
	}
	m.kubeClient.SetCurrentContext(input.Name)

	return NewToolResultText(fmt.Sprintf("Context changed to %s", input.Name))
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/get_apis_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
	cli "k8s.io/cli-runtime/pkg/genericclioptions"

	"github.com/controlplaneio-fluxcd/flux-operator/cmd/mcp/k8s"
)

func TestManager_HandleGetAPIVersions(t *testing.T) {
	configFile := "testdata/kubeconfig.yaml"
	t.Setenv("KUBECONFIG", configFile)

	m := &Manager{
		kubeconfig: k8s.NewKubeConfig(),
		kubeClient: k8s.NewClientFactory(cli.NewConfigFlags(false)),
		timeout:    time.Second,
	}

	request := &mcp.CallToolRequest{
		Params: &mcp.CallToolParamsRaw{
			Name: "get_kubernetes_api_versions",
		},
	}

	tests := []struct {
		testName  string
		arguments map[string]any
		matchErr  string
	}{
		{
			testName:  "fails with invalid kubeconfig",
			arguments: map[string]any{},
			matchErr:  "Failed to get Kubernetes client",
		},
	}

	for _, test := range tests {
		t.Run(test.testName, func(t *testing.T) {
			g := NewWithT(t)
			argsJSON, _ := json.Marshal(test.arguments)
			request.Params.Arguments = argsJSON

			result, content, err := m.HandleGetAPIVersions(context.Background(), request, struct{}{})
			g.Expect(err).ToNot(HaveOccurred())
			textContent, ok := result.Content[0].(*mcp.TextContent)
			g.Expect(ok).To(BeTrue())

			g.Expect(result.IsError).To(BeTrue())
			g.Expect(textContent.Text).To(ContainSubstring(test.matchErr))
			_ = content
		})
	}
}

```

--------------------------------------------------------------------------------
/cmd/cli/testdata/build_resourceset/rset-with-rsip-permuted.yaml:
--------------------------------------------------------------------------------

```yaml
---
apiVersion: fluxcd.controlplane.io/v1
kind: ResourceSet
metadata:
  name: color-environments
  namespace: test
spec:
  inputStrategy:
    name: Permute
  inputs:
    - id: blue
    - id: green
    - id: red
    - id: yellow
  inputsFrom:
    - selector:
        matchLabels:
          fluxcd.controlplane.io/role: provisioning
  resourcesTemplate: |
    <<- $id := inputs.team1_apps.id >>
    <<- $tenant := inputs.team1_apps.tenantName >>
    <<- range $app := inputs.team1_apps.applications >>
    <<- $appName := $app.name >>
    <<- range $env := $app.envs >>
    ---
    apiVersion: source.toolkit.fluxcd.io/v1
    kind: OCIRepository
    metadata:
      name: << $appName >>
      namespace: << $tenant >>-<< $env.name >>-<< inputs.color_environments.id >>
      annotations:
        permutation-id: << inputs.id | quote >>
        fluxcd.controlplane.io/id: << $id | quote >>
    spec:
      interval: 10m
      url: oci://registry.example.com/<< $appName >>
      ref:
        tag: << $env.version >>
    ---
    apiVersion: kustomize.toolkit.fluxcd.io/v1
    kind: Kustomization
    metadata:
      name: << $appName >>
      namespace: << $tenant >>-<< $env.name >>-<< inputs.color_environments.id >>
      annotations:
        permutation-id: << inputs.id | quote >>
        fluxcd.controlplane.io/id: << $id | quote >>
    spec:
      interval: 1h
      prune: true
      sourceRef:
        kind: OCIRepository
        name: << $appName >>
      path: ./
    <<- end >>
    <<- end >>

```

--------------------------------------------------------------------------------
/cmd/mcp/k8s/logs.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package k8s

import (
	"bytes"
	"context"
	"fmt"
	"io"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/client-go/kubernetes"
)

// GetLogs retrieves the logs for a specific pod container in the given namespace.
func (k *Client) GetLogs(ctx context.Context, pod, container, namespace string, limit int64) (*unstructured.Unstructured, error) {
	limitBytes := int64(64 * 1024)
	podLogOpts := corev1.PodLogOptions{
		Container:  container,
		TailLines:  &limit,
		LimitBytes: &limitBytes,
	}

	clientset, err := kubernetes.NewForConfig(k.cfg)
	if err != nil {
		return nil, err
	}

	req := clientset.CoreV1().Pods(namespace).GetLogs(pod, &podLogOpts)
	logs, err := req.Stream(ctx)
	if err != nil {
		return nil, fmt.Errorf("unable to get logs for pod %s/%s: %w", namespace, pod, err)
	}
	defer logs.Close()

	logsBuffer := new(bytes.Buffer)
	_, err = io.Copy(logsBuffer, logs)
	if err != nil {
		return nil, fmt.Errorf("failed to read logs for pod %s/%s: %w", namespace, pod, err)
	}

	logsContent := logsBuffer.String()

	if logsContent == "" {
		logsContent = fmt.Sprintf("no logs found for container %s", container)
	}

	return &unstructured.Unstructured{
		Object: map[string]any{
			"apiVersion": "v1",
			"kind":       "Pod",
			"metadata": map[string]any{
				"name":      pod,
				"namespace": namespace,
			},
			"container": container,
			"logs":      logsContent,
		},
	}, nil
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/library/library.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package library

import (
	"bytes"
	_ "embed"
	"encoding/gob"
	"fmt"
)

//go:embed index.gob
var embeddedIndex []byte

var searchIndex *SearchIndex

// DocumentMetadata holds the metadata for a Flux document,
// including its URL, group, kind, and keywords.
type DocumentMetadata struct {
	URL      string   `json:"url"`
	Group    string   `json:"group"`
	Kind     string   `json:"kind"`
	Keywords []string `json:"keywords"`
}

// Library holds a collection of document references represented by the DocumentMetadata type.
// It provides methods to search in the metadata and fetch the document content.
type Library struct {
	Documents []DocumentMetadata `json:"documents"`
}

func init() {
	var err error
	searchIndex, err = loadIndex()
	if err != nil {
		// If index doesn't exist yet (e.g., during first build), that's okay
		// The index will be generated by the indexer tool
		searchIndex = nil
	}
}

// loadIndex deserializes the embedded search index.
func loadIndex() (*SearchIndex, error) {
	if len(embeddedIndex) == 0 {
		return nil, fmt.Errorf("embedded index is empty")
	}

	reader := bytes.NewReader(embeddedIndex)
	decoder := gob.NewDecoder(reader)

	var index SearchIndex
	if err := decoder.Decode(&index); err != nil {
		return nil, fmt.Errorf("failed to decode index: %w", err)
	}

	return &index, nil
}

// GetSearchIndex returns the loaded search index.
func GetSearchIndex() *SearchIndex {
	return searchIndex
}

```

--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="robots" content="noindex, nofollow" />
    <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Real-time visibility into your GitOps pipelines" />
    <meta property="og:type" content="website" />
    <meta property="og:title" content="Flux Status" />
    <meta property="og:description" content="Real-time visibility into your GitOps pipelines" />
    <title>Flux Status</title>
    <script>
      // Apply theme before page renders to prevent flash
      (function() {
        const theme = localStorage.getItem('theme') || 'auto';
        const isDark = theme === 'dark' ||
          (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
        if (isDark) {
          document.documentElement.classList.add('dark');
        }
      })();
    </script>
    <style>
      /* Critical CSS to prevent flash of white background */
      html {
        background-color: #f9fafb; /* gray-50 for light */
      }
      html.dark {
        background-color: #111827; /* gray-900 for dark */
      }
      body {
        background-color: transparent;
      }
    </style>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

--------------------------------------------------------------------------------
/cmd/cli/testdata/build_resourceset/golden.yaml:
--------------------------------------------------------------------------------

```yaml
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
  annotations:
    fluxcd.controlplane.io/id: "340788154"
  labels:
    resourceset.fluxcd.controlplane.io/name: apps
    resourceset.fluxcd.controlplane.io/namespace: test
  name: app1
  namespace: team1-staging
spec:
  interval: 10m
  ref:
    tag: v1.0.1
  url: oci://registry.example.com/app1
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  annotations:
    fluxcd.controlplane.io/id: "340788154"
  labels:
    resourceset.fluxcd.controlplane.io/name: apps
    resourceset.fluxcd.controlplane.io/namespace: test
  name: app1
  namespace: team1-staging
spec:
  interval: 1h
  path: ./
  prune: true
  sourceRef:
    kind: OCIRepository
    name: app1
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
  annotations:
    fluxcd.controlplane.io/id: "340788154"
  labels:
    resourceset.fluxcd.controlplane.io/name: apps
    resourceset.fluxcd.controlplane.io/namespace: test
  name: app1
  namespace: team1-production
spec:
  interval: 10m
  ref:
    tag: v1.0.0
  url: oci://registry.example.com/app1
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  annotations:
    fluxcd.controlplane.io/id: "340788154"
  labels:
    resourceset.fluxcd.controlplane.io/name: apps
    resourceset.fluxcd.controlplane.io/namespace: test
  name: app1
  namespace: team1-production
spec:
  interval: 1h
  path: ./
  prune: true
  sourceRef:
    kind: OCIRepository
    name: app1

```

--------------------------------------------------------------------------------
/internal/controller/fluxinstance_manager.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package controller

import (
	"k8s.io/client-go/util/workqueue"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/builder"
	"sigs.k8s.io/controller-runtime/pkg/controller"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"

	runtimectrl "github.com/fluxcd/pkg/runtime/controller"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

// FluxInstanceReconcilerOptions contains options for the reconciler.
type FluxInstanceReconcilerOptions struct {
	RateLimiter             workqueue.TypedRateLimiter[reconcile.Request]
	DisableWaitInterruption bool
}

// SetupWithManager sets up the controller with the Manager.
func (r *FluxInstanceReconciler) SetupWithManager(mgr ctrl.Manager, opts FluxInstanceReconcilerOptions) error {
	var blder *builder.Builder
	var toComplete reconcile.TypedReconciler[reconcile.Request]

	pred := predicate.Or(
		predicate.GenerationChangedPredicate{},
		predicate.AnnotationChangedPredicate{},
	)

	if opts.DisableWaitInterruption {
		toComplete = r
		blder = ctrl.NewControllerManagedBy(mgr).
			For(&fluxcdv1.FluxInstance{}, builder.WithPredicates(pred))
	} else {
		wr := runtimectrl.WrapReconciler(r)
		toComplete = wr
		blder = runtimectrl.NewControllerManagedBy(mgr, wr).
			For(&fluxcdv1.FluxInstance{}, pred).Builder
	}

	return blder.
		WithOptions(controller.Options{
			RateLimiter: opts.RateLimiter,
		}).Complete(toComplete)
}

```

--------------------------------------------------------------------------------
/hack/build-olm-images.sh:
--------------------------------------------------------------------------------

```bash
#!/usr/bin/env bash

# Copyright 2024 Stefan Prodan.
# SPDX-License-Identifier: AGPL-3.0

set -euo pipefail

VERSION=$1
ARCH=""
case $(uname -m) in
    x86_64)   ARCH="x86_64" ;;
    aarch64)  ARCH="aarch64" ;;
    arm64)    ARCH="aarch64" ;;
    *)        echo "Unsupported architecture"
              exit 1
              ;;
esac
REPOSITORY_ROOT=$(git rev-parse --show-toplevel)
OCI_IMAGE_PREFIX="ghcr.io/controlplaneio-fluxcd/openshift-flux-operator"
DEST_DIR="${REPOSITORY_ROOT}/bin/olm"

info() {
    echo '[INFO] ' "$@"
}

fatal() {
    echo '[ERROR] ' "$@" >&2
    exit 1
}

if [ ! -d "${DEST_DIR}/${VERSION}" ]; then
  fatal "${DEST_DIR}/${VERSION} does not exist"
fi

# build catalog image
docker build -t ${OCI_IMAGE_PREFIX}-catalog:bundle-${VERSION} \
-f "${DEST_DIR}/test/bundle.Dockerfile" "${DEST_DIR}/${VERSION}"

# push catalog image
docker push ${OCI_IMAGE_PREFIX}-catalog:bundle-${VERSION}

# build opm image
docker build -t opm --build-arg ARCH=$ARCH -f "${DEST_DIR}/test/opm.Dockerfile" "${DEST_DIR}"

# build index image
docker run --rm --privileged \
  -v /var/lib/docker:/var/lib/docker \
  -v /var/run/docker.sock:/var/run/docker.sock \
  opm:latest index add \
  --container-tool docker \
  --bundles ${OCI_IMAGE_PREFIX}-catalog:bundle-${VERSION} \
  --tag ${OCI_IMAGE_PREFIX}-index:v${VERSION}

# push index image
docker push ${OCI_IMAGE_PREFIX}-index:v${VERSION}

info "OLM catalog pushed to ${OCI_IMAGE_PREFIX}-catalog:bundle-${VERSION}"
info "OLM index pushed to ${OCI_IMAGE_PREFIX}-index:v${VERSION}"

```

--------------------------------------------------------------------------------
/docs/logo/flux-operator-icon.svg:
--------------------------------------------------------------------------------

```
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_4)">
<path d="M392.294 423.731C317.276 484.792 209.992 488.438 131.41 432.64C49.0496 374.139 18.473 265.83 55.9411 174.152L107.377 200.745C83.9168 263.188 101.796 333.64 154.081 377.508C191.642 409.006 243.692 420.71 293.304 408.811L284.375 371.507C246.497 380.59 207.022 371.845 178.729 348.129C135.332 311.695 123.218 251.116 149.248 200.806L158.075 183.757L38.7277 122.081L29.9213 139.11C-28.0371 251.003 6.06208 390.646 109.189 463.923C170.271 507.279 246.036 520.192 315.894 503.306C351.949 494.592 386.437 477.962 416.512 453.489C425.4 446.239 433.633 438.2 441.498 429.844L405.996 411.494C401.582 415.754 397.046 419.871 392.294 423.731Z" fill="#326CE5"/>
<path d="M402.964 48.087C311.46 -16.9984 185.969 -13.527 97.8227 56.5555C87.9002 64.4198 78.7763 73.1853 70.2054 82.3501L105.687 100.68C110.797 95.7542 116.101 91.0029 121.682 86.5587C196.557 27.0643 303.063 24.0742 380.733 79.3088C463.002 137.83 493.507 246.098 460.186 339.845L408.75 313.252C428.052 248.811 410.276 178.442 358.164 134.564C319.621 102.072 266.301 90.8083 215.398 103.895L224.563 141.138C263.291 130.877 304.435 139.438 333.476 163.912C376.74 200.346 388.772 260.844 362.783 311.081L353.956 328.131L473.324 389.806L482.109 372.777C539.996 260.936 505.989 121.375 402.975 48.0768L402.964 48.087Z" fill="#5FB7FF"/>
</g>
<defs>
<clipPath id="clip0_1_4">
<rect width="512" height="512" fill="white"/>
</clipPath>
</defs>
</svg>

```

--------------------------------------------------------------------------------
/cmd/cli/wait_inputprovider.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package main

import (
	"context"
	"fmt"

	"github.com/spf13/cobra"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

var waitInputProviderCmd = &cobra.Command{
	Use:     "inputprovider [name]",
	Aliases: []string{"rsip", "resourcesetinputprovider"},
	Short:   "Wait for ResourceSetInputProvider to become ready",
	Example: `  # Wait for an ResourceSetInputProvider to become ready
  flux-operator -n flux-system wait inputprovider my-inputprovider

  # Wait for an ResourceSetInputProvider to become ready with a custom timeout
  flux-operator -n flux-system wait rsip my-inputprovider --timeout=5m
`,
	Args:              cobra.ExactArgs(1),
	RunE:              waitInputProviderCmdRun,
	ValidArgsFunction: resourceNamesCompletionFunc(fluxcdv1.GroupVersion.WithKind(fluxcdv1.ResourceSetInputProviderKind)),
}

func init() {
	waitCmd.AddCommand(waitInputProviderCmd)
}

func waitInputProviderCmdRun(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("name is required")
	}

	name := args[0]
	gvk := fluxcdv1.GroupVersion.WithKind(fluxcdv1.ResourceSetInputProviderKind)

	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
	defer cancel()

	rootCmd.Println(`◎`, "Waiting for inputprovider to become ready...")
	msg, err := waitForResourceReconciliation(ctx, gvk, name, *kubeconfigArgs.Namespace, "", rootArgs.timeout)
	if err != nil {
		return err
	}

	rootCmd.Println(`✔`, msg)
	return nil
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/manager_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"testing"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
)

func TestManager_RegisterToolsDoesNotPanic(t *testing.T) {
	g := NewWithT(t)

	server := mcp.NewServer(&mcp.Implementation{
		Name:    "flux-operator-mcp",
		Version: "test-version",
	}, &mcp.ServerOptions{
		HasTools: true,
	})

	manager := NewManager(nil, 0, false, false, nil)
	registeredTools := manager.RegisterTools(server, false)
	g.Expect(registeredTools).To(Equal([]string{
		"install_flux_instance",
		"get_flux_instance",
		"get_kubernetes_api_versions",
		"get_kubernetes_logs",
		"get_kubernetes_metrics",
		"get_kubernetes_resources",
		"search_flux_docs",
		"apply_kubernetes_manifest",
		"delete_kubernetes_resource",
		"reconcile_flux_source",
		"reconcile_flux_kustomization",
		"reconcile_flux_helmrelease",
		"reconcile_flux_resourceset",
		"suspend_flux_reconciliation",
		"resume_flux_reconciliation",
		"get_kubeconfig_contexts",
		"set_kubeconfig_context",
	}))
}

func TestManager_RegisterSpecificTools(t *testing.T) {
	g := NewWithT(t)

	server := mcp.NewServer(&mcp.Implementation{
		Name:    "flux-operator-mcp",
		Version: "test-version",
	}, &mcp.ServerOptions{
		HasTools: true,
	})

	manager := NewManager(nil, 0, false, false, []string{
		"get_kubeconfig_contexts",
		"set_kubeconfig_context",
	})
	registeredTools := manager.RegisterTools(server, false)
	g.Expect(registeredTools).To(Equal([]string{
		"get_kubeconfig_contexts",
		"set_kubeconfig_context",
	}))
}

```

--------------------------------------------------------------------------------
/cmd/cli/resume_instance.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package main

import (
	"context"
	"fmt"

	"github.com/spf13/cobra"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

var resumeInstanceCmd = &cobra.Command{
	Use:               "instance [name]",
	Short:             "Resume FluxInstance reconciliation",
	Args:              cobra.ExactArgs(1),
	RunE:              resumeInstanceCmdRun,
	ValidArgsFunction: resourceNamesCompletionFunc(fluxcdv1.GroupVersion.WithKind(fluxcdv1.FluxInstanceKind)),
}

type resumeInstanceFlags struct {
	wait bool
}

var resumeInstanceArgs resumeInstanceFlags

func init() {
	resumeInstanceCmd.Flags().BoolVar(&resumeInstanceArgs.wait, "wait", true,
		"Wait for the resource to become ready.")
	resumeCmd.AddCommand(resumeInstanceCmd)
}

func resumeInstanceCmdRun(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("name is required")
	}

	name := args[0]
	now := timeNow()
	gvk := fluxcdv1.GroupVersion.WithKind(fluxcdv1.FluxInstanceKind)

	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
	defer cancel()

	err := toggleSuspension(ctx, gvk, name, *kubeconfigArgs.Namespace, now, false)
	if err != nil {
		return err
	}

	if resumeInstanceArgs.wait {
		rootCmd.Println(`◎`, "Waiting for reconciliation...")
		msg, err := waitForResourceReconciliation(ctx, gvk, name, *kubeconfigArgs.Namespace, now, rootArgs.timeout)
		if err != nil {
			return err
		}

		rootCmd.Println(`✔`, msg)
	} else {
		rootCmd.Println(`✔`, "Reconciliation resumed")
	}

	return nil
}

```

--------------------------------------------------------------------------------
/internal/inputs/provider.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package inputs

import (
	"strings"

	"k8s.io/apimachinery/pkg/runtime/schema"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

// ProviderKey is the key used to identify input providers.
type ProviderKey struct {
	GVK       schema.GroupVersionKind
	Name      string
	Namespace string
}

// NewProviderKey returns the key for the input provider.
func NewProviderKey(provider fluxcdv1.InputProvider) ProviderKey {
	return ProviderKey{
		GVK:       provider.GroupVersionKind(),
		Name:      provider.GetName(),
		Namespace: provider.GetNamespace(),
	}
}

// compareProviderKeys compares two ProviderKey objects.
func compareProviderKeys(a, b ProviderKey) int {
	if gvkA, gvkB := a.GVK.String(), b.GVK.String(); gvkA != gvkB {
		return strings.Compare(gvkA, gvkB)
	}
	if a.Namespace != b.Namespace {
		return strings.Compare(a.Namespace, b.Namespace)
	}
	return strings.Compare(a.Name, b.Name)
}

// getFromProvider returns the inputs from the given input provider
// and annotates each input with the provider reference.
func getFromProvider(provider fluxcdv1.InputProvider) ([]map[string]any, error) {
	providerInputs, err := provider.GetInputs()
	if err != nil {
		return nil, err
	}

	for _, input := range providerInputs {
		providerType := provider.GroupVersionKind()
		input["provider"] = map[string]any{
			"apiVersion": providerType.GroupVersion().String(),
			"kind":       providerType.Kind,
			"name":       provider.GetName(),
			"namespace":  provider.GetNamespace(),
		}
	}
	return providerInputs, nil
}

```

--------------------------------------------------------------------------------
/internal/web/config/user_actions_types.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package config

import (
	"fmt"
	"slices"
)

const (
	// UserActionReconcile is the reconcile user action.
	UserActionReconcile = "reconcile"

	// UserActionSuspend is the suspend user action.
	UserActionSuspend = "suspend"

	// UserActionResume is the resume user action.
	UserActionResume = "resume"
)

var (
	// AllUserActions lists all possible user actions.
	AllUserActions = []string{
		UserActionReconcile,
		UserActionSuspend,
		UserActionResume,
	}
)

// UserActionsSpec holds the actions configuration.
type UserActionsSpec struct {
	// Audit is a list of actions to be audited.
	// If the field is empty or omitted, no actions are audited.
	// The special value ["*"] can be used to audit all actions.
	// +optional
	Audit []string `json:"audit,omitempty"`
}

// Validate validates the UserActionsSpec configuration.
func (u *UserActionsSpec) Validate() error {
	auditedActions := make(map[string]struct{})
	for _, action := range u.Audit {
		if _, exists := auditedActions[action]; exists {
			return fmt.Errorf("duplicate audit action: '%s'", action)
		}
		if !slices.Contains(AllUserActions, action) && action != "*" {
			return fmt.Errorf("invalid audit action: '%s'", action)
		}
		auditedActions[action] = struct{}{}
	}
	if _, exists := auditedActions["*"]; exists && len(auditedActions) > 1 {
		return fmt.Errorf("audit action '*' cannot be combined with other actions")
	}
	return nil
}

// ApplyDefaults applies default values to the UserActionsSpec.
func (u *UserActionsSpec) ApplyDefaults() {
	if u == nil {
		return
	}
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/get_instance.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	"k8s.io/apimachinery/pkg/runtime/schema"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

const (
	// ToolGetFluxInstance is the name of the get_flux_instance tool.
	ToolGetFluxInstance = "get_flux_instance"
)

func init() {
	systemTools[ToolGetFluxInstance] = systemTool{
		readOnly:  true,
		inCluster: true,
	}
}

// HandleGetFluxInstance is the handler function for the get_flux_instance tool.
func (m *Manager) HandleGetFluxInstance(ctx context.Context, request *mcp.CallToolRequest, input struct{}) (*mcp.CallToolResult, any, error) {
	if err := CheckScopes(ctx, ToolGetFluxInstance, m.readOnly); err != nil {
		return NewToolResultError(err.Error())
	}

	ctx, cancel := context.WithTimeout(ctx, m.timeout)
	defer cancel()

	kubeClient, err := m.kubeClient.GetClient(ctx)
	if err != nil {
		return NewToolResultErrorFromErr("Failed to get Kubernetes client", err)
	}

	result, err := kubeClient.Export(ctx, []schema.GroupVersionKind{
		{
			Group:   fluxcdv1.GroupVersion.Group,
			Version: fluxcdv1.GroupVersion.Version,
			Kind:    fluxcdv1.FluxInstanceKind,
		},
		{
			Group:   fluxcdv1.GroupVersion.Group,
			Version: fluxcdv1.GroupVersion.Version,
			Kind:    fluxcdv1.FluxReportKind,
		},
	}, "", "", "", 1, true)
	if err != nil {
		return NewToolResultErrorFromErr("Failed to determine the Flux status", err)
	}

	if result == "" {
		return NewToolResultError("No Flux instance found")
	}

	return NewToolResultText(result)
}

```

--------------------------------------------------------------------------------
/internal/entitlement/client.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package entitlement

import (
	"context"
	"fmt"
	"os"
	"strings"
)

const (
	// VendorKey is the key in the entitlement secret
	// that holds the vendor name.
	VendorKey = "vendor"

	// TokenKey is the key in the entitlement secret
	// that holds the token.
	TokenKey = "token"

	// DefaultVendor is the default vendor name.
	DefaultVendor = "controlplane"

	// MarketplaceTypeEnvKey is the environment variable key
	// that holds the marketplace type.
	MarketplaceTypeEnvKey = "MARKETPLACE_TYPE"
)

// Client is the interface for entitlement clients
// that can register usage and verify tokens.
type Client interface {
	// RegisterUsage registers the usage with the entitlement service
	// and returns a signed JWT token.
	RegisterUsage(ctx context.Context, id string) (string, error)

	// Verify verifies that the token is signed by the
	// entitlement service and matches the usage id.
	Verify(token, id string) (bool, error)

	// GetVendor returns the vendor name.
	GetVendor() string
}

// NewClient returns a new entitlement client based on the
// marketplace type environment variable.
func NewClient() (Client, error) {
	vendor := DefaultVendor
	marketplace, found := os.LookupEnv(MarketplaceTypeEnvKey)
	if found && marketplace != "" && marketplace != DefaultVendor {
		vendor = fmt.Sprintf("%s-%s", DefaultVendor, strings.ToLower(marketplace))
	}

	switch vendor {
	case DefaultVendor:
		return &DefaultClient{Vendor: vendor}, nil
	case "controlplane-aws":
		return NewAmazonClient(vendor)
	}

	return nil, fmt.Errorf("unsupported vendor %s", vendor)
}

```

--------------------------------------------------------------------------------
/config/mcp/deployment.yaml:
--------------------------------------------------------------------------------

```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flux-operator-mcp
  labels:
    app.kubernetes.io/name: flux-operator-mcp
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: flux-operator-mcp
  template:
    metadata:
      labels:
        app.kubernetes.io/name: flux-operator-mcp
    spec:
      serviceAccountName: flux-operator
      terminationGracePeriodSeconds: 10
      affinity:
       nodeAffinity:
         requiredDuringSchedulingIgnoredDuringExecution:
           nodeSelectorTerms:
             - matchExpressions:
               - key: kubernetes.io/os
                 operator: In
                 values:
                   - linux
      containers:
      - name: server
        image: flux-operator-mcp:latest
        imagePullPolicy: IfNotPresent
        args:
          - serve
          - --transport=sse
          - --port=9090
        securityContext:
          runAsNonRoot: true
          readOnlyRootFilesystem: true
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - "ALL"
          seccompProfile:
            type: RuntimeDefault
        ports:
        - containerPort: 9090
          name: http
          protocol: TCP
        livenessProbe:
          tcpSocket:
            port: http
        readinessProbe:
          tcpSocket:
            port: http
        resources:
          limits:
            cpu: 1000m
            memory: 1Gi
          requests:
            cpu: 10m
            memory: 64Mi
        volumeMounts:
          - name: temp
            mountPath: /tmp
      volumes:
        - name: temp
          emptyDir: {}

```

--------------------------------------------------------------------------------
/cmd/cli/resume_resourceset.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package main

import (
	"context"
	"fmt"

	"github.com/spf13/cobra"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

var resumeResourceSetCmd = &cobra.Command{
	Use:               "resourceset [name]",
	Aliases:           []string{"rset"},
	Short:             "Resume ResourceSet reconciliation",
	Args:              cobra.ExactArgs(1),
	RunE:              resumeResourceSetCmdRun,
	ValidArgsFunction: resourceNamesCompletionFunc(fluxcdv1.GroupVersion.WithKind(fluxcdv1.ResourceSetKind)),
}

type resumeResourceSetFlags struct {
	wait bool
}

var resumeResourceSetArgs resumeResourceSetFlags

func init() {
	resumeResourceSetCmd.Flags().BoolVar(&resumeResourceSetArgs.wait, "wait", true,
		"Wait for the resource to become ready.")
	resumeCmd.AddCommand(resumeResourceSetCmd)
}

func resumeResourceSetCmdRun(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("name is required")
	}

	name := args[0]
	now := timeNow()
	gvk := fluxcdv1.GroupVersion.WithKind(fluxcdv1.ResourceSetKind)

	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
	defer cancel()

	err := toggleSuspension(ctx, gvk, name, *kubeconfigArgs.Namespace, now, false)
	if err != nil {
		return err
	}

	if resumeResourceSetArgs.wait {
		rootCmd.Println(`◎`, "Waiting for reconciliation...")
		msg, err := waitForResourceReconciliation(ctx, gvk, name, *kubeconfigArgs.Namespace, now, rootArgs.timeout)
		if err != nil {
			return err
		}

		rootCmd.Println(`✔`, msg)
	} else {
		rootCmd.Println(`✔`, "Reconciliation resumed")
	}

	return nil
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/apply_manifest.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

const (
	// ToolApplyKubernetesManifest is the name of the apply_kubernetes_manifest tool.
	ToolApplyKubernetesManifest = "apply_kubernetes_manifest"
)

func init() {
	systemTools[ToolApplyKubernetesManifest] = systemTool{
		readOnly:  false,
		inCluster: true,
	}
}

// applyKubernetesManifestInput defines the input parameters for applying a Kubernetes manifest.
type applyKubernetesManifestInput struct {
	YAMLContent string `json:"yaml_content" jsonschema:"The multi-doc YAML content."`
	Overwrite   bool   `json:"overwrite,omitempty" jsonschema:"Overwrite resources managed by Flux."`
}

// HandleApplyKubernetesManifest is the handler function for the apply_kubernetes_manifest tool.
func (m *Manager) HandleApplyKubernetesManifest(ctx context.Context, request *mcp.CallToolRequest, input applyKubernetesManifestInput) (*mcp.CallToolResult, any, error) {
	if err := CheckScopes(ctx, ToolApplyKubernetesManifest, m.readOnly); err != nil {
		return NewToolResultError(err.Error())
	}

	if input.YAMLContent == "" {
		return NewToolResultError("YAML manifest cannot be empty")
	}

	ctx, cancel := context.WithTimeout(ctx, m.timeout)
	defer cancel()

	kubeClient, err := m.kubeClient.GetClient(ctx)
	if err != nil {
		return NewToolResultErrorFromErr("Failed to get Kubernetes client", err)
	}

	changeSet, err := kubeClient.Apply(ctx, input.YAMLContent, input.Overwrite)
	if err != nil {
		return NewToolResultErrorFromErr("Failed to apply manifest", err)
	}

	return NewToolResultText(changeSet)
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/library/bm25.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package library

import "math"

const (
	// K1 is the term frequency saturation parameter for BM25
	K1 = 1.2
	// B is the length normalization parameter for BM25
	B = 0.75
)

// Score calculates the BM25 score for a document given query terms.
// BM25 formula:
// score = Σ IDF(qi) × (f(qi,D) × (k1+1)) / (f(qi,D) + k1 × (1-b + b × |D|/avgdl))
func (idx *SearchIndex) Score(queryTerms []string, docID int) float64 {
	score := 0.0
	doc := idx.Documents[docID]

	for _, term := range queryTerms {
		// Get term frequency in document
		tf := idx.termFrequency(term, docID)
		if tf == 0 {
			continue
		}

		// Calculate IDF
		idf := idx.IDF(term)

		// BM25 formula
		numerator := float64(tf) * (K1 + 1)
		denominator := float64(tf) + K1*(1-B+B*float64(doc.Length)/idx.AvgDocLength)
		score += idf * (numerator / denominator)
	}

	return score
}

// IDF calculates the inverse document frequency for a term.
// IDF formula with smoothing:
// IDF(qi) = log((N - df(qi) + 0.5) / (df(qi) + 0.5))
func (idx *SearchIndex) IDF(term string) float64 {
	postings, exists := idx.Terms[term]
	if !exists {
		return 0.0
	}

	df := float64(len(postings)) // document frequency
	N := float64(idx.TotalDocs)

	// IDF formula with smoothing
	return math.Log((N - df + 0.5) / (df + 0.5))
}

// termFrequency returns the frequency of a term in a specific document.
func (idx *SearchIndex) termFrequency(term string, docID int) int {
	postings, exists := idx.Terms[term]
	if !exists {
		return 0
	}

	for _, posting := range postings {
		if posting.DocID == docID {
			return posting.Frequency
		}
	}
	return 0
}

```

--------------------------------------------------------------------------------
/internal/reporter/components.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package reporter

import (
	"cmp"
	"context"
	"fmt"

	"github.com/fluxcd/cli-utils/pkg/kstatus/status"
	"golang.org/x/exp/slices"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"sigs.k8s.io/controller-runtime/pkg/client"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

func (r *FluxStatusReporter) getComponentsStatus(ctx context.Context) ([]fluxcdv1.FluxComponentStatus, error) {
	deployments := unstructured.UnstructuredList{
		Object: map[string]any{
			"apiVersion": "apps/v1",
			"kind":       "Deployment",
		},
	}

	if err := r.List(ctx, &deployments, client.InNamespace(r.namespace), r.labelSelector); err != nil {
		return nil, fmt.Errorf("failed to list deployments: %w", err)
	}

	components := make([]fluxcdv1.FluxComponentStatus, len(deployments.Items))
	for i, d := range deployments.Items {
		res, err := status.Compute(&d)
		if err != nil {
			components[i] = fluxcdv1.FluxComponentStatus{
				Name:   d.GetName(),
				Ready:  false,
				Status: fmt.Sprintf("Failed to compute status: %s", err.Error()),
			}
		} else {
			components[i] = fluxcdv1.FluxComponentStatus{
				Name:   d.GetName(),
				Ready:  res.Status == status.CurrentStatus,
				Status: fmt.Sprintf("%s %s", string(res.Status), res.Message),
			}
		}

		containers, found, _ := unstructured.NestedSlice(d.Object, "spec", "template", "spec", "containers")
		if found && len(containers) > 0 {
			components[i].Image = containers[0].(map[string]any)["image"].(string)
		}
	}

	slices.SortStableFunc(components, func(i, j fluxcdv1.FluxComponentStatus) int {
		return cmp.Compare(i.Name, j.Name)
	})

	return components, nil
}

```

--------------------------------------------------------------------------------
/cmd/cli/stats.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package main

import (
	"context"
	"fmt"

	"github.com/spf13/cobra"
	"sigs.k8s.io/controller-runtime/pkg/client"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

var statsCmd = &cobra.Command{
	Use:   "stats",
	Short: "Print Flux custom resources statistics",
	Args:  cobra.NoArgs,
	RunE:  statsCmdRun,
}

func init() {
	rootCmd.AddCommand(statsCmd)
}

func statsCmdRun(cmd *cobra.Command, args []string) error {
	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
	defer cancel()

	kubeClient, err := newKubeClient()
	if err != nil {
		return fmt.Errorf("unable to create kube client error: %w", err)
	}

	lsOpts := &client.ListOptions{}
	var list fluxcdv1.FluxReportList
	err = kubeClient.List(ctx, &list, lsOpts)
	if err != nil {
		return fmt.Errorf("failed to get FluxReport resource: %w", err)
	}

	if len(list.Items) == 0 {
		return fmt.Errorf("no FluxReport resources found, ensure the Flux Operator is installed")
	}

	report := list.Items[0]
	if len(report.Spec.ReconcilersStatus) == 0 {
		return fmt.Errorf("no stats found in the FluxReport")
	}

	rows := make([][]string, 0)
	for _, status := range report.Spec.ReconcilersStatus {
		storage := "-"
		if status.Stats.TotalSize != "" {
			storage = status.Stats.TotalSize
		}
		row := []string{
			status.Kind,
			fmt.Sprintf("%d", status.Stats.Running),
			fmt.Sprintf("%d", status.Stats.Failing),
			fmt.Sprintf("%d", status.Stats.Suspended),
			storage,
		}
		rows = append(rows, row)
	}

	header := []string{"Reconcilers", "Running", "Failing", "Suspended", "Storage"}
	printTable(rootCmd.OutOrStdout(), header, rows)

	return nil
}

```

--------------------------------------------------------------------------------
/web/eslint.config.js:
--------------------------------------------------------------------------------

```javascript
import js from '@eslint/js'

export default [
  {
    ignores: ['node_modules/', 'dist/'],
  },
  {
    files: ['src/**/*.{js,jsx}'],
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
      },
      globals: {
        h: 'readonly',
        Fragment: 'readonly',
        window: 'readonly',
        document: 'readonly',
        console: 'readonly',
        setTimeout: 'readonly',
        setInterval: 'readonly',
        clearInterval: 'readonly',
        fetch: 'readonly',
        localStorage: 'readonly',
        URLSearchParams: 'readonly',
        crypto: 'readonly',
      },
    },
    rules: {
      ...js.configs.recommended.rules,
      indent: ['error', 2],
      'no-unused-vars': [
        'warn',
        {
          argsIgnorePattern: '^_',
          varsIgnorePattern: '^[A-Z]',
        },
      ],
      'no-console': ['error', { 'allow': ['warn', 'error'] }]
    },
  },
  {
    files: ['src/**/*.test.{js,jsx}', 'vitest.setup.js'],
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      globals: {
        describe: 'readonly',
        it: 'readonly',
        expect: 'readonly',
        beforeEach: 'readonly',
        afterEach: 'readonly',
        beforeAll: 'readonly',
        afterAll: 'readonly',
        vi: 'readonly',
        global: 'readonly',
        window: 'readonly',
        document: 'readonly',
      },
    },
    rules: {
      ...js.configs.recommended.rules,
      indent: ['error', 2],
      'no-unused-vars': [
        'warn',
        {
          argsIgnorePattern: '^_',
          varsIgnorePattern: '^[A-Z]',
        },
      ],
    },
  },
]

```

--------------------------------------------------------------------------------
/cmd/cli/resume_inputprovider.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package main

import (
	"context"
	"fmt"

	"github.com/spf13/cobra"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

var resumeInputProviderCmd = &cobra.Command{
	Use:               "inputprovider [name]",
	Aliases:           []string{"rsip", "resourcesetinputprovider"},
	Short:             "Resume ResourceSetInputProvider reconciliation",
	Args:              cobra.ExactArgs(1),
	RunE:              resumeInputProviderCmdRun,
	ValidArgsFunction: resourceNamesCompletionFunc(fluxcdv1.GroupVersion.WithKind(fluxcdv1.ResourceSetInputProviderKind)),
}

type resumeInputProviderFlags struct {
	wait bool
}

var resumeInputProviderArgs resumeInputProviderFlags

func init() {
	resumeInputProviderCmd.Flags().BoolVar(&resumeInputProviderArgs.wait, "wait", true,
		"Wait for the resource to become ready.")
	resumeCmd.AddCommand(resumeInputProviderCmd)
}

func resumeInputProviderCmdRun(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("name is required")
	}

	name := args[0]
	now := timeNow()
	gvk := fluxcdv1.GroupVersion.WithKind(fluxcdv1.ResourceSetInputProviderKind)

	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
	defer cancel()

	err := toggleSuspension(ctx, gvk, name, *kubeconfigArgs.Namespace, now, false)
	if err != nil {
		return err
	}

	if resumeInstanceArgs.wait {
		rootCmd.Println(`◎`, "Waiting for reconciliation...")
		msg, err := waitForResourceReconciliation(ctx, gvk, name, *kubeconfigArgs.Namespace, now, rootArgs.timeout)
		if err != nil {
			return err
		}

		rootCmd.Println(`✔`, msg)
	} else {
		rootCmd.Println(`✔`, "Reconciliation resumed")
	}

	return nil
}

```

--------------------------------------------------------------------------------
/internal/lkm/licensekey.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package lkm

import "encoding/json"

// LicenseKey represents a license key object that contains
// standard claims as defined in RFC 7519 (JSON Web Token)
// and custom claims specific to the Flux distribution.
// RFC7519: https://datatracker.ietf.org/doc/rfc7519
type LicenseKey struct {
	// ID is the unique identifier UUID v6 for the license key
	// (RFC 7519 JTI claim).
	// +required
	ID string `json:"jti"`

	// Issuer is the identifier of the entity that issued the license key
	// (RFC 7519 ISS claim).
	// +required
	Issuer string `json:"iss"`

	// Subject is the identifier of the entity that the license key is issued to
	// (RFC 7519 SUB claim).
	// +required
	Subject string `json:"sub"`

	// Audience is the intended audience for the license key
	// (RFC 7519 AUD claim).
	// +required
	Audience []string `json:"aud"`

	// Expiry is the expiration time of the license key in Unix timestamp format
	// (RFC 7519 EXP claim).
	// +required
	Expiry int64 `json:"exp"`

	// IssuedAt is the time when the license key was issued in Unix timestamp format
	// (RFC 7519 IAT claim).
	// +required
	IssuedAt int64 `json:"iat"`

	// NotBefore is the time before which the license key must not be accepted
	// for processing in Unix timestamp format
	// (RFC 7519 NBF claim).
	// +required
	NotBefore int64 `json:"nbf"`

	// Capabilities is a list of features granted by the license key.
	// +optional
	Capabilities []string `json:"caps,omitempty"`
}

// String returns a JSON representation of the LicenseKey object.
func (lk LicenseKey) String() string {
	data, err := json.MarshalIndent(lk, "", "  ")
	if err != nil {
		return "invalid license key"
	}
	return string(data)
}

```

--------------------------------------------------------------------------------
/hack/build-olm-manifests.sh:
--------------------------------------------------------------------------------

```bash
#!/usr/bin/env bash

# Copyright 2024 Stefan Prodan.
# SPDX-License-Identifier: AGPL-3.0

set -euo pipefail

VERSION=$1
UBI=${2:-false}
REPOSITORY_ROOT=$(git rev-parse --show-toplevel)
SOUCE_DIR="${REPOSITORY_ROOT}/config/olm"
DEST_DIR="${REPOSITORY_ROOT}/bin/olm"

info() {
    echo '[INFO] ' "$@"
}

fatal() {
    echo '[ERROR] ' "$@" >&2
    exit 1
}

rm -rf ${DEST_DIR}
mkdir -p ${DEST_DIR}
cp -r ${SOUCE_DIR}/* ${DEST_DIR}/

export FLUX_OPERATOR_VERSION=${VERSION}
export FLUX_OPERATOR_TS=$(date +"%Y-%m-%dT%H:%M:%S")
export FLUX_OPERATOR_VARIANT=""

if [ "$UBI" = "true" ]; then
  export FLUX_OPERATOR_VARIANT="-ubi"
fi

cat ${SOUCE_DIR}/bundle/manifests/flux-operator.clusterserviceversion.yaml | \
envsubst > ${DEST_DIR}/bundle/manifests/flux-operator.clusterserviceversion.yaml

cat ${SOUCE_DIR}/bundle/manifests/flux-operator.service.yaml > \
${DEST_DIR}/bundle/manifests/flux-operator.service.yaml

cat ${SOUCE_DIR}/test/olm.yaml | \
envsubst > ${DEST_DIR}/test/olm.yaml

cat ${REPOSITORY_ROOT}/config/crd/bases/fluxcd.controlplane.io_fluxinstances.yaml > \
${DEST_DIR}/bundle/manifests/fluxinstances.fluxcd.controlplane.io.crd.yaml

cat ${REPOSITORY_ROOT}/config/crd/bases/fluxcd.controlplane.io_fluxreports.yaml > \
${DEST_DIR}/bundle/manifests/fluxreports.fluxcd.controlplane.io.crd.yaml

cat ${REPOSITORY_ROOT}/config/crd/bases/fluxcd.controlplane.io_resourcesets.yaml > \
${DEST_DIR}/bundle/manifests/resourcesets.fluxcd.controlplane.io.crd.yaml

cat ${REPOSITORY_ROOT}/config/crd/bases/fluxcd.controlplane.io_resourcesetinputproviders.yaml > \
${DEST_DIR}/bundle/manifests/resourcesetinputproviders.fluxcd.controlplane.io.crd.yaml

mv ${DEST_DIR}/bundle ${DEST_DIR}/${VERSION}
info "OperatorHub bundle created in ${DEST_DIR}/${VERSION}"

```

--------------------------------------------------------------------------------
/internal/inventory/inventory_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package inventory

import (
	"os"
	"strings"
	"testing"

	"github.com/fluxcd/pkg/ssa"
	ssautil "github.com/fluxcd/pkg/ssa/utils"
	. "github.com/onsi/gomega"

	"github.com/fluxcd/cli-utils/pkg/object"
)

func Test_Inventory(t *testing.T) {
	g := NewWithT(t)

	set1, err := readManifest("testdata/inventory1.yaml")
	if err != nil {
		t.Fatal(err)
	}

	inv1 := New()
	err = AddChangeSet(inv1, set1)
	g.Expect(err).ToNot(HaveOccurred())

	set2, err := readManifest("testdata/inventory2.yaml")
	if err != nil {
		t.Fatal(err)
	}

	inv2 := New()
	err = AddChangeSet(inv2, set2)
	g.Expect(err).ToNot(HaveOccurred())

	t.Run("lists objects in inventory", func(t *testing.T) {
		unList, err := List(inv1)
		g.Expect(err).ToNot(HaveOccurred())
		g.Expect(len(unList)).To(BeIdenticalTo(len(inv1.Entries)))

		mList, err := ListMetadata(inv1)
		g.Expect(err).ToNot(HaveOccurred())
		g.Expect(len(mList)).To(BeIdenticalTo(len(inv1.Entries)))
	})

	t.Run("diff objects in inventory", func(t *testing.T) {
		unList, err := Diff(inv2, inv1)
		g.Expect(err).ToNot(HaveOccurred())
		g.Expect(len(unList)).To(BeIdenticalTo(1))
		g.Expect(unList[0].GetName()).To(BeIdenticalTo("test2"))
	})
}

func readManifest(manifest string) (*ssa.ChangeSet, error) {
	data, err := os.ReadFile(manifest)
	if err != nil {
		return nil, err
	}

	objects, err := ssautil.ReadObjects(strings.NewReader(string(data)))
	if err != nil {
		return nil, err
	}

	cs := ssa.NewChangeSet()

	for _, o := range objects {
		cse := ssa.ChangeSetEntry{
			ObjMetadata:  object.UnstructuredToObjMetadata(o),
			GroupVersion: o.GroupVersionKind().Version,
			Subject:      ssautil.FmtUnstructured(o),
			Action:       ssa.CreatedAction,
		}
		cs.Add(cse)
	}

	return cs, nil
}

```

--------------------------------------------------------------------------------
/web/src/components/layout/NotFoundPage.test.jsx:
--------------------------------------------------------------------------------

```javascript
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/preact'
import { NotFoundPage } from './NotFoundPage'

describe('NotFoundPage component', () => {
  it('should render the 404 page with correct elements', () => {
    render(<NotFoundPage />)

    // Check for heading
    expect(screen.getByText('Page Not Found')).toBeInTheDocument()

    // Check for description
    expect(screen.getByText("The page you're looking for doesn't exist or has been moved.")).toBeInTheDocument()

    // Check for Go Home link
    expect(screen.getByRole('link', { name: /go to home/i })).toBeInTheDocument()
  })

  it('should have the correct test id', () => {
    render(<NotFoundPage />)

    expect(screen.getByTestId('not-found-page')).toBeInTheDocument()
  })

  it('should have correct href on Go Home link', () => {
    render(<NotFoundPage />)

    const goHomeLink = screen.getByRole('link', { name: /go to home/i })
    expect(goHomeLink).toHaveAttribute('href', '/')
  })

  it('should render with proper styling classes', () => {
    render(<NotFoundPage />)

    const main = screen.getByTestId('not-found-page')
    expect(main).toHaveClass('max-w-7xl')
    expect(main).toHaveClass('mx-auto')
  })

  it('should render the 404 icon', () => {
    render(<NotFoundPage />)

    // The icon container should be present
    const iconContainer = document.querySelector('.w-20.h-20.rounded-full')
    expect(iconContainer).toBeInTheDocument()
  })

  it('should render the home icon in the link', () => {
    render(<NotFoundPage />)

    const link = screen.getByRole('link', { name: /go to home/i })
    const svg = link.querySelector('svg')
    expect(svg).toBeInTheDocument()
  })
})

```

--------------------------------------------------------------------------------
/internal/gitprovider/result.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package gitprovider

import (
	"k8s.io/apimachinery/pkg/util/json"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

// Result holds the information extracted from the Git SaaS provider response.
type Result struct {
	ID     string   `json:"id"`
	SHA    string   `json:"sha"`
	Branch string   `json:"branch,omitempty"`
	Tag    string   `json:"tag,omitempty"`
	Author string   `json:"author,omitempty"`
	Title  string   `json:"title,omitempty"`
	Slug   string   `json:"slug,omitempty"`
	Labels []string `json:"labels,omitempty"`
}

// ToMap converts the result into a map.
func (r *Result) ToMap() map[string]any {
	m := map[string]any{
		"id":  r.ID,
		"sha": r.SHA,
	}

	if r.Branch != "" {
		m["branch"] = r.Branch
	}

	if r.Tag != "" {
		m["tag"] = r.Tag
	}

	if r.Author != "" {
		m["author"] = r.Author
	}

	if r.Title != "" {
		m["title"] = r.Title
	}

	if r.Slug != "" {
		m["slug"] = r.Slug
	}

	if len(r.Labels) > 0 {
		m["labels"] = r.Labels
	}

	return m
}

// OverrideFromExportedInputs override result fields from exportedInput.
func (r *Result) OverrideFromExportedInputs(input map[string]any) error {
	var err error

	data, err := json.Marshal(input)
	if err != nil {
		return err
	}

	err = json.Unmarshal(data, r)
	if err != nil {
		return err
	}

	return nil
}

// MakeInputs converts a list of results into a list of ResourceSet inputs with defaults.
func MakeInputs(results []Result, defaults map[string]any) ([]fluxcdv1.ResourceSetInput, error) {
	inputs := make([]fluxcdv1.ResourceSetInput, 0, len(results))
	for _, item := range results {
		input, err := fluxcdv1.NewResourceSetInput(defaults, item.ToMap())
		if err != nil {
			return nil, err
		}
		inputs = append(inputs, input)
	}
	return inputs, nil
}

```

--------------------------------------------------------------------------------
/web/src/components/dashboards/common/panel.jsx:
--------------------------------------------------------------------------------

```javascript
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

import { useSignal } from '@preact/signals'

/**
 * Collapsible dashboard panel with header and expandable content
 */
export function DashboardPanel({ title, subtitle, id, defaultExpanded = true, children }) {
  const isExpanded = useSignal(defaultExpanded)

  return (
    <div class="card p-0" id={id}>
      <button
        onClick={() => isExpanded.value = !isExpanded.value}
        class="w-full px-6 py-4 text-left hover:bg-gray-50 dark:hover:bg-gray-700/30 transition-colors"
        aria-expanded={isExpanded.value}
      >
        <div class="flex items-center justify-between">
          <div>
            <h3 class="text-base sm:text-lg font-semibold text-gray-900 dark:text-white">{title}</h3>
            {subtitle}
          </div>
          <svg
            class={`w-5 h-5 text-gray-400 dark:text-gray-500 transition-transform ${isExpanded.value ? 'rotate-180' : ''}`}
            fill="none"
            stroke="currentColor"
            viewBox="0 0 24 24"
          >
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
          </svg>
        </div>
      </button>
      {isExpanded.value && (
        <div class="px-6 pt-2 pb-4">
          {children}
        </div>
      )}
    </div>
  )
}

/**
 * Tab button for section tabs
 */
export function TabButton({ active, onClick, children }) {
  return (
    <button
      onClick={onClick}
      class={`py-2 px-1 text-sm font-medium border-b-2 transition-colors ${
        active
          ? 'border-flux-blue text-flux-blue dark:text-blue-400'
          : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
      }`}
    >
      {children}
    </button>
  )
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/apply_manifest_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
	cli "k8s.io/cli-runtime/pkg/genericclioptions"

	"github.com/controlplaneio-fluxcd/flux-operator/cmd/mcp/k8s"
)

func TestManager_HandleApplyKubernetesManifest(t *testing.T) {
	configFile := "testdata/kubeconfig.yaml"
	t.Setenv("KUBECONFIG", configFile)

	m := &Manager{
		kubeconfig: k8s.NewKubeConfig(),
		kubeClient: k8s.NewClientFactory(cli.NewConfigFlags(false)),
		timeout:    time.Second,
	}

	request := &mcp.CallToolRequest{
		Params: &mcp.CallToolParamsRaw{
			Name: "apply_kubernetes_manifest",
		},
	}

	tests := []struct {
		testName  string
		arguments map[string]any
		matchErr  string
	}{
		{
			testName: "fails without empty yaml",
			arguments: map[string]any{
				"yaml_content": "",
			},
			matchErr: "YAML manifest cannot be empty",
		},
		{
			testName: "fails with invalid kubeconfig",
			arguments: map[string]any{
				"yaml_content": "test: test",
			},
			matchErr: "Failed to get Kubernetes client",
		},
	}

	for _, test := range tests {
		t.Run(test.testName, func(t *testing.T) {
			g := NewWithT(t)
			argsJSON, _ := json.Marshal(test.arguments)
			request.Params.Arguments = argsJSON

			var input applyKubernetesManifestInput
			err := json.Unmarshal(request.Params.Arguments, &input)
			g.Expect(err).ToNot(HaveOccurred())
			result, content, err := m.HandleApplyKubernetesManifest(context.Background(), request, input)
			g.Expect(err).ToNot(HaveOccurred())
			textContent, ok := result.Content[0].(*mcp.TextContent)
			g.Expect(ok).To(BeTrue())

			g.Expect(result.IsError).To(BeTrue())
			g.Expect(textContent.Text).To(ContainSubstring(test.matchErr))
			_ = content
		})
	}
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/get_metrics_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
	cli "k8s.io/cli-runtime/pkg/genericclioptions"

	"github.com/controlplaneio-fluxcd/flux-operator/cmd/mcp/k8s"
)

func TestManager_HandleGetKubernetesMetrics(t *testing.T) {
	configFile := "testdata/kubeconfig.yaml"
	t.Setenv("KUBECONFIG", configFile)

	m := &Manager{
		kubeconfig: k8s.NewKubeConfig(),
		kubeClient: k8s.NewClientFactory(cli.NewConfigFlags(false)),
		timeout:    time.Second,
	}

	request := &mcp.CallToolRequest{
		Params: &mcp.CallToolParamsRaw{
			Name: "get_kubernetes_metrics",
		},
	}

	tests := []struct {
		testName  string
		arguments map[string]any
		matchErr  string
	}{
		{
			testName: "fails without namespace",
			arguments: map[string]any{
				"pod_name": "test",
			},
			matchErr: "pod namespace is required",
		},
		{
			testName: "fails with invalid kubeconfig",
			arguments: map[string]any{
				"pod_name":      "test",
				"pod_namespace": "default",
			},
			matchErr: "Failed to get Kubernetes client",
		},
	}

	for _, test := range tests {
		t.Run(test.testName, func(t *testing.T) {
			g := NewWithT(t)
			argsJSON, _ := json.Marshal(test.arguments)
			request.Params.Arguments = argsJSON

			var input getKubernetesMetricsInput
			err := json.Unmarshal(request.Params.Arguments, &input)
			g.Expect(err).ToNot(HaveOccurred())
			result, content, err := m.HandleGetKubernetesMetrics(context.Background(), request, input)
			g.Expect(err).ToNot(HaveOccurred())
			textContent, ok := result.Content[0].(*mcp.TextContent)
			g.Expect(ok).To(BeTrue())

			g.Expect(result.IsError).To(BeTrue())
			g.Expect(textContent.Text).To(ContainSubstring(test.matchErr))
			_ = content
		})
	}
}

```

--------------------------------------------------------------------------------
/internal/web/auth/errors_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package auth

import (
	"errors"
	"fmt"
	"testing"

	. "github.com/onsi/gomega"
)

func TestSanitizeErrorMessage(t *testing.T) {
	for _, tt := range []struct {
		name     string
		err      error
		expected string
	}{
		{
			name:     "internal error returns generic message",
			err:      errInternalError,
			expected: "An internal error occurred. Please try again. Contact your administrator if the problem persists.",
		},
		{
			name:     "wrapped internal error returns generic message",
			err:      fmt.Errorf("something went wrong: %w", errInternalError),
			expected: "An internal error occurred. Please try again. Contact your administrator if the problem persists.",
		},
		{
			name:     "invalid scopes error returns generic message",
			err:      errInvalidOAuth2Scopes,
			expected: "An internal error occurred. Please try again. Contact your administrator if the problem persists.",
		},
		{
			name:     "wrapped invalid scopes error returns generic message",
			err:      fmt.Errorf("scope issue: %w", errInvalidOAuth2Scopes),
			expected: "An internal error occurred. Please try again. Contact your administrator if the problem persists.",
		},
		{
			name:     "user error returns authentication failed",
			err:      errUserError,
			expected: "Authentication failed. Please try again.",
		},
		{
			name:     "random error returns authentication failed",
			err:      errors.New("some random error"),
			expected: "Authentication failed. Please try again.",
		},
		{
			name:     "nil error returns authentication failed",
			err:      nil,
			expected: "Authentication failed. Please try again.",
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			g := NewWithT(t)
			result := sanitizeErrorMessage(tt.err)
			g.Expect(result).To(Equal(tt.expected))
		})
	}
}

```

--------------------------------------------------------------------------------
/web/vitest.setup.js:
--------------------------------------------------------------------------------

```javascript
// web/vitest.setup.js

// Import jest-dom matchers for DOM assertions
import '@testing-library/jest-dom/vitest'

// Mock localStorage globally before any modules are imported
const localStorageMock = {
  getItem: vi.fn(),
  setItem: vi.fn(),
  clear: vi.fn(),
}

Object.defineProperty(global, 'localStorage', {
  value: localStorageMock,
  writable: true,
})

// Mock matchMedia globally
const matchMediaMock = vi.fn((query) => ({
  matches: false, // Default to light theme for system preference
  addEventListener: vi.fn(),
  removeEventListener: vi.fn(),
}))

Object.defineProperty(global, 'matchMedia', {
  value: matchMediaMock,
  writable: true,
})

// Mock crypto.randomUUID globally
const cryptoMock = {
  randomUUID: vi.fn(() => 'test-uuid-1234'),
}

Object.defineProperty(global, 'crypto', {
  value: cryptoMock,
  writable: true,
})

// Expose the mocks for individual test files to reset/inspect
global.localStorageMock = localStorageMock
global.matchMediaMock = matchMediaMock
global.cryptoMock = cryptoMock

// Suppress expected console messages during tests
// These are logged by the application code when testing error scenarios
const suppressedErrorPatterns = [
  /Failed to fetch/,
  /Failed to parse URL/,
  /Network error/,
  /Network connection failed/,
]

const suppressedWarnPatterns = [
  /getMockWorkload:/,
]

const originalConsoleError = console.error
console.error = (...args) => {
  const message = args.join(' ')
  const shouldSuppress = suppressedErrorPatterns.some(pattern => pattern.test(message))
  if (!shouldSuppress) {
    originalConsoleError(...args)
  }
}

const originalConsoleWarn = console.warn
console.warn = (...args) => {
  const message = args.join(' ')
  const shouldSuppress = suppressedWarnPatterns.some(pattern => pattern.test(message))
  if (!shouldSuppress) {
    originalConsoleWarn(...args)
  }
}

```

--------------------------------------------------------------------------------
/.github/workflows/e2e-olm.yaml:
--------------------------------------------------------------------------------

```yaml
name: e2e-olm

on:
  workflow_dispatch:
  push:
    branches: [ '*' ]
    paths:
    - 'config/olm/**'
    - 'test/olm/**'
    - '.github/workflows/e2e-olm.yaml'

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      packages: write # for pushing and signing container images.
    steps:
      - name: Checkout
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
      - name: Disk Cleanup
        uses: ./.github/actions/runner-cleanup
      - name: Setup Go
        uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
        with:
          go-version: 1.25.x
          cache-dependency-path: |
            **/go.sum
            **/go.mod
      - name: Setup Node
        uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
        with:
          node-version: 24
          cache: 'npm'
          cache-dependency-path: web/package-lock.json
      - name: Build frontend
        run: make web-ci-install web-build
      - name: Setup Kubernetes
        uses: helm/kind-action@92086f6be054225fa813e0a4b13787fc9088faab # v1.13.0
        with:
          version: v0.24.0
          cluster_name: kind
      - name: Setup QEMU
        uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
      - name: Setup Docker Buildx
        id: buildx
        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
      - name: Login to GitHub Container Registry
        uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Run OLM tests
        run: make test-olm-e2e
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

```

--------------------------------------------------------------------------------
/cmd/cli/resume_resource.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/spf13/cobra"
)

var resumeResourceCmd = &cobra.Command{
	Use:   "resource [kind/name]",
	Short: "Resume Flux resource reconciliation",
	Example: `  # Resume the reconciliation of a Flux Kustomization
  flux-operator -n apps resume resource Kustomization/my-app
`,
	Args:              cobra.ExactArgs(1),
	RunE:              resumeResourceCmdRun,
	ValidArgsFunction: resourceKindNameCompletionFunc(true),
}

type resumeResourceFlags struct {
	wait bool
}

var resumeResourceArgs resumeResourceFlags

func init() {
	resumeResourceCmd.Flags().BoolVar(&resumeResourceArgs.wait, "wait", true,
		"Wait for the resource to become ready.")
	resumeCmd.AddCommand(resumeResourceCmd)
}

func resumeResourceCmdRun(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("resource name is required")
	}

	parts := strings.Split(args[0], "/")
	if len(parts) != 2 {
		return fmt.Errorf("resource name must be in the format <kind>/<name>, e.g., HelmRelease/my-app")
	}

	kind := parts[0]
	name := parts[1]
	now := timeNow()

	gvk, err := preferredFluxGVK(kind, kubeconfigArgs)
	if err != nil {
		return fmt.Errorf("unable to get gvk for kind %s : %w", kind, err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
	defer cancel()

	err = toggleSuspension(ctx, *gvk, name, *kubeconfigArgs.Namespace, now, false)
	if err != nil {
		return err
	}

	if resumeResourceArgs.wait {
		rootCmd.Println(`◎`, "Waiting for reconciliation...")
		msg, err := waitForResourceReconciliation(ctx, *gvk, name, *kubeconfigArgs.Namespace, now, rootArgs.timeout)
		if err != nil {
			return err
		}
		rootCmd.Println(`✔`, msg)
	} else {
		rootCmd.Println(`✔`, "Reconciliation resumed")
	}

	return nil
}

```

--------------------------------------------------------------------------------
/internal/reporter/distribution.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package reporter

import (
	"context"
	"fmt"

	corev1 "k8s.io/api/core/v1"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
	"github.com/controlplaneio-fluxcd/flux-operator/internal/entitlement"
)

func (r *FluxStatusReporter) getDistributionStatus(ctx context.Context) fluxcdv1.FluxDistributionStatus {
	result := fluxcdv1.FluxDistributionStatus{
		Status:      "Unknown",
		Entitlement: "Unknown",
	}

	crdMeta := &metav1.PartialObjectMetadata{
		TypeMeta: metav1.TypeMeta{
			APIVersion: apiextensionsv1.SchemeGroupVersion.String(),
			Kind:       "CustomResourceDefinition",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: "gitrepositories.source.toolkit.fluxcd.io",
		},
	}
	if err := r.Get(ctx, client.ObjectKeyFromObject(crdMeta), crdMeta); err == nil {
		result.Status = "Installed"

		if version, found := crdMeta.Labels["app.kubernetes.io/version"]; found {
			result.Version = version
		}

		if manager, ok := crdMeta.Labels["app.kubernetes.io/managed-by"]; ok {
			result.ManagedBy = manager
		} else if _, ok := crdMeta.Labels["kustomize.toolkit.fluxcd.io/name"]; ok {
			result.ManagedBy = "flux bootstrap"
		}
	} else {
		result.Status = "Not Installed"
	}

	entitlementSecret := &corev1.Secret{}
	err := r.Get(ctx, client.ObjectKey{
		Namespace: r.namespace,
		Name:      fmt.Sprintf("%s-entitlement", r.manager),
	}, entitlementSecret)
	if err == nil {
		if _, found := entitlementSecret.Data[entitlement.TokenKey]; found {
			result.Entitlement = "Issued"
			if vendor, found := entitlementSecret.Data[entitlement.VendorKey]; found {
				result.Entitlement += " by " + string(vendor)
			}
		}
	}

	return result
}

```

--------------------------------------------------------------------------------
/web/src/utils/meta.js:
--------------------------------------------------------------------------------

```javascript
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

import { useEffect } from 'preact/hooks'

const BASE_TITLE = 'Flux Status'
const HOME_TITLE = 'Flux Status'
const HOME_DESCRIPTION = 'Real-time visibility into your GitOps pipelines'

/**
 * Sets the document title and og:title
 * @param {string|null} pageTitle - The page-specific title, or null for home page
 */
export function setPageTitle(pageTitle) {
  const title = pageTitle ? `${pageTitle} - ${BASE_TITLE}` : HOME_TITLE
  document.title = title
  const ogTitle = document.querySelector('meta[property="og:title"]')
  if (ogTitle) {
    ogTitle.setAttribute('content', title)
  }
}

/**
 * Sets the meta description and og:description
 * @param {string|null} description - The page description, or null for default
 */
export function setPageDescription(description) {
  const content = description || HOME_DESCRIPTION
  const metaDescription = document.querySelector('meta[name="description"]')
  if (metaDescription) {
    metaDescription.setAttribute('content', content)
  }
  const ogDescription = document.querySelector('meta[property="og:description"]')
  if (ogDescription) {
    ogDescription.setAttribute('content', content)
  }
}

/**
 * Hook to set the document title on component mount
 * @param {string|null} pageTitle - The page-specific title, or null for home page
 */
export function usePageTitle(pageTitle) {
  useEffect(() => {
    setPageTitle(pageTitle)
  }, [pageTitle])
}

/**
 * Hook to set both document title and meta description on component mount
 * @param {string|null} pageTitle - The page-specific title, or null for home page
 * @param {string|null} description - The page description, or null for default
 */
export function usePageMeta(pageTitle, description) {
  useEffect(() => {
    setPageTitle(pageTitle)
    setPageDescription(description)
  }, [pageTitle, description])
}

```

--------------------------------------------------------------------------------
/web/src/components/layout/ConnectionStatus.jsx:
--------------------------------------------------------------------------------

```javascript
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

import { useSignal } from '@preact/signals'
import { useEffect } from 'preact/hooks'
import { connectionStatus } from '../../app'

/**
 * ConnectionStatus component - Displays server connection status banner
 *
 * Shows a colored banner at the top of the page indicating:
 * - Loading: Gray banner with pulse animation (only if loading > 300ms)
 * - Disconnected: Red banner with error indication
 * - Connected: No banner (hidden)
 */
export function ConnectionStatus() {
  const status = connectionStatus.value
  const isDisconnected = status === 'disconnected'
  const isLoading = status === 'loading'
  const showLoading = useSignal(false)

  // Delay showing loading bar by 300ms to avoid flicker on fast fetches
  useEffect(() => {
    let timer
    if (isLoading) {
      timer = setTimeout(() => {
        showLoading.value = true
      }, 300)
    } else {
      showLoading.value = false
    }
    return () => window.clearTimeout(timer)
  }, [isLoading])

  // Only show the component if disconnected or loading (after delay)
  if (!isDisconnected && !showLoading.value) {
    return null
  }

  // Determine colors based on status
  let barColor = 'bg-gray-400'
  let barHeight = 'h-1'

  if (isDisconnected) {
    barColor = 'bg-red-500'
    barHeight = 'h-1.5'
  }

  return (
    <div class="fixed top-0 left-0 right-0 z-50">
      <div class={`${barColor} ${barHeight} w-full transition-colors duration-300`}>
        {showLoading.value && (
          <div class="absolute inset-0 bg-gradient-to-r from-transparent via-white to-transparent opacity-30 animate-pulse"></div>
        )}
      </div>
      {isDisconnected && (
        <div class="hidden sm:flex justify-center">
          <span class="bg-red-500 text-white text-xs font-medium px-3 py-1 rounded-b shadow-md">
            disconnected
          </span>
        </div>
      )}
    </div>
  )
}

```

--------------------------------------------------------------------------------
/web/src/utils/time.test.js:
--------------------------------------------------------------------------------

```javascript
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

import { formatTimestamp, formatTime } from './time'

describe('formatTimestamp', () => {
  const now = new Date()

  it('should return "just now" for timestamps less than 1 minute ago', () => {
    const date = new Date(now.getTime() - 30 * 1000) // 30 seconds ago
    expect(formatTimestamp(date)).toBe('just now')
  })

  it('should return "Xm ago" for timestamps less than 60 minutes ago', () => {
    const date = new Date(now.getTime() - 5 * 60 * 1000) // 5 minutes ago
    expect(formatTimestamp(date)).toBe('5m ago')
  })

  it('should return "Xh ago" for timestamps less than 24 hours ago', () => {
    const date = new Date(now.getTime() - 3 * 60 * 60 * 1000) // 3 hours ago
    expect(formatTimestamp(date)).toBe('3h ago')
  })

  it('should return absolute date for timestamps older than 24 hours', () => {
    const date = new Date(now.getTime() - 25 * 60 * 60 * 1000) // 25 hours ago
    // Mock Date.toLocaleString to ensure consistent output across environments
    const mockToLocaleString = vi.fn(() => 'Jan 15, 02:30 PM');
    date.toLocaleString = mockToLocaleString;
    expect(formatTimestamp(date)).toMatch(/\w{3} \d{1,2}, \d{2}:\d{2} (AM|PM)/)
  })
})

describe('formatTime', () => {
  it('should return "Never" for null or undefined input', () => {
    expect(formatTime(null)).toBe('Never')
    expect(formatTime(undefined)).toBe('Never')
  })

  it('should format a Date object into HH:MM:SS AM/PM format', () => {
    const date = new Date('2025-11-16T14:30:45Z') // 2:30:45 PM UTC
    // Adjust for local timezone if necessary, or use a fixed locale for testing
    // For simplicity, we'll test against a known output for a specific locale/timezone
    // This might need adjustment based on the test runner's environment
    const expectedTimeRegex = /\d{2}:\d{2}:\d{2} (AM|PM)/
    expect(formatTime(date)).toMatch(expectedTimeRegex)
  })
})

```

--------------------------------------------------------------------------------
/test/e2e/utils.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package e2e

import (
	"fmt"
	"os"
	"os/exec"
	"strings"

	. "github.com/onsi/ginkgo/v2"
)

// Run executes the provided command within this context
func Run(cmd *exec.Cmd, dirPattern string) ([]byte, error) {
	dir, _ := GetProjectDir(dirPattern)
	cmd.Dir = dir

	if err := os.Chdir(cmd.Dir); err != nil {
		fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) //nolint:errcheck
	}

	cmd.Env = append(os.Environ(), "GO111MODULE=on")
	command := strings.Join(cmd.Args, " ")
	fmt.Fprintf(GinkgoWriter, "running: %s\n", command) //nolint:errcheck
	output, err := cmd.CombinedOutput()
	if err != nil {
		return output, fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output))
	}

	return output, nil
}

// LoadImageToKindClusterWithName loads a local docker image to the kind cluster
func LoadImageToKindClusterWithName(name, dirPattern string) error {
	cluster := "kind"
	if v, ok := os.LookupEnv("KIND_CLUSTER"); ok {
		cluster = v
	}
	kindOptions := []string{"load", "docker-image", name, "--name", cluster}
	cmd := exec.Command("kind", kindOptions...)
	_, err := Run(cmd, dirPattern)
	return err
}

// GetNonEmptyLines converts given command output string into individual objects
// according to line breakers, and ignores the empty elements in it.
func GetNonEmptyLines(output string) []string {
	var res []string
	elements := strings.Split(output, "\n")
	for _, element := range elements {
		if element != "" {
			res = append(res, element)
		}
	}

	return res
}

// GetProjectDir will return the directory where the project is
// The pattern is used to remove the last part of the path
// e.g. /home/user/go/src/github.com/flux-operator/test/e2e -> /home/user/go/src/github.com/flux-operator
func GetProjectDir(pattern string) (string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return wd, err
	}
	wd = strings.ReplaceAll(wd, pattern, "")
	return wd, nil
}

```

--------------------------------------------------------------------------------
/hack/prep-release.sh:
--------------------------------------------------------------------------------

```bash
#!/usr/bin/env bash

# Copyright 2025 Stefan Prodan.
# SPDX-License-Identifier: AGPL-3.0

set -euo pipefail

REPOSITORY_ROOT=$(git rev-parse --show-toplevel)
LATEST_VERSION=$(gh release view --json tagName -q '.tagName')
NEXT_VERSION="$1"

info() {
    echo '[INFO] ' "$@"
}

fatal() {
    echo '[ERROR] ' "$@" >&2
    exit 1
}

# If the NEXT_VERSION is not provided, increment LATEST_VERSION to the next minor
if [ -z "${NEXT_VERSION:-}" ]; then
    info "No version provided, calculating next version from latest: ${LATEST_VERSION}"
    
    if [[ ${LATEST_VERSION} =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
        MAJOR=${BASH_REMATCH[1]}
        MINOR=${BASH_REMATCH[2]}
        NEXT_MINOR=$((MINOR + 1))
        NEXT_VERSION="v${MAJOR}.${NEXT_MINOR}.0"
        info "Calculated next version: ${NEXT_VERSION}"
    else
        fatal "Unable to parse version format from ${LATEST_VERSION}"
    fi
fi

if [ -z "${NEXT_VERSION}" ]; then
    fatal "NEXT_VERSION is required as an argument or will be auto-calculated"
fi

info "Preparing release ${NEXT_VERSION}"

BRANCH_NAME="release-${NEXT_VERSION}"
info "Creating and switching to branch: ${BRANCH_NAME}"
git checkout -b "${BRANCH_NAME}"

info "Updating version in kustomization files"
yq -i '.images[0].newTag = "'"${NEXT_VERSION}"'"' "${REPOSITORY_ROOT}/config/manager/kustomization.yaml"
yq -i '.images[0].newTag = "'"${NEXT_VERSION}"'"' "${REPOSITORY_ROOT}/config/mcp/kustomization.yaml"

info "Committing all config changes"
git add --all "config/"

if [[ -z $(git status --porcelain config/) ]]; then
    fatal "No changes to commit. Version may already be up to date."
fi

git commit -s -m "Release ${NEXT_VERSION}"

info "Pushing branch to remote"
git push origin "${BRANCH_NAME}"

info "Creating pull request"
gh pr create --title "Release ${NEXT_VERSION}" --body "Prepare release ${NEXT_VERSION}" --base main --head "${BRANCH_NAME}"

info "Release preparation complete for ${NEXT_VERSION}"

```

--------------------------------------------------------------------------------
/web/src/utils/theme.js:
--------------------------------------------------------------------------------

```javascript
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

import { signal, effect } from '@preact/signals'

// Theme modes
export const themes = {
  light: 'light',
  dark: 'dark',
  auto: 'auto'
}

// Get initial theme from localStorage or default to auto
const getInitialTheme = () => {
  const stored = localStorage.getItem('theme')
  return stored || themes.auto
}

// Check system preference
const getSystemTheme = () => {
  if (typeof window === 'undefined') return themes.light
  const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
  return systemPrefersDark ? themes.dark : themes.light
}

// Current theme selection (light/dark/auto)
export const themeMode = signal(getInitialTheme())

// Actual applied theme (light/dark only)
export const appliedTheme = signal(
  themeMode.value === themes.auto ? getSystemTheme() : themeMode.value
)

// Update applied theme when mode changes or system preference changes
effect(() => {
  const mode = themeMode.value

  if (mode === themes.auto) {
    appliedTheme.value = getSystemTheme()
  } else {
    appliedTheme.value = mode
  }

  // Save to localStorage
  localStorage.setItem('theme', mode)

  // Apply to document
  if (appliedTheme.value === themes.dark) {
    document.documentElement.classList.add('dark')
  } else {
    document.documentElement.classList.remove('dark')
  }
})

// Listen for system theme changes when in auto mode
if (typeof window !== 'undefined') {
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
    if (themeMode.value === themes.auto) {
      appliedTheme.value = e.matches ? themes.dark : themes.light
    }
  })
}

// Toggle between themes
export const cycleTheme = () => {
  const themeOrder = [themes.auto, themes.dark, themes.light]
  const currentIndex = themeOrder.indexOf(themeMode.value)
  const nextIndex = (currentIndex + 1) % themeOrder.length
  themeMode.value = themeOrder[nextIndex]
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/reconcile_helmrelease_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
	cli "k8s.io/cli-runtime/pkg/genericclioptions"

	"github.com/controlplaneio-fluxcd/flux-operator/cmd/mcp/k8s"
)

func TestManager_HandleReconcileHelmRelease(t *testing.T) {
	configFile := "testdata/kubeconfig.yaml"
	t.Setenv("KUBECONFIG", configFile)

	m := &Manager{
		kubeconfig: k8s.NewKubeConfig(),
		kubeClient: k8s.NewClientFactory(cli.NewConfigFlags(false)),
		timeout:    time.Second,
	}

	request := &mcp.CallToolRequest{
		Params: &mcp.CallToolParamsRaw{
			Name: "reconcile_flux_helmrelease",
		},
	}

	tests := []struct {
		testName  string
		arguments map[string]any
		matchErr  string
	}{
		{
			testName: "fails without name",
			arguments: map[string]any{
				"kind": "HelmRelease",
			},
			matchErr: "name is required",
		},
		{
			testName: "fails without namespace",
			arguments: map[string]any{
				"name": "test",
			},
			matchErr: "namespace is required",
		},
		{
			testName: "fails with invalid kubeconfig",
			arguments: map[string]any{
				"name":      "test",
				"namespace": "default",
			},
			matchErr: "Failed to get Kubernetes client",
		},
	}

	for _, test := range tests {
		t.Run(test.testName, func(t *testing.T) {
			g := NewWithT(t)
			argsJSON, _ := json.Marshal(test.arguments)
			request.Params.Arguments = argsJSON

			var input reconcileFluxHelmReleaseInput
			err := json.Unmarshal(request.Params.Arguments, &input)
			g.Expect(err).ToNot(HaveOccurred())
			result, content, err := m.HandleReconcileHelmRelease(context.Background(), request, input)
			g.Expect(err).ToNot(HaveOccurred())
			textContent, ok := result.Content[0].(*mcp.TextContent)
			g.Expect(ok).To(BeTrue())

			g.Expect(result.IsError).To(BeTrue())
			g.Expect(textContent.Text).To(ContainSubstring(test.matchErr))
			_ = content
		})
	}
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/reconcile_resourceset_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
	cli "k8s.io/cli-runtime/pkg/genericclioptions"

	"github.com/controlplaneio-fluxcd/flux-operator/cmd/mcp/k8s"
)

func TestManager_HandleReconcileResourceSet(t *testing.T) {
	configFile := "testdata/kubeconfig.yaml"
	t.Setenv("KUBECONFIG", configFile)

	m := &Manager{
		kubeconfig: k8s.NewKubeConfig(),
		kubeClient: k8s.NewClientFactory(cli.NewConfigFlags(false)),
		timeout:    time.Second,
	}

	request := &mcp.CallToolRequest{
		Params: &mcp.CallToolParamsRaw{
			Name: "reconcile_flux_resourceset",
		},
	}

	tests := []struct {
		testName  string
		arguments map[string]any
		matchErr  string
	}{
		{
			testName: "fails without name",
			arguments: map[string]any{
				"kind": "ResourceSet",
			},
			matchErr: "name is required",
		},
		{
			testName: "fails without namespace",
			arguments: map[string]any{
				"name": "test",
			},
			matchErr: "namespace is required",
		},
		{
			testName: "fails with invalid kubeconfig",
			arguments: map[string]any{
				"name":      "test",
				"namespace": "default",
			},
			matchErr: "Failed to get Kubernetes client",
		},
	}

	for _, test := range tests {
		t.Run(test.testName, func(t *testing.T) {
			g := NewWithT(t)
			argsJSON, _ := json.Marshal(test.arguments)
			request.Params.Arguments = argsJSON

			var input reconcileFluxResourceSetInput
			err := json.Unmarshal(request.Params.Arguments, &input)
			g.Expect(err).ToNot(HaveOccurred())
			result, content, err := m.HandleReconcileResourceSet(context.Background(), request, input)
			g.Expect(err).ToNot(HaveOccurred())
			textContent, ok := result.Content[0].(*mcp.TextContent)
			g.Expect(ok).To(BeTrue())

			g.Expect(result.IsError).To(BeTrue())
			g.Expect(textContent.Text).To(ContainSubstring(test.matchErr))
			_ = content
		})
	}
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/reconcile_kustomization_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
	cli "k8s.io/cli-runtime/pkg/genericclioptions"

	"github.com/controlplaneio-fluxcd/flux-operator/cmd/mcp/k8s"
)

func TestManager_HandleReconcileKustomization(t *testing.T) {
	configFile := "testdata/kubeconfig.yaml"
	t.Setenv("KUBECONFIG", configFile)

	m := &Manager{
		kubeconfig: k8s.NewKubeConfig(),
		kubeClient: k8s.NewClientFactory(cli.NewConfigFlags(false)),
		timeout:    time.Second,
	}

	request := &mcp.CallToolRequest{
		Params: &mcp.CallToolParamsRaw{
			Name: "reconcile_flux_kustomization",
		},
	}

	tests := []struct {
		testName  string
		arguments map[string]any
		matchErr  string
	}{
		{
			testName: "fails without name",
			arguments: map[string]any{
				"kind": "Kustomization",
			},
			matchErr: "name is required",
		},
		{
			testName: "fails without namespace",
			arguments: map[string]any{
				"name": "test",
			},
			matchErr: "namespace is required",
		},
		{
			testName: "fails with invalid kubeconfig",
			arguments: map[string]any{
				"name":      "test",
				"namespace": "default",
			},
			matchErr: "Failed to get Kubernetes client",
		},
	}

	for _, test := range tests {
		t.Run(test.testName, func(t *testing.T) {
			g := NewWithT(t)
			argsJSON, _ := json.Marshal(test.arguments)
			request.Params.Arguments = argsJSON

			var input reconcileFluxKustomizationInput
			err := json.Unmarshal(request.Params.Arguments, &input)
			g.Expect(err).ToNot(HaveOccurred())
			result, content, err := m.HandleReconcileKustomization(context.Background(), request, input)
			g.Expect(err).ToNot(HaveOccurred())
			textContent, ok := result.Content[0].(*mcp.TextContent)
			g.Expect(ok).To(BeTrue())

			g.Expect(result.IsError).To(BeTrue())
			g.Expect(textContent.Text).To(ContainSubstring(test.matchErr))
			_ = content
		})
	}
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/search_flux_docs_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"
	"encoding/json"
	"testing"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
)

func TestManager_HandleSearchFluxDocs(t *testing.T) {
	request := &mcp.CallToolRequest{
		Params: &mcp.CallToolParamsRaw{
			Name: "search_flux_docs",
		},
	}
	m := &Manager{}

	tests := []struct {
		testName     string
		arguments    map[string]any
		matchErr     string
		matchResults []string
	}{
		{
			testName: "fails with not found",
			arguments: map[string]any{
				"query": "notfound",
			},
			matchErr: "No documents found",
		},
		{
			testName: "returns single result",
			arguments: map[string]any{
				"query": "GitHub Pull Request",
			},
			matchResults: []string{"ResourceSetInputProvider"},
		},
		{
			testName: "returns multiple results",
			arguments: map[string]any{
				"query": "GitLab Merge Request",
				"limit": 2,
			},
			matchResults: []string{
				"ResourceSetInputProvider",
				"GitRepository",
			}},
	}

	for _, test := range tests {
		t.Run(test.testName, func(t *testing.T) {
			g := NewWithT(t)
			argsJSON, _ := json.Marshal(test.arguments)
			request.Params.Arguments = argsJSON

			var input searchFluxDocsInput
			err := json.Unmarshal(request.Params.Arguments, &input)
			g.Expect(err).ToNot(HaveOccurred())
			result, _, err := m.HandleSearchFluxDocs(context.Background(), request, input)
			g.Expect(err).ToNot(HaveOccurred())

			g.Expect(result).ToNot(BeNil())
			g.Expect(result.Content).ToNot(BeEmpty())
			textContent, ok := result.Content[0].(*mcp.TextContent)
			g.Expect(ok).To(BeTrue())

			if test.matchErr != "" {
				g.Expect(result.IsError).To(BeTrue())
				g.Expect(textContent.Text).To(ContainSubstring(test.matchErr))
			} else {
				g.Expect(result.IsError).To(BeFalse())
				for _, matchResult := range test.matchResults {
					g.Expect(textContent.Text).To(ContainSubstring(matchResult))
				}
			}
		})
	}
}

```

--------------------------------------------------------------------------------
/cmd/cli/reconcile_resourceset.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package main

import (
	"context"
	"fmt"

	"github.com/fluxcd/pkg/apis/meta"
	"github.com/spf13/cobra"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

var reconcileResourceSetCmd = &cobra.Command{
	Use:     "resourceset [name]",
	Aliases: []string{"rset"},
	Short:   "Trigger ResourceSet reconciliation",
	Example: `  # Trigger the reconciliation of a ResourceSet
  flux-operator -n flux-system reconcile rset my-resourceset

  # Trigger the reconciliation of a ResourceSet without waiting for it to become ready
  flux-operator -n flux-system reconcile rset my-resourceset --wait=false
`,
	Args:              cobra.ExactArgs(1),
	RunE:              reconcileResourceSetCmdRun,
	ValidArgsFunction: resourceNamesCompletionFunc(fluxcdv1.GroupVersion.WithKind(fluxcdv1.ResourceSetKind)),
}

type reconcileResourceSetFlags struct {
	wait bool
}

var reconcileResourceSetArgs reconcileResourceSetFlags

func init() {
	reconcileResourceSetCmd.Flags().BoolVar(&reconcileResourceSetArgs.wait, "wait", true,
		"Wait for the resource to become ready.")
	reconcileCmd.AddCommand(reconcileResourceSetCmd)
}

func reconcileResourceSetCmdRun(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("name is required")
	}

	name := args[0]
	now := timeNow()
	gvk := fluxcdv1.GroupVersion.WithKind(fluxcdv1.ResourceSetKind)

	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
	defer cancel()

	err := annotateResource(ctx, gvk, name, *kubeconfigArgs.Namespace, meta.ReconcileRequestAnnotation, now)
	if err != nil {
		return err
	}

	rootCmd.Println(`►`, "Reconciliation triggered")
	if reconcileResourceSetArgs.wait {
		rootCmd.Println(`◎`, "Waiting for reconciliation...")
		msg, err := waitForResourceReconciliation(ctx, gvk, name, *kubeconfigArgs.Namespace, now, rootArgs.timeout)
		if err != nil {
			return err
		}

		rootCmd.Println(`✔`, msg)
	}

	return nil
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/get_resource_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
	cli "k8s.io/cli-runtime/pkg/genericclioptions"

	"github.com/controlplaneio-fluxcd/flux-operator/cmd/mcp/k8s"
)

func TestManager_HandleGetKubernetesResources(t *testing.T) {
	configFile := "testdata/kubeconfig.yaml"
	t.Setenv("KUBECONFIG", configFile)

	m := &Manager{
		kubeconfig: k8s.NewKubeConfig(),
		kubeClient: k8s.NewClientFactory(cli.NewConfigFlags(false)),
		timeout:    time.Second,
	}

	request := &mcp.CallToolRequest{
		Params: &mcp.CallToolParamsRaw{
			Name: "get_kubernetes_resources",
		},
	}

	tests := []struct {
		testName  string
		arguments map[string]any
		matchErr  string
	}{
		{
			testName: "fails without apiVersion",
			arguments: map[string]any{
				"name": "test",
			},
			matchErr: "apiVersion is required",
		},
		{
			testName: "fails without kind",
			arguments: map[string]any{
				"apiVersion": "v1",
				"name":       "test",
			},
			matchErr: "kind is required",
		},
		{
			testName: "fails with invalid kubeconfig",
			arguments: map[string]any{
				"apiVersion": "apps/v1",
				"kind":       "Deployment",
			},
			matchErr: "Failed to get Kubernetes client",
		},
	}

	for _, test := range tests {
		t.Run(test.testName, func(t *testing.T) {
			g := NewWithT(t)
			argsJSON, _ := json.Marshal(test.arguments)
			request.Params.Arguments = argsJSON

			var input getKubernetesResourcesInput
			err := json.Unmarshal(request.Params.Arguments, &input)
			g.Expect(err).ToNot(HaveOccurred())
			result, content, err := m.HandleGetKubernetesResources(context.Background(), request, input)
			g.Expect(err).ToNot(HaveOccurred())
			textContent, ok := result.Content[0].(*mcp.TextContent)
			g.Expect(ok).To(BeTrue())

			g.Expect(result.IsError).To(BeTrue())
			g.Expect(textContent.Text).To(ContainSubstring(test.matchErr))
			_ = content
		})
	}
}

```

--------------------------------------------------------------------------------
/cmd/mcp/prompter/debug_helmrelease_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package prompter

import (
	"context"
	"testing"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
)

func TestManager_HandleDebugHelmRelease(t *testing.T) {
	m := &Manager{}

	tests := []struct {
		testName     string
		arguments    map[string]string
		matchErr     string
		matchMessage string
	}{
		{
			testName: "fails without name",
			arguments: map[string]string{
				"kind": "HelmRelease",
			},
			matchErr: "missing name argument",
		},
		{
			testName: "fails without namespace",
			arguments: map[string]string{
				"name": "test",
			},
			matchErr: "missing namespace argument",
		},
		{
			testName: "message with identifier",
			arguments: map[string]string{
				"name":      "test",
				"namespace": "apps",
			},
			matchMessage: "HelmRelease test in namespace apps on the current cluster",
		},
		{
			testName: "message with cluster",
			arguments: map[string]string{
				"name":      "test",
				"namespace": "apps",
				"cluster":   "dev",
			},
			matchMessage: "HelmRelease test in namespace apps on the dev cluster",
		},
	}

	for _, test := range tests {
		t.Run(test.testName, func(t *testing.T) {
			g := NewWithT(t)

			request := &mcp.GetPromptRequest{
				Params: &mcp.GetPromptParams{
					Name:      "debug_flux_helmrelease",
					Arguments: test.arguments,
				},
			}

			result, err := m.HandleDebugHelmRelease(context.Background(), request)

			if test.matchErr != "" {
				g.Expect(err).To(HaveOccurred())
				g.Expect(err.Error()).To(ContainSubstring(test.matchErr))
			} else {
				g.Expect(err).ToNot(HaveOccurred())
				g.Expect(result.Messages).To(HaveLen(1))
				g.Expect(result.Messages[0].Role).To(Equal(mcp.Role("assistant")))

				// In the new SDK, Content is a pointer to TextContent
				textContent, ok := result.Messages[0].Content.(*mcp.TextContent)
				g.Expect(ok).To(BeTrue())

				g.Expect(textContent.Text).To(ContainSubstring(test.matchMessage))
			}
		})
	}
}

```

--------------------------------------------------------------------------------
/cmd/mcp/prompter/debug_kustomization_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package prompter

import (
	"context"
	"testing"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
)

func TestManager_HandleDebugKustomization(t *testing.T) {
	m := &Manager{}

	tests := []struct {
		testName     string
		arguments    map[string]string
		matchErr     string
		matchMessage string
	}{
		{
			testName: "fails without name",
			arguments: map[string]string{
				"kind": "HelmRelease",
			},
			matchErr: "missing name argument",
		},
		{
			testName: "fails without namespace",
			arguments: map[string]string{
				"name": "test",
			},
			matchErr: "missing namespace argument",
		},
		{
			testName: "message with identifier",
			arguments: map[string]string{
				"name":      "test",
				"namespace": "apps",
			},
			matchMessage: "Kustomization test in namespace apps on the current cluster",
		},
		{
			testName: "message with cluster",
			arguments: map[string]string{
				"name":      "test",
				"namespace": "apps",
				"cluster":   "dev",
			},
			matchMessage: "Kustomization test in namespace apps on the dev cluster",
		},
	}

	for _, test := range tests {
		t.Run(test.testName, func(t *testing.T) {
			g := NewWithT(t)

			request := &mcp.GetPromptRequest{
				Params: &mcp.GetPromptParams{
					Name:      "debug_flux_kustomization",
					Arguments: test.arguments,
				},
			}

			result, err := m.HandleDebugKustomization(context.Background(), request)

			if test.matchErr != "" {
				g.Expect(err).To(HaveOccurred())
				g.Expect(err.Error()).To(ContainSubstring(test.matchErr))
			} else {
				g.Expect(err).ToNot(HaveOccurred())
				g.Expect(result.Messages).To(HaveLen(1))
				g.Expect(result.Messages[0].Role).To(Equal(mcp.Role("assistant")))

				// In the new SDK, Content is a pointer to TextContent
				textContent, ok := result.Messages[0].Content.(*mcp.TextContent)
				g.Expect(ok).To(BeTrue())

				g.Expect(textContent.Text).To(ContainSubstring(test.matchMessage))
			}
		})
	}
}

```

--------------------------------------------------------------------------------
/cmd/cli/version.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package main

import (
	"context"
	"fmt"

	"github.com/spf13/cobra"
	"sigs.k8s.io/controller-runtime/pkg/client"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

var versionCmd = &cobra.Command{
	Use:   "version",
	Short: "Print the client and server version information",
	Args:  cobra.NoArgs,
	RunE:  versionCmdRun,
}

type versionFlags struct {
	clientOnly bool
}

var versionArgs versionFlags

func init() {
	versionCmd.Flags().BoolVar(&versionArgs.clientOnly, "client", false,
		"If true, shows client version only (no server required).")
	rootCmd.AddCommand(versionCmd)
}

func versionCmdRun(cmd *cobra.Command, args []string) error {
	_, err := fmt.Fprintln(rootCmd.OutOrStdout(), "client:", VERSION)
	if err != nil {
		return fmt.Errorf("failed to print client version: %w", err)
	}

	if versionArgs.clientOnly {
		return nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
	defer cancel()

	kubeClient, err := newKubeClient()
	if err != nil {
		return fmt.Errorf("unable to create kube client error: %w", err)
	}

	lsOpts := &client.ListOptions{}
	var list fluxcdv1.FluxReportList
	err = kubeClient.List(ctx, &list, lsOpts)
	if err != nil {
		return fmt.Errorf("failed to get FluxReport resource: %w", err)
	}

	if len(list.Items) == 0 {
		return fmt.Errorf("no FluxReport resources found, ensure the Flux Operator is installed")
	}

	report := list.Items[0]
	if report.Spec.Operator == nil || report.Spec.Operator.Version == "" {
		return fmt.Errorf("operator version not found in FluxReport resource")
	}

	_, err = fmt.Fprintln(rootCmd.OutOrStdout(), "server:", report.Spec.Operator.Version)
	if err != nil {
		return fmt.Errorf("failed to print server version: %w", err)
	}

	if report.Spec.Distribution.Version != "" {
		_, err = fmt.Fprintln(rootCmd.OutOrStdout(), "distribution:", report.Spec.Distribution.Version)
		if err != nil {
			return fmt.Errorf("failed to print distribution version: %w", err)
		}
	}

	return nil
}

```

--------------------------------------------------------------------------------
/web/src/components/layout/NotFoundPage.jsx:
--------------------------------------------------------------------------------

```javascript
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

/**
 * NotFoundPage - 404 error page for unmatched routes
 *
 * Displays a friendly error message with a link to return to the home page.
 */
export function NotFoundPage() {
  return (
    <main data-testid="not-found-page" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 flex-grow w-full">
      <div class="flex items-center justify-center min-h-[60vh]">
        <div class="text-center">
          {/* 404 Icon */}
          <div class="mx-auto w-20 h-20 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center mb-6">
            <svg class="w-12 h-12 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
            </svg>
          </div>

          {/* Heading */}
          <h1 class="text-2xl sm:text-3xl font-semibold text-gray-900 dark:text-white mb-2">
            Page Not Found
          </h1>

          {/* Description */}
          <p class="text-gray-500 dark:text-gray-400 mb-8 max-w-md mx-auto">
            The page you're looking for doesn't exist or has been moved.
          </p>

          {/* Go Home Link */}
          <a
            href="/"
            class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-flux-blue hover:bg-blue-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-flux-blue transition-colors"
          >
            <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
            </svg>
            Go to Home
          </a>
        </div>
      </div>
    </main>
  )
}

```

--------------------------------------------------------------------------------
/internal/controller/fluxinstance_artifact_manager.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package controller

import (
	"k8s.io/client-go/util/workqueue"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/builder"
	"sigs.k8s.io/controller-runtime/pkg/controller"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/handler"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

// FluxInstanceArtifactReconcilerOptions contains options for the reconciler.
type FluxInstanceArtifactReconcilerOptions struct {
	RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
}

// ArtifactReconciliationConfigurationChangedPredicate contains the logic
// to determine if the annotations of a FluxInstance object relevant to the
// artifact reconciliation have changed.
type ArtifactReconciliationConfigurationChangedPredicate struct {
	predicate.Funcs
}

// SetupWithManager sets up the controller with the Manager.
func (r *FluxInstanceArtifactReconciler) SetupWithManager(mgr ctrl.Manager, opts FluxInstanceArtifactReconcilerOptions) error {
	return ctrl.NewControllerManagedBy(mgr).
		Named("fluxinstance_artifact").
		Watches(&fluxcdv1.FluxInstance{},
			&handler.EnqueueRequestForObject{},
			builder.WithPredicates(ArtifactReconciliationConfigurationChangedPredicate{})).
		WithOptions(controller.Options{
			RateLimiter: opts.RateLimiter,
		}).Complete(r)
}

func (ArtifactReconciliationConfigurationChangedPredicate) Update(e event.UpdateEvent) bool {
	if e.ObjectOld == nil || e.ObjectNew == nil {
		return false
	}

	// Start reconciliation if the object was disabled and is now enabled.
	oldObj := e.ObjectOld.(*fluxcdv1.FluxInstance)
	newObj := e.ObjectNew.(*fluxcdv1.FluxInstance)
	if oldObj.IsDisabled() && !newObj.IsDisabled() {
		return true
	}

	// Trigger reconciliation if the artifact interval has changed.
	if oldObj.GetArtifactInterval() != newObj.GetArtifactInterval() {
		return true
	}

	return false
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/search_flux_docs.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"
	"fmt"
	"strings"

	"github.com/modelcontextprotocol/go-sdk/mcp"

	"github.com/controlplaneio-fluxcd/flux-operator/cmd/mcp/toolbox/library"
)

const (
	// ToolSearchFluxDocs is the name of the search_flux_docs tool.
	ToolSearchFluxDocs = "search_flux_docs"
)

func init() {
	systemTools[ToolSearchFluxDocs] = systemTool{
		readOnly:  true,
		inCluster: true,
	}
}

// searchFluxDocsInput defines the input parameters for searching Flux documentation.
type searchFluxDocsInput struct {
	Query string  `json:"query" jsonschema:"The search query."`
	Limit float64 `json:"limit,omitempty" jsonschema:"The maximum number of matching documents to return. Default is 1."`
}

// HandleSearchFluxDocs is the handler function for the search_flux_docs tool.
func (m *Manager) HandleSearchFluxDocs(ctx context.Context, request *mcp.CallToolRequest, input searchFluxDocsInput) (*mcp.CallToolResult, any, error) {
	if err := CheckScopes(ctx, ToolSearchFluxDocs, m.readOnly); err != nil {
		return NewToolResultError(err.Error())
	}

	limit := int(input.Limit)
	if limit == 0 {
		limit = 1
	}

	// Use the embedded search index
	index := library.GetSearchIndex()
	if index == nil {
		return NewToolResultError("Search index not available. Run 'make mcp-build-search-index' to build it.")
	}

	results := index.Search(input.Query, limit)
	if len(results) == 0 {
		return NewToolResultError("No documents found")
	}

	// Format results
	var content strings.Builder
	for i, result := range results {
		if i > 0 {
			content.WriteString("\n\n---\n\n")
		}

		// Add metadata header
		content.WriteString(fmt.Sprintf("# %s (%s)\n\n",
			result.Document.Metadata.Kind,
			result.Document.Metadata.Group))
		content.WriteString(fmt.Sprintf("**URL:** %s\n\n",
			result.Document.Metadata.URL))
		content.WriteString(fmt.Sprintf("**Score:** %v\n\n",
			result.Score))

		// Add document content
		content.WriteString(result.Document.Content)
	}

	return NewToolResultText(content.String())
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/install_instance_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	. "github.com/onsi/gomega"
	cli "k8s.io/cli-runtime/pkg/genericclioptions"

	"github.com/controlplaneio-fluxcd/flux-operator/cmd/mcp/k8s"
)

func TestManager_HandleInstallFluxInstance(t *testing.T) {
	configFile := "testdata/kubeconfig.yaml"
	t.Setenv("KUBECONFIG", configFile)

	m := &Manager{
		kubeconfig: k8s.NewKubeConfig(),
		kubeClient: k8s.NewClientFactory(cli.NewConfigFlags(false)),
		timeout:    time.Second,
	}

	request := &mcp.CallToolRequest{
		Params: &mcp.CallToolParamsRaw{
			Name: "install_flux_instance",
		},
	}

	tests := []struct {
		testName  string
		arguments map[string]any
		matchErr  string
	}{
		{
			testName: "fails without instance_url",
			arguments: map[string]any{
				"instance_url": "",
			},
			matchErr: "The instance URL cannot be empty",
		},
		{
			testName: "fails with invalid timeout",
			arguments: map[string]any{
				"instance_url": "https://example.com/instance.yaml",
				"timeout":      "invalid",
			},
			matchErr: "The timeout is not a valid duration",
		},
		{
			testName: "fails with invalid kubeconfig",
			arguments: map[string]any{
				"instance_url": "https://example.com/instance.yaml",
			},
			matchErr: "failed to fetch instance manifest",
		},
	}

	for _, test := range tests {
		t.Run(test.testName, func(t *testing.T) {
			g := NewWithT(t)
			argsJSON, _ := json.Marshal(test.arguments)
			request.Params.Arguments = argsJSON

			var input installFluxInstanceInput
			err := json.Unmarshal(request.Params.Arguments, &input)
			g.Expect(err).ToNot(HaveOccurred())
			result, content, err := m.HandleInstallFluxInstance(context.Background(), request, input)
			g.Expect(err).ToNot(HaveOccurred())
			textContent, ok := result.Content[0].(*mcp.TextContent)
			g.Expect(ok).To(BeTrue())

			g.Expect(result.IsError).To(BeTrue())
			g.Expect(textContent.Text).To(ContainSubstring(test.matchErr))
			_ = content
		})
	}
}

```

--------------------------------------------------------------------------------
/internal/filtering/filters.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package filtering

import (
	"regexp"
	"slices"

	"github.com/Masterminds/semver/v3"
	"github.com/fluxcd/pkg/version"

	fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
)

// DefaultLimit is the default limit for the number of results returned by the filters.
const DefaultLimit = 100

// Filters holds the filters for the input provider responses.
type Filters struct {
	// Labels is used for a "match labels" filter.
	Labels []string

	// Include is used for including tags or branches.
	Include *regexp.Regexp

	// Exclude is used for excluding tags or branches.
	Exclude *regexp.Regexp

	// SemVer is used for sorting and filtering tags.
	// Supported only for tags at the moment.
	SemVer *semver.Constraints

	// Limit is used to limit the number of results.
	Limit int
}

// MatchLabels returns true if the given labels include all the label filters.
func (f *Filters) MatchLabels(labels []string) bool {
	for _, label := range f.Labels {
		if !slices.Contains(labels, label) {
			return false
		}
	}
	return true
}

// MatchString returns true if the string matches the include and exclude regex filters.
func (f *Filters) MatchString(s string) bool {
	if f.Include != nil {
		if !f.Include.MatchString(s) {
			return false
		}
	}
	if f.Exclude != nil {
		if f.Exclude.MatchString(s) {
			return false
		}
	}
	return true
}

// Tags applies all the filters supported for tags to a list of tags.
// nolint:prealloc
func (f *Filters) Tags(tags []string) []string {

	var filtered []string

	// Apply include and exclude.
	for _, tag := range tags {
		if f.MatchString(tag) {
			filtered = append(filtered, tag)
		}
	}

	// Apply semver or sort in reverse alphabetical order.
	switch {
	case f.SemVer != nil:
		filtered = version.Sort(f.SemVer, filtered)
	default:
		slices.Sort(filtered)
		slices.Reverse(filtered)
	}

	// Apply limit.
	lim := fluxcdv1.DefaultResourceSetInputProviderFilterLimit
	if f.Limit > 0 {
		lim = f.Limit
	}
	lim = min(lim, len(filtered))
	return slices.Clone(filtered[:lim])
}

```

--------------------------------------------------------------------------------
/cmd/mcp/toolbox/get_metrics.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package toolbox

import (
	"context"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	"sigs.k8s.io/yaml"
)

const (
	// ToolGetKubernetesMetrics is the name of the get_kubernetes_metrics tool.
	ToolGetKubernetesMetrics = "get_kubernetes_metrics"
)

func init() {
	systemTools[ToolGetKubernetesMetrics] = systemTool{
		readOnly:  true,
		inCluster: true,
	}
}

// getKubernetesMetricsInput defines the input parameters for retrieving pod metrics.
type getKubernetesMetricsInput struct {
	PodName      string  `json:"pod_name,omitempty" jsonschema:"The name of the pod when not specified all pods are selected."`
	PodNamespace string  `json:"pod_namespace" jsonschema:"The namespace of the pods."`
	PodSelector  string  `json:"pod_selector,omitempty" jsonschema:"The pod label selector in the format key1=value1 key2=value2."`
	Limit        float64 `json:"limit,omitempty" jsonschema:"The maximum number of resources to return. Defaults to 100."`
}

// HandleGetKubernetesMetrics is the handler function for the get_kubernetes_metrics tool.
func (m *Manager) HandleGetKubernetesMetrics(ctx context.Context, request *mcp.CallToolRequest, input getKubernetesMetricsInput) (*mcp.CallToolResult, any, error) {
	if err := CheckScopes(ctx, ToolGetKubernetesMetrics, m.readOnly); err != nil {
		return NewToolResultError(err.Error())
	}

	if input.PodNamespace == "" {
		return NewToolResultError("pod namespace is required")
	}
	limit := int(input.Limit)
	if limit == 0 {
		limit = 100
	}

	ctx, cancel := context.WithTimeout(ctx, m.timeout)
	defer cancel()

	kubeClient, err := m.kubeClient.GetClient(ctx)
	if err != nil {
		return NewToolResultErrorFromErr("Failed to get Kubernetes client", err)
	}

	result, err := kubeClient.GetMetrics(ctx, input.PodName, input.PodNamespace, input.PodSelector, limit)
	if err != nil {
		return NewToolResultError(err.Error())
	}

	data, err := yaml.Marshal(result)
	if err != nil {
		return NewToolResultErrorFromErr("Failed marshalling data", err)
	}

	return NewToolResultText(string(data))
}

```
Page 3/97FirstPrevNextLast