Imported from stefanprodan/timoni (
AGENTS.md). Install upstream withnpx skills add stefanprodan/timoni. Copyright stays with the author.
AGENTS.md
High-signal onboarding for AI coding agents working in Timoni.
What Timoni is
Timoni is a package manager for Kubernetes powered by CUE, inspired by Helm. Apps are defined as Modules (type-safe CUE templates + config schema, distributed as OCI artifacts), deployed as Instances (a module rendered with values and applied to a cluster), and composed via Bundles (multiple instances declared together). Helm analogy: module ≈ chart, bundle ≈ umbrella chart, instance ≈ release. A Runtime is the environment a bundle is applied into: it defines the target clusters and the values fetched live from cluster resources (Secrets/ConfigMaps) for injection into bundles.
Common commands
make build # Build ./bin/timoni (CGO_ENABLED=0)
make test # tidy + generate + fmt + vet + run all Go tests with Kubernetes envtest
make generate # Regenerate api/v1alpha1/zz_generated.deepcopy.go via controller-gen
make cue-vet # cue fmt + cue vet schemas, and `timoni mod vet` the example/blueprint/testdata modules
make docgen # Regenerate docs/cmd/*.mdx from the cobra commands via `timoni docgen`
Run a single test:
go test ./cmd/timoni/... -run TestApply -v
make testis the canonical pre-commit gate; it runsgo mod tidy, code generation, fmt, vet and golangci-lint before testing, so a cleanmake testimplies all of those pass.- Tests in
cmd/timoniand packages that talk to the API server require envtest (make install-envtestdownloads the Kubernetes test binaries into./bin). - After changing CUE schemas or example modules, run
make cue-vet. The blueprint, example and testdata modules vendor the canonicalschemas/timoni.sh/core/v1alpha1package as relative symlinks undercue.mod/pkg, so schema changes propagate without a sync step. After changing types inapi/v1alpha1, runmake generate.
Architecture
The data flow for an apply (install/upgrade) is: fetch module → build CUE → render K8s objects → reconcile onto cluster → record inventory. Trace it through these layers:
cmd/timoni/ — CLI (cobra)
One file per command (e.g. apply.go, build.go, bundle_apply.go, mod_push.go), each with a sibling _test.go. main.go defines the root command, global flags (--timeout, --namespace via kubeconfigArgs, registry flags), and logger injection. Commands wire together the engine, reconciler, oci, and runtime packages — they hold little logic themselves. Tests inject a logger and drive the real cobra command, asserting with the gomega matchers and in-process OCI registry from internal/testutils. The CRD vendoring suite is the exception that uses on-disk golden fixtures — regen with the timoni mod vendor crd … commands documented at the top of mod_vendor_crd_test.go.
internal/engine/ — CUE acquisition and compilation
The core of Timoni. Turns CUE into Kubernetes objects:
ModuleBuilder— compiles a module's CUE package into K8s objects, injecting instance name, namespace, module version, andkubeVersion(overridable viaTIMONI_KUBE_VERSION). Default Kubernetes version is thedefaultKubeVersionconstant inmodule_builder.go.ValuesBuilder— merges--valuesoverlays on top of the module'svalues.cue.BundleBuilder/RuntimeBuilder— compile Bundle and Runtime CUE definitions; bundles instantiate per-workspace with runtime values injected.RuntimeInjector— substitutes runtime values (cluster-read secrets/config) into bundles.ResourceSet— the rendered set of objects.HealthCheck— extracts the custom health checks a module declares undertimoni: healthChecks:(the#HealthCheck/#HealthCheckForConditionCUE schemas) for custom resources that are not kstatus-compliant.Importer(importer.go) — generates CUE definitions from Kubernetes CRDs by converting their OpenAPI v3 schemas (this is whattimoni mod vendor crdruns, letting module authors use custom resources type-safely).CRDValidator(crd_validator.go) — validates rendered custom resources with the kube-apiserver admission packages (OpenAPI schema, CEL rules, list uniqueness) against the original CRDs, which theImporterembeds in the generatedtypes_gen.cueas a hidden_crdfield andtimoni mod vetcollects from the module imports and output.fetcher/— pulls module sources, eitherlocal.go(filesystem path) oroci.go(OCI registry).
internal/reconciler/ — server-side apply
Reconciler (built via NewReconciler) takes the engine's build result and applies it using github.com/fluxcd/pkg/ssa (server-side apply): diff, apply, wait-for-ready, prune stale objects, handle force-recreate of immutable fields. interactive.go wraps it as InteractiveReconciler — the variant apply/bundle apply actually use — which adds the --dry-run (server-side dry-run) and --diff flows, rendering diffs via internal/dyff. Honors the action.timoni.sh/* annotations (force, prune, one-off, wait), all defined in api/v1alpha1/actions.go.
internal/runtime/ — cluster-side state
Reflects instances on the cluster: the instance inventory is stored in a Secret named timoni.<instance> (storage.go); instances.go/resources.go read back applied resources; reader.go reads runtime values from cluster objects; job_wait.go waits on Jobs; resource_wait.go plugs the module-defined custom health checks into the kstatus polling as a status reader.
Runtime & multi-cluster
A Runtime (#Runtime CUE schema; apiv1.Runtime/RuntimeCluster) declares a list of target clusters — each with a name, group, and kubeContext — plus values read live from the cluster. Bundle commands take it via the persistent --runtime/-r flag (or --runtime-from-env) and select clusters with --runtime-cluster/--runtime-group (both default * = all).
- Multi-cluster delivery (the key mechanic):
Runtime.SelectClusters()resolves the selection, thenbundle apply/bundle buildloop over each selected cluster, switchingkubeconfigArgs.Contextto that cluster's kube-context per iteration. Each cluster'sname/groupare exposed to the bundle asTIMONI_CLUSTER_NAME/TIMONI_CLUSTER_GROUP, so config can vary per cluster. - Two value sources: live cluster resources (read via
internal/runtime'sNewResourceReaderbefore the build), and — with--runtime-from-env— the process environment (engine.GetEnv()copies all OS env vars into the runtime values map). The env path is what makes Timoni CI-friendly: a pipeline injects secrets/config as env vars (no cluster read needed) and the bundle references them like any other runtime value. - No runtime supplied: a single
_defaultcluster using the current kube-context (DefaultRuntime).
internal/oci/ — OCI artifact distribution
Push/pull/list/tag/sign modules and generic artifacts to/from container registries via go-containerregistry (crane). Signing (sign_cosign.go) shells out to an external cosign binary that must be on PATH — it is not an embedded Go library. Modules are versioned OCI artifacts living next to app images.
api/v1alpha1/ — Go API types
Go structs for Bundle, Instance, Runtime, Module, Artifact, Inventory, plus the action.timoni.sh / selector annotation constants. zz_generated.deepcopy.go is generated — never edit by hand; run make generate. schema.go bridges to the embedded CUE schemas.
schemas/ — CUE schemas (single source of truth)
schemas/timoni.sh/core/v1alpha1/*.cue are the canonical CUE definitions (Bundle, Instance, Runtime, etc.), embedded into the binary via schemas/embed.go (//go:embed) and published as the importable timoni.sh/core/v1alpha1 CUE package that module authors import. Changing a schema here changes both the Go-side validation and what users import.
docs/ — user-facing documentation (published to timoni.sh)
The MDX site is built with Mintlify (docs/docs.json holds the theme, navigation and redirects; preview with cd docs && npx mint dev). Pages use Mintlify components (<Tip>, <Tabs>, <Card>), root-relative links without extensions (/bundle, /cue/module/signing) and title/description frontmatter. Three tiers, all hand-written except cmd/:
docs/root — the feature/concept guides (one.mdxper feature, e.g.bundle*.mdx,concepts.mdx,module.mdx, plus Flux/GitOps integration pages). The Bundle/Runtime feature set lives here; notebundle-runtime.mdxdocuments the non-obvious@timoni(runtime:…)attributes and env-var values.ls docs/*.mdxfor the full set.docs/cue/module/— the module-authoring behavior contracts that mirror code (apply/prune/wait semantics, immutability, signing, CRD vendoring, test jobs, semver).ls docs/cue/module/for the full set.docs/cmd/— the generated CLI reference (do not hand-edit — produced bymake docgen, gitignored, published from thewebsitebranch by the docs workflow). New commands must also be added to the CLI Reference tab indocs/docs.json.skills/timoni/SKILL.md— the agent skill; see theskills/section below..mcp.json— points AI agents at the docs MCP server Mintlify hosts athttps://timoni.sh/mcp(search over the published docs).
Timoni version references in docs/ (action pins, TIMONI_VER= in the install guide) use the {{TIMONI_VERSION}} marker inside MDX files, which .github/workflows/docs.yaml replaces with the release version before validating and publishing; never hardcode a Timoni release version in docs.
New pages must be added to the navigation in docs/docs.json. The site is published from the website branch, which .github/workflows/docs.yaml rebuilds from docs/ plus the generated cmd/ pages on release tags; never edit that branch by hand.
When you change behavior — flags, apply/prune/wait semantics, the action.timoni.sh/* annotations, the Runtime/Bundle schema, vendoring, signing — update the matching page(s) under docs/ in the same change, not as a follow-up. A Runtime or Bundle change almost always touches a docs/ root guide and a schema; a module-rendering change touches docs/cue/module/.
skills/ — the agent skill
skills/timoni/SKILL.md is a self-contained Timoni skill for AI agents, published at timoni.sh/skill.md and /.well-known/agent-skills/ (make docs-skills copies skills/ to the gitignored docs/.mintlify/skills/, which the docs workflow publishes). It must let an agent operate Timoni with no other documentation.
Update it when commands, flags or Bundle/Runtime semantics change, and bump metadata.version (semver, independent of Timoni releases): patch for wording fixes, minor for significant changes.
Evaluate skill changes by running a sub-agent against it:
make buildso the sub-agent has./bin/timonito drive.- Spawn a fresh sub-agent whose only documentation is
skills/timoni/SKILL.md. Forbid readingdocs/, the source code, and web/MCP docs tools; allowtimoni <cmd> --help, files the agent creates or pulls itself, and one example module (e.g.examples/redis) as the deployment target. - Give it a realistic end-to-end task that exercises the changed sections. A good baseline: bundle
examples/redistwice with different values, discover the config schema from the module's README andconfig.cue, run the fmt/vet/build editing loop, and inject a secret from an env var with--runtime-from-env. With a cluster, extend it through the full lifecycle:--diffpreview, apply, status, inspect, delete. - Cluster access is fine but must be confined to a unique
timoni-testnamespace, with full cleanup at the end (bundle delete, then namespace delete). Without registry access, reference modules withfile://paths. Working files go in a temp dir outside the repo. - Require a critical report as the deliverable: per-step completion with the exact commands used, a scorecard of which skill sections pulled their weight, gaps only
--helpfilled, statements that proved wrong or misleading, and ambiguities that cost a retry. - Fold confirmed findings back into
SKILL.mdand re-run. Testing with a weaker model than the author is a stronger signal that the skill carries the task on its own.
Other dirs
examples/— runnable modules (redis, etc.) used as docs and asmake cue-vet/make push-redistargets.blueprints/— module scaffolding templates.actions/— GitHub Action for using Timoni in CI.internal/dyff/— structured YAML diffing for the interactive flow.internal/mask/— secret redaction applied tobuild/bundle buildoutput.internal/flags/,internal/logger/,internal/fscopy/— shared cobra flag types, logger setup, and fs helpers used across commands.internal/testutils/— gomega matchers and an in-process OCI registry for tests.test/— Kubernnetes Kind e2e setup (seetest/README.md); run its targets only when the user explicitly asks.
Conventions
- Every Go source file starts with the Apache 2.0 license header (see
api/license.go.txt). - Instance, namespace, and runtime names are restricted to lowercase (RFC 1123).
- New code and tests should follow the patterns in existing siblings; commands almost always come with an envtest-backed
_test.gousing theinternal/testutilshelpers. - Code changes should be accompanied by a corresponding change to the docs.
- Add Go doc comments for new functions and types.
- After modifying a function or type, update its doc comment.
- Add in-line comments for complex logic but don't comment obvious code.
- Commits require a
Signed-off-bytrailer (DCO — usegit commit -s); subjects are short and imperative, typically with afeat:/fix:/docs:prefix.