This is the loop for changing the CLI itself. For the loop that carries a change to an application in a workspace into a running environment — render, build, deploy, and how to tell a stale build from a wrong one — see the development loop.
- Go 1.27.3+ (check with
go version;go.modis the source of truth — see runbooks/bump-go-version.md) - Docker (for container builds, agent processes, and infrastructure)
- codefly binary (the CLI itself, for bootstrapping)
CI selects its exact Go toolchain with go-version-file: go.mod, including the
bootstrap install action used by audit and control integration. Raising the root
go directive therefore raises those CI compiler pins together; GOTOOLCHAIN=local
then uses the compiler already installed by setup-go. The deployment-contract
workflow reads contracts/deployment/go.mod separately, so update that directive
with the root when adopting a security patch.
The Docker-backed quality fixtures use explicit mirror.gcr.io/library/ image
references: Postgres 16 Alpine for ordered teardown, Go 1.27 Alpine for the
credential-free module build, Alpine 3.22 for generate recovery and bind-mount
proofs, Alpine 3.21 for the gateway container transport, and Registry 2 for
authenticated OCI acquisition. CI pre-pulls the same
references as their tests. Hosted runners share Docker Hub's anonymous pull
quota; exhausting it otherwise fails a prerequisite and cancels the other
quality jobs through matrix fail-fast. Explicit references prevent Docker's
daemon mirror fallback from returning to that exhausted quota. Image versions,
assertions and all CI and conformance requirements remain unchanged; a failed
pull still fails the gate.
cli/
├── cmd/ # Cobra command definitions
│ ├── root.go # Root command + global flags
│ ├── run.go # run → run/service.go, run/job.go
│ ├── build.go # build → build/service.go
│ ├── test.go # test → test/service.go
│ ├── deploy.go # deploy → deploy/service.go, deploy/init.go
│ ├── add.go # add → add/module.go, add/service.go, ...
│ ├── delete.go # delete → delete/module.go, delete/service.go
│ ├── update.go # update → update/service.go, update/workspace.go
│ ├── sync.go # sync → sync/service.go, sync/library_dependencies.go
│ ├── list.go # list → list/project.go, list/application.go, ...
│ ├── initialize.go # init → initialize/workspace.go
│ ├── install.go # install → install/library.go
│ ├── agent.go # agent → agents/info.go, agents/build.go, agents/generate.go
│ ├── generate.go # generate → generate/grpc.go, generate/proto.go, generate/swagger.go
│ ├── daemon.go # daemon start/stop/restart/status/logs/monitor/gateway
│ ├── mcp.go # mcp serve/tools
│ ├── server.go # server (web companion)
│ ├── expose.go # expose → expose/service.go
│ ├── ci.go # ci → ci/test.go, ci/build.go, ci/deploy.go, ci/push.go
│ ├── login.go # login
│ ├── version.go # version
│ ├── clear.go # clear
│ ├── replay.go # replay
│ ├── import.go # import
│ └── common/ # Shared utilities (context, workspace loading, logo)
│
├── pkg/ # Core packages
│ ├── orchestration/ # Flow, Playbook, Runner, Builder, Policies, StateManager
│ ├── daemon/ # Background process management + monitor
│ ├── mcp/ # MCP server, tools, resources, protocol types
│ ├── gateway/ # Mind Gateway gRPC server
│ ├── builder/ # Docker build context utilities
│ ├── cli/ # CLI utilities (logging, errors, TUI communication)
│ ├── deployments/ # Deployment manager interface
│ ├── generators/ # Code generation utilities
│ ├── imports/ # Application import logic
│ ├── observability/ # OTEL integration
│ ├── platform/ # Platform login/token management
│ ├── types/ # Shared types
│ └── web/ # Web companion server
│
├── main.go # Entry point
├── go.mod # Module: github.com/codefly-dev/cli
├── go.work # Workspace: cli + core + wool + wool/otel
├── Makefile # Build targets
├── scripts/ # Build and publish scripts
├── docs/ # Documentation (you are here)
└── test/ # Test fixtures and data
The CLI depends on core and wool packages. For local development, use Go workspaces:
mkdir -p ~/Development/codefly.dev
cd ~/Development/codefly.dev
git clone https://github.com/codefly-dev/cli.git
git clone https://github.com/codefly-dev/core.git
git clone https://github.com/codefly-dev/wool.gitThe go.work file links local packages:
go 1.25
use (
.
../core
../wool
../wool/otel
)
This means changes to core or wool are immediately reflected in the CLI without publishing.
cd cli
codefly self buildcodefly self buildgo install produces an operator-managed development build. It does not carry
the authenticated release metadata and codefly self update will not replace
it. It needs read access to codefly-dev/authority, a private repository whose
libraries the CLI requires, so it is not supported from outside the codefly-dev
organization; releases are the install path there:
GOPRIVATE=github.com/codefly-dev/authority \
go install github.com/codefly-dev/cli/cmd/codefly@<version>For a self-updating direct installation, use a signed release archive as described in CLI releases and updates.
CI downloads these private libraries with the existing organization GH_PAT
secret, also used by companion checkout and release publication. The token is
passed only to the module download/bootstrap step; the download helper scopes
Git authentication to codefly-dev/authority without persisting Git settings.
Build, lint and test steps consume the populated module cache. Missing access
fails the download; no gate is skipped. The CI reader-app action remains
available to callers that configure its app credentials, but CLI workflows do
not depend on unconfigured reader-app secrets.
go test ./...make check-coverageFor a top-level command, create cmd/mycommand.go:
package cmd
import "github.com/spf13/cobra"
var MyCommandCmd = &cobra.Command{
Use: "mycommand",
Short: "Description of mycommand",
Run: func(cmd *cobra.Command, args []string) {
// Implementation
},
}For a subcommand (e.g., codefly run job), create cmd/run/job.go:
package run
import "github.com/spf13/cobra"
var JobCmd = &cobra.Command{
Use: "job [name]",
Short: "Run a job",
Run: func(cmd *cobra.Command, args []string) {
// Implementation
},
}In cmd/root.go (for top-level) or the parent command file:
func init() {
RootCmd.AddCommand(MyCommandCmd)
}Or for subcommands, in the parent (e.g., cmd/run.go):
func init() {
RunCmd.AddCommand(run.JobCmd)
}Loading workspace context:
ctx, done := common.NewContext()
defer done()
workspace, module, service := common.LoadRequired(ctx, args)Signal handling:
ctx, stop := signal.NotifyContext(ctx, os.Interrupt, os.Kill)
defer stop()Agent cleanup:
cli.RegisterCleanup(services.ClearAgents)Error handling:
cli.ExitOnError(err, "Cannot do the thing")The heart of the CLI. Start with:
action.go-- Action types and the ActionManager channel systemflow.go-- Flow creation and lifecycleplaybook.go-- The execution looprunner.go-- Service runtime lifecycle (Load/Init/Start/Test/Stop)builder.go-- Build lifecycle (Load/Init/Build/Deploy)runtime_start_policy.go-- State machine transitions forrun
Two files:
daemon.go-- Process start/stop/status, PID file managementmonitor.go-- Process health monitoring, CPU/memory thresholds
protocol.go-- JSON-RPC 2.0 and MCP type definitionsserver.go-- MCP server with stdio transporttools.go-- Workspace-level tool registrationsservice_tools.go-- Per-service tools (read/write/build/test)symbol_tools.go-- LSP-backed symbol intelligence toolsresources.go-- Resource registrations (workspace/module/service configs)
Mind Gateway gRPC server for the Mind engineering platform.
The CLI depends on two sibling packages:
- Resource model:
resources.Workspace,resources.Module,resources.Service - Architecture:
architecture.ServiceDependencies(DAG) - Agent lifecycle:
services.Instance,services.Runtime,services.Builder - Network management:
network.RuntimeManager,network.RemoteManager - Configuration management:
configurations.Manager - gRPC proto definitions:
codefly/base/v0,codefly/services/runtime/v0, etc.
Structured logging library with:
- Scoped loggers (
wool.Get(ctx).In("scope")) - Typed fields (
wool.Field("key", value)) - Error wrapping (
w.Wrapf(err, "context")) - Log levels: TRACE, DEBUG, INFO, WARN, ERROR, FOCUS
go test ./pkg/orchestration/...
go test ./pkg/mcp/...
go test ./pkg/daemon/...The MCP server has integration tests that use ServeIO() with in-memory readers/writers:
go test ./pkg/mcp/... -vPlaybook sync and policy tests:
go test ./pkg/orchestration/... -vls scripts/build/ # Build scripts
ls scripts/publish/ # Publish/release scriptsmake check-coverage # Run tests with coverage report- Cobra for all CLI commands
- wool for all logging (never
fmt.Printforlog.Printfin packages) - gRPC for all agent communication (never shell exec to agents)
- Context propagation through all functions
- Signal handling in all long-running commands
- Error wrapping with
w.Wrapf(err, "context message")
The CLI's existing JSON file logger allocates one exclusive invocation directory
beneath the resolved Codefly home logs directory. Nested command contexts reuse
that sink and register it with Core's agent log forwarding only once. Context
completion does not close it: preparation, delegated service execution and final
shutdown belong to the same invocation. The process owns its final descriptor
lifetime. Explicitly closing a standalone logger is terminal and idempotent.
Date/size rotation exclusively creates segments inside the original invocation directory. Log discovery selects the latest-started invocation, then its latest segment; delayed writes or rotations in an older process cannot change that selection. Legacy root-level daily files remain a fallback and explicit paths remain readable. A follower keeps its opened descriptor across rotation and later runs; it does not automatically switch to a different file.
This isolates the detailed JSON files of separate CLI processes. It does not change caller-owned stdout/stderr capture, daemon/service logs, or establish per-flow isolation inside a long-lived control process whose agent forwarding is still global. Tests cover separate subprocesses, late writes and forced rotation, repeated contexts through Core's actual log fanout, legacy discovery and terminal close behavior.
The composition adapter sends a terminal signed withdrawal without an authority
sidecar. It does not read the removed module's method policies, ceilings or
solution roles: withdrawal must work after those source files disappear, and
live declarations contradict the tombstone. Ordinary delivery still signs and
pairs those policies with its carrier. Transport regressions cover both module
and solution withdrawals through the generated composition client. Successful ApplyManifest responses must acknowledge the sent generation.
The adapter retains the acknowledged request's digest, release and removal flag
for the run's connection to the host. Native withdrawal completion uses the
Applied callback and requires that exact identity; it does not contact a
private HTTP route or mint an observer credential for an interface host. A
host without the interface retains the legacy HTTP observer until that path
is deleted. This is synchronous response evidence, not a remote-state query
after a host restart. Live launcher
shutdown/restart remains unqualified by these transport tests.