gomo is a Rust CLI for running Gleam package workflows across a monorepo. It
discovers packages from configured project roots such as
apps/*, libs/*, services/*, and tools/*, builds a local dependency graph
from Gleam path dependencies, runs tasks in dependency order, and caches
successful build and test tasks.
Create a full-stack Gleam monorepo in a new directory, or initialize the current directory:
gomo init my_app
gomo init .The generated starter contains a JavaScript Lustre frontend under apps/web, a
shared API contract package under libs/shared, and a Wisp/Mist service under
services/api. The frontend uses the official lustre_dev_tools development
server and proxies /api requests to the service. The scaffold also includes an
Ubuntu GitHub Actions workflow, focused tests, and a README with development and
production commands. Package lock manifests are included so CI can run
gomo deps check. The scaffold does not generate Nix files, a task runner, or
version control metadata.
init refuses to overwrite managed files or merge into existing generated
package directories. Other files already present in the target directory are
left unchanged.
From anywhere inside a configured workspace:
gomo build
gomo test
gomo format
gomo format --checkNamed workflow tasks compose native Gomo operations, project-backed modules, structured external commands, shell pipelines, and other tasks:
gomo task list
gomo task list --project web_app
gomo task run release
gomo task run vite-build --project web_app
gomo task run-many lint --allWorkspace tasks are declared under [tasks.<name>] in gomo.toml. Reusable
project tasks set scope = "project" and are exposed explicitly by a package:
# gomo.toml
[tasks.vite-build]
scope = "project"
description = "Build the Vite-backed application"
cache = true
steps = [
{ shell = { command = "vite build", cwd = "{project_root}" } },
]
[tasks.release]
depends_on = ["validate"]
steps = [
{ task = { name = "vite-build", project = "web_app" } },
{ exec = { program = "docker", args = ["compose", "build"] } },
]
# apps/web_app/gleam.toml
[tools.gomo.tasks.vite-build]
extends = "vite-build"
[tools.gomo.build]
task = "vite-build"Task definitions support sequential or parallel steps, explicit prerequisites,
workspace/project scope, descriptions, persistent workflows, declared inputs
and outputs, and opt-in cache metadata. Each step has exactly one task,
target, dev, watch, module, exec, or shell action. Direct project
task invocation always requires --project; the current directory never
selects a project implicitly.
Watch finite work across a project and its transitive local dependencies:
gomo watch --target build --project my_app --with-deps
gomo watch --project my_app --with-deps -- ./scripts/regenerate.shWatch performs an initial run, debounces filesystem events, serializes runs,
and keeps waiting after a failed task or callback. Generated build/, target/,
node_modules/, .gomo/, .gomo-restore-*, .gomo-backups-*, .git/, .jj/,
.devenv/, and .direnv/ output is ignored. Callback commands run from the
selected project root and receive GOMO_CHANGED_FILES, GOMO_CHANGED_PROJECTS,
GOMO_WATCH_PROJECT, and JSON companion variables ending in _JSON.
Use --no-initial-run to skip the initial callback or target run.
Manage a long-running development process with build-before-restart semantics:
gomo dev --project my_app
gomo dev --project my_app -- gleam run -m my_appgomo dev builds the project and its local dependencies before starting the
process, runs it from the project root, and restarts it only after a successful
rebuild. A failed rebuild leaves the current process running. Filesystem
events and debounce timing are provided by Watchexec, and its supervisor owns
the development process and its descendants. Development commands may be
configured in gleam.toml:
[tools.gomo.watch]
command = "./scripts/regenerate.sh"
[tools.gomo.dev]
command = "gleam run"
reload = "restart"The hot reload value is accepted as a forward-compatible strategy and falls
back to a managed restart until a runtime helper is connected. Watchexec runs
development commands in their own process group so restart and Ctrl-C clean up
shell descendants as well.
For workspace inspection and troubleshooting:
gomo doctor
gomo deps check
gomo deps vendor
gomo projects
gomo graphLocal build and test task runs use a full-screen Ratatui interface when stdout
is an interactive terminal. It shows per-project status, selected task logs,
current parallel work, progress, and cache counts. Use ↑/↓ or j/k to
select tasks and L to view logs fullscreen without side borders for easier
copying. If the finished TUI auto-exits, Gomo prints the captured task logs and
summary in a static format. Gomo automatically uses plain static output when
run by a recognized coding agent, terminal I/O is captured, TERM=dumb,
NO_COLOR is set, or CI is set. Use --ci to force static logs or --json
for machine-readable summaries.
Run one project, or include its upstream local dependencies:
gomo run --target build --project web_app
gomo run --target test --project web_app --with-depsRun affected validation from an explicit changed-file list, or from VCS changes against a base ref:
gomo affected --target test --files libs/shared/src/widget.gleam
gomo affected --target test --base mainaffected --base uses Jujutsu when .jj exists at the workspace root, otherwise
Git when .git exists. Use --files to bypass VCS discovery.
Workspace-level target inputs can make root files affect every project for a target. They are matched relative to the workspace root and also participate in task cache keys:
[workspace.test]
inputs = ["gomo.toml", "devenv.nix", ".github/workflows/**"]Workspace discovery and default task concurrency are configured in gomo.toml:
[workspace]
project_roots = ["apps/*", "libs/*", "services/*", "tools/*"]
default_parallelism = "auto"
[dependency_versions]
enabled = true
include_local = true
ignore = []
# Optional strict vendoring
[vendoring]
dir = "vendor"project_roots supports exact paths and direct-child globs like apps/*.
Unknown config fields are rejected so typos do not silently change behavior.
dependency_versions is optional. When present, enabled defaults to true,
include_local defaults to true, and ignore defaults to an empty list.
[vendoring] is optional; omit it unless the workspace wants locked external
packages checked into a vendor store.
Project-level target config lives in each package's gleam.toml under
[tools.gomo.<target>]. inputs override the files used for cache keys and
affected-file matching. command overrides the command Gomo runs for that
target. Build targets can use cached_folders to replace the default
["build"] output list with exact project-relative directories:
[tools.gomo.test]
inputs = ["gleam.toml", "src/**", "test/**", "fixtures/**"]
command = "gleam test --target erlang"
[tools.gomo.build]
task = "vite-build"
[tools.gomo.format]
command = "mise exec -- gleam format"
[tools.gomo.format.check]
command = "mise exec -- gleam format --check"Custom commands run through sh, so shell syntax such as &&, pipes,
redirects, quoting, and environment variable expansion is supported.
Default commands are gleam build, gleam test, gleam format, and
gleam format --check. Custom format commands must be configured as a pair:
if [tools.gomo.format].command is set, [tools.gomo.format.check].command
must also be set, and vice versa.
gomo deps check validates resolved dependency versions from each project's
manifest.toml. It intentionally checks the lock manifest instead of comparing
version ranges in gleam.toml, because the resolved version is the version that
was actually built and tested.
gomo deps check
gomo deps check --jsonFor Hex packages, the same dependency name must resolve to one version across
all checked manifests. Git packages must resolve to the same version, repository
URL, and commit. For local packages, Gomo also verifies that the locked local
version matches the referenced local package's gleam.toml version. When
[vendoring] is configured, the same command also validates the vendor store
(see Dependency Vendoring).
Automatic doctor enforcement is controlled from root gomo.toml:
[dependency_versions]
enabled = true
include_local = true
ignore = ["some_intentional_exception"]If the table is absent, gomo deps check still works explicitly, but
gomo doctor skips dependency version policy checks. Set enabled = false to
keep the table's include_local or ignore settings for explicit checks while
leaving doctor unchanged.
Add an optional root [vendoring] table to enable strict dependency vendoring.
dir defaults to vendor:
[vendoring]
dir = "vendor"gomo deps vendor synchronizes exact Hex and Git packages from checked-in
manifest.toml locks into gomo-vendor.toml, hex, and git under the
configured vendor directory. The directory is vendor when dir is omitted.
Set HEXPM_READ_API_KEY when private Hex packages need a read token.
gomo deps vendor
gomo deps checkWith vendoring enabled, gomo deps check validates resolved versions plus
vendor inventory, archive checksums, and project snapshots. gomo doctor
reports vendoring issues when the table is present.
Build, test, dev, watch, tasks bound to build/test targets, and structured
module/target steps launched through Gomo fail closed when vendor state is
missing or stale. Raw gleam invocations and standalone arbitrary shell/exec
task steps are outside that guarantee.
gomo clean removes project build output but not vendor files. The next Gomo
command that prepares projects hydrates Hex archives into Gleam's checksum cache
and restores Git package freshness state under each project's build/packages.
Successful build and test tasks are cached by default. Build cache hits
restore the project's configured cached folders, which default to build/;
test cache hits replay the successful test output. Failed test runs are not
cached. Every configured cached folder must be created by a successful build.
Cache restore completely replaces each folder so stale output files cannot
survive a cache hit. Cached folders must be non-overlapping, project-relative
directories without . or .., and symlinks are not supported within them.
Gleam embeds absolute source paths in build/ artefacts, so build cache
entries record the producing workspace root and rewrite those paths on restore
when the checkout lives at a different absolute location (for example unique
CI workdirs).
Useful cache controls:
gomo --cache-mode read-write test
gomo --cache-mode read test
gomo --no-remote-cache build
gomo --remote-cache-read-only test
gomo --require-remote-cache test
gomo --no-cache build
gomo --no-restore test
gomo explain --target test --project web_app
gomo reset --only-cacheoff, read, write, and read-write cache modes are also available through
GOMO_CACHE_MODE. The local cache is always checked before a configured remote.
reset --only-cache removes the configured local cache directory.
Small projects can snapshot .gomo/cache with the supported
actions/cache/restore@v5 and actions/cache/save@v5 actions. See
examples/github-actions-cache.yml. The
save key is unique per job execution while the restore prefix is stable.
Matrix dimensions that alter inputs or project selection must be included in
both keys. Pull requests should restore without saving a trusted branch key.
This snapshots the entire local cache, is branch-scoped and evictable by GitHub, and does not merge snapshots from parallel jobs. It is not the multi-user remote cache protocol and Gomo does not call GitHub's undocumented cache-service upload APIs.
Configure a service without putting credentials in gomo.toml:
[cache.remote]
backend = "http"
url = "https://cache.example.internal"
workspace = "wooli"
mode = "auto"
failure = "warn"
max_concurrent_transfers = 4Set GOMO_REMOTE_CACHE_TOKEN at runtime for static-token authentication.
GitHub Actions jobs may instead grant id-token: write; when
ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN are
available, Gomo requests and exchanges a GitHub OIDC JWT automatically. Set
GOMO_REMOTE_CACHE_OIDC_AUDIENCE when the trust rule's audience differs from
the service URL. No long-lived cache secret is needed in that mode.
GOMO_REMOTE_CACHE_URL,
GOMO_REMOTE_CACHE_WORKSPACE, GOMO_REMOTE_CACHE_MODE, and
GOMO_REMOTE_CACHE_RUN_ID can override CI-specific settings. Tokens, S3
credentials, and cached secret files must never be committed. Use a read-only
identity for developers and untrusted CI; only protected CI should receive
cache:shared:write. Release and deployment jobs that require fresh provenance
should use --no-remote-cache and must not treat a cache hit as a deployable
artifact attestation.
HTTPS uses the platform certificate store. Install an internal cache service's root CA in the operating system trust store instead of disabling certificate validation.
The server is a separate binary. Linux x86_64 GitHub releases publish both
gomo and gomo-cache-server archives:
gomo-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz
gomo-cache-server-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz
gomo-cache-server migrate
gomo-cache-server doctor
gomo-cache-server serveIt uses a local SQLite database as the first-writer-wins publication authority
and an S3-compatible bucket only for immutable bundle bytes. The cache server is
single-process by design; SQLite uses exclusive locking to prevent a second
server or administration command from opening the database concurrently. The
serving process applies embedded migrations at startup and runs retention
garbage collection itself.
Garage is supported with region garage, a custom endpoint, and path-style
addressing. Clients never receive bucket credentials.
Non-interactive commands automatically avoid rich terminal rendering. Use
--ci to force plain output even in a terminal and --json for
machine-readable summaries:
gomo --ci doctor
gomo --json projects
gomo --json run-many --target test --all
gomo --json affected --target build --base main--json implies CI-friendly rendering for commands that would otherwise use
terminal UI. Task-running JSON output reports the selected target, totals,
cache hit/miss/bypass counts, and each task status.
Start with:
gomo doctorCommon fixes:
- Missing workspace: run from inside a repo containing root
gomo.toml. - Unknown project: check
gomo projectsfor discovered package names. - Invalid graph: check local path dependencies in each package's
gleam.toml. - Stale or missing vendor store: run
gomo deps check, thengomo deps vendor. - Cache confusion: run
gomo explain --target <build|test> --project <name>to inspect cache inputs, orgomo reset --only-cacheto remove local entries.
MIT. See LICENSE.