The plugins
Some files mean more than their text: a CI job is its includes and its extends chain merged, a dependency’s
version may be set in a parent three files away. A plugin reads one such kind of file and answers for it through
Otōto’s own tools, outline and read, so that what comes back is the thing as its tool would evaluate it, each
line with the file and line it came from.
Nine come with Otōto. The installer puts them in ~/.config/ototo/plugins; each has its own version and is
published on its own, between Otōto’s releases.
| Plugin | Reads | Ask it |
|---|---|---|
gitlab-ci | GitLab CI pipelines | read .gitlab-ci.yml#deploy:prod |
compose | docker compose files | read compose.yaml#web |
maven | Maven POMs | read pom.xml#spring-core |
spring-config | Spring’s application* and bootstrap* files | read application.yml#spring.datasource.url |
terraform | Terraform and OpenTofu modules | read main.tf#aws_db_instance.main@prod |
kube | Kustomize overlays and Helm values | read overlays/prod/kustomization.yaml#Deployment/api |
codeowners | CODEOWNERS | read .github/CODEOWNERS#src/api/server.rs |
archive | JARs, WARs, wheels and other ZIPs; compiled Java classes | read lib/core.jar!org.demo.Shelf |
gitlab | GitLab itself: merge requests and pipelines | the forge tool |
ototo.dev/plugins has the same list as the download channel has it today, with each one’s version.
Managing them
ototo plugins
lists the ones installed: on or off, version, who signed each, the files it reads, and why one does not load.
| Command | Does |
|---|---|
ototo plugins available [tag] | what the channel offers (java, infrastructure, gitlab…), and how each stands to the one here |
ototo plugins add <plugin> | installs one from the channel |
ototo plugins update | brings the installed ones up to the channel’s; never back a version, and a plugin of your own is left alone |
ototo plugins remove <plugin> | deletes one |
ototo plugins help <plugin> | which of this repository’s files it reads, and its own notes |
ototo plugins grant <plugin> | lets a forge plugin reach the hosts it declares (revoke takes that back) |
Nothing is trusted for having been downloaded. The channel’s index must be signed by the release key built into
Otōto; a plugin’s file must have the checksum the index gives; and the plugin must then carry its own signature,
by a key in your ~/.config/ototo/allowed_signers. The channel is asked only when you run one of these commands.
How it works says what a plugin can and cannot reach; Writing a plugin builds one from an empty directory.
gitlab-ci
Pipelines outlined a line per job and template, with stages, include, variables and default. read .gitlab-ci.yml#deploy:prod (or read deploy:prod from anywhere, #deploy:prod.script for one key) returns the
job as GitLab runs it: local includes followed, the extends chain deep-merged, default and global variables
inherited unless inherit says not, anchors and !reference expanded, and every key marked with the file, lines
and job or template it came from. Project, remote, template and component includes are named, not followed.
compose
Compose files and their overlays outlined a line per service. read compose.yaml#web returns the service as
docker compose runs it: includes, the file and its override (or an overlay’s base under it) merged as compose
merges them, extends resolved, anchors expanded, and ${VAR} filled in from the project’s .env, with each
line’s source. Variables come from .env only: a plugin sees no environment.
maven
POMs outlined as coordinates, parent, modules, properties, dependency management, dependencies, build plugins and
profiles. read pom.xml#spring-core returns the dependency with its effective version and scope as Maven
resolves them: parents found by relativePath or by coordinates, properties merged and ${…} filled in, a version
not declared taken from dependency management in Maven’s order, imported BOMs included. A parent or BOM outside the
repository is named, and no version is made up. Also #effective, #properties, #parent, #modules.
With dependency_caches = true in Otōto’s settings, a parent or a BOM that is not in the repository is read
where Maven keeps what it has downloaded, ~/.m2/repository, and the dependency’s JAR is named when it is there.
From version 0.2 of the plugin, and an Otōto later than 2026.19.
spring-config
application* and bootstrap* .properties, .yml and .yaml, read with the files Spring loads beside them.
read application.yml#spring.datasource.url gives the value with no profile active and for each profile that
changes it, each with its file and line and what it overrides; #@prod gives every property as that profile sees
it. Placeholders are filled in; a key no file sets shows as coming from the environment, never guessed.
terraform
Modules read with their variables’ values as Terraform gives them: the default, the tfvars files in its order,
then an environment’s (@prod). read main.tf#aws_db_instance.main@prod gives the block as written, each
expression that uses a variable or a local marked with what it works out to, and where every value came from.
read var.region gives a variable’s declaration, value, every file that sets it and every use. No function is
called, sensitive variables are never shown, and state, workspaces and remote modules are out of sight.
kube
Kustomize and Helm, from the repository’s files alone: no kustomize, kubectl or helm runs. read overlays/prod/kustomization.yaml#Deployment/api builds the resource in kustomize’s order (resources, components,
generators, patches, namespace, prefixes, labels, replicas, images), each line with the file that set it and what
changed it. read values.yaml#image.tag gives a chart’s value across its values files and the template lines that
use it. Remote bases and generator plugins are named, not applied; templates are not rendered.
codeowners
Any CODEOWNERS, outlined as its rules. read .github/CODEOWNERS#src/api/server.rs answers who owns that
path: the owners, the rule that decides with its line, and the earlier rules it overrides; a GitLab file with
sections is answered section by section. #@backend lists the lines that name an owner. It says whether it read
the patterns as GitHub or as GitLab does, and when that cannot be told and the two differ.
archive
Inside a JAR, WAR, wheel or any other ZIP without unpacking it, and a compiled Java class as its declarations,
with no javap: outline lib/core.jar lists it, read lib/core.jar!org.demo.Shelf gives a class with its
generics, parameter names and constants, and !META-INF/MANIFEST.MF any other entry; a.war!WEB-INF/lib/b.jar!…
reaches inside. It is lent only the bytes of the files it opens, and writes and runs nothing. With
dependency_caches = true, it reads a dependency’s JAR where the build tool keeps it.
gitlab
A forge plugin: it answers the forge tool’s questions about the repository on gitlab.com, or a GitLab of your
own added in its settings. The checked-out branch’s open merge request and latest pipeline, a change by number, a
pipeline’s jobs by stage, a failed job’s log cut to what failed. It reaches nothing until you grant it:
ototo plugins grant gitlab
It may only GET, over https, from the hosts it declares, and it never sees a token: Otōto adds the one you set for
that host. Without a token it sees what is public; job logs need one. ototo plugins help gitlab has its settings.