Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ jobs:
- name: Run tests
run: make test

- name: Test release installer
run: sudo env INSTALL_DIR=/usr/local/bin sh scripts/install-test.sh

lint:
runs-on: ubuntu-latest
steps:
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

All notable changes to the FlatRun CLI are documented in this file.

## [0.4.0-beta.4] - 2026-08-20
## [0.3.1] - 2026-08-21

### Added

- Push a local directory to a deployment in one request, with optional destination cleanup
- File operations from the agent are available through the generated command catalogue
- Update the installed CLI in place after verifying its published SHA-256 checksum
- Install verified Linux and macOS releases with one shell command

## [0.3.0] - 2026-08-11

Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help deps build test fmt fmt-check vet lint lint-install test-coverage clean qa
.PHONY: help deps build test test-installer fmt fmt-check vet lint lint-install test-coverage clean qa

BINARY_NAME=flatrun
VERSION?=$(shell cat VERSION 2>/dev/null || echo "dev")
Expand All @@ -12,6 +12,7 @@ help:
@echo "make deps - Download dependencies"
@echo "make build - Build CLI binary"
@echo "make test - Run unit tests"
@echo "make test-installer - Install and run a pinned published release"
@echo "make test-coverage - Run tests with coverage report"
@echo "make fmt - Format code with gofmt"
@echo "make fmt-check - Check gofmt formatting"
Expand All @@ -30,6 +31,9 @@ build:
test:
go test ./...

test-installer:
sh scripts/install-test.sh

test-coverage:
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
Expand Down
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,28 @@

`flatrun` is the command-line interface for FlatRun. It is intended to be the automation and operator surface for FlatRun in the same way cloud CLIs wrap a larger platform API.

## Install from source
## Install

Install the latest release on Linux or macOS:

```bash
curl -fsSL https://raw.githubusercontent.com/flatrun/cli/main/scripts/install.sh | sudo sh
```

The installer detects amd64 or arm64, verifies the published SHA-256 checksum, and installs
`flatrun` in `/usr/local/bin`. Pin a release with `FLATRUN_VERSION`, or choose another destination
with `INSTALL_DIR`.

Update an installed release in place:

```bash
flatrun update
```

Use `sudo flatrun update` when the binary is installed in a system directory such as
`/usr/local/bin`. Check for a newer version without installing it with `flatrun update --check`.

## Build from source

```bash
make build
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.4.0-beta.4
0.3.1
36 changes: 36 additions & 0 deletions internal/command/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/flatrun/cli/internal/config"
"github.com/flatrun/cli/internal/flatrun"
cliupdate "github.com/flatrun/cli/internal/update"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -194,6 +195,8 @@ func Run(args []string, stdout, stderr io.Writer) int {
case "version", "--version":
_, _ = fmt.Fprintf(stdout, "%s\nbuild_time=%s\ngit_commit=%s\n", Version, BuildTime, GitCommit)
return 0
case "update":
return runUpdate(args[1:], stdout, stderr)
case "configure":
return runConfigure(args[1:], stdout, stderr)
case "health":
Expand Down Expand Up @@ -245,10 +248,43 @@ func usage(w io.Writer) {
_, _ = fmt.Fprintln(w, " health Check that the agent is reachable")
_, _ = fmt.Fprintln(w, " api Call any endpoint directly")
_, _ = fmt.Fprintln(w, " version Print CLI version")
_, _ = fmt.Fprintln(w, " update Update the CLI to the latest release")
_, _ = fmt.Fprintln(w)
_, _ = fmt.Fprintln(w, "Add --json to any command for the raw answer, or to a listing for every command.")
}

func runUpdate(args []string, stdout, stderr io.Writer) int {
fs := flag.NewFlagSet("update", flag.ContinueOnError)
fs.SetOutput(stderr)
check := fs.Bool("check", false, "Check for an update without installing it")
if code, ok := parseFlagSet(fs, args); !ok {
return code
}
if fs.NArg() != 0 {
_, _ = fmt.Fprintln(stderr, "Usage: flatrun update [--check]")
return 2
}
result, err := cliupdate.Run(context.Background(), Version, *check)
if err != nil {
_, _ = fmt.Fprintf(stderr, "Update failed: %v\n", err)
return 1
}
if result.Latest == result.Current {
_, _ = fmt.Fprintf(stdout, "FlatRun CLI %s is up to date.\n", result.Current)
return 0
}
if *check {
_, _ = fmt.Fprintf(stdout, "FlatRun CLI %s is available. Current version: %s.\n", result.Latest, result.Current)
return 0
}
if result.Updated {
_, _ = fmt.Fprintf(stdout, "Updated FlatRun CLI from %s to %s.\n", result.Current, result.Latest)
return 0
}
_, _ = fmt.Fprintf(stdout, "FlatRun CLI %s is up to date.\n", result.Current)
return 0
}

func wrapNames(names []string, width int) []string {
var lines []string
current := ""
Expand Down
28 changes: 28 additions & 0 deletions internal/command/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,34 @@ func TestVersionPrintsBuildMetadata(t *testing.T) {
}
}

func TestUpdateRejectsDevelopmentBuild(t *testing.T) {
oldVersion := Version
Version = "dev"
t.Cleanup(func() { Version = oldVersion })

var stdout bytes.Buffer
var stderr bytes.Buffer
code := Run([]string{"update"}, &stdout, &stderr)
if code != 1 {
t.Fatalf("code=%d stderr=%s", code, stderr.String())
}
if !strings.Contains(stderr.String(), "development builds cannot be updated automatically") {
t.Fatalf("unexpected stderr: %s", stderr.String())
}
}

func TestUpdateRejectsArguments(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
code := Run([]string{"update", "unexpected"}, &stdout, &stderr)
if code != 2 {
t.Fatalf("code=%d stderr=%s", code, stderr.String())
}
if !strings.Contains(stderr.String(), "Usage: flatrun update [--check]") {
t.Fatalf("unexpected stderr: %s", stderr.String())
}
}

func TestPrintResponsePrintsTopLevelArrayWhenNoFallback(t *testing.T) {
var stdout bytes.Buffer

Expand Down
Loading
Loading