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
28 changes: 28 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Summary

<!-- What does this PR do? Link any related issues with "Closes #N" -->

## Type of change

- [ ] Bug fix (`fix:`)
- [ ] New feature (`feat:`)
- [ ] Breaking change (`feat!:` or `fix!:`)
- [ ] Docs / chore

## Product trees affected

<!-- Shared command changes must be applied to all three trees (see AGENTS.md) -->

- [ ] AWS (root tree)
- [ ] GCP (`commands/gcp/`)
- [ ] Azure (`commands/az/`)
- [ ] N/A (shared infrastructure, docs, CI)

## Checklist

- [ ] `mvn test` passes locally
- [ ] New or updated tests added (or explained below why not needed)
- [ ] `CHANGELOG.md` entry added under `[Unreleased]` (user-visible changes)
- [ ] `README.md` updated (if a command or flag changed)
- [ ] Native binary verified (`mvn package -Pnative`) — required if Jackson serialization or dependencies changed
- [ ] Commit messages / PR title follow [Conventional Commits](https://www.conventionalcommits.org/)
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ jobs:
iwr https://floci.io/install.ps1 | iex
```

**JVM fallback** (requires Java 21+): `floci.jar`
**JVM fallback** (requires Java 25+): `floci.jar`

See [CHANGELOG.md](CHANGELOG.md) for what's new.
files: |
Expand Down
143 changes: 143 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# AGENTS.md

Guidance for AI coding agents working in the `floci-cli` repository.

This file defines repository-specific operating rules for autonomous or semi-autonomous coding agents. Follow these instructions unless a maintainer explicitly tells you otherwise.

---

## Project Overview

`floci-cli` is the official command-line interface for [Floci](https://floci.io) — the free, open-source local cloud emulator for AWS, GCP, and Azure. It manages emulator lifecycle (start/stop/status/logs), configuration profiles, diagnostics (`doctor`), and state snapshots.

- Stack: Java 25, Picocli 4.7.x, Jackson (JSON + YAML), JUnit 5
- Distribution: GraalVM native binaries (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64) + fat-JAR fallback
- No frameworks beyond Picocli — plain `java.net.http.HttpClient` and `docker` CLI subprocesses

---

## Build and Test Commands

```sh
mvn compile # compile only
mvn test # run all unit tests
mvn test -Dtest=WaitCommandParseTest # run a single test class
mvn test -Dtest=DockerVersionCheckTest#testVersionParsing # run a single test method
mvn package # compile + test + produce target/floci.jar (fat JAR)
mvn package -DskipTests # skip tests
mvn package -Pnative -DskipTests # build native binary → target/floci (requires GraalVM)
```

Running the fat JAR (requires Java 25+):

```sh
java -jar target/floci.jar <command>
```

---

## Architecture

### Entry point and command wiring

`FlociCli` is the Picocli root command. Every subcommand is registered in its `subcommands = {}` array. All commands implement `Callable<Integer>` and return an exit code.

Bare commands (`floci start`) route to the configured default product (`aws` unless changed via `floci config default-product aws|gcp|az`). Explicit product groups are `floci aws` (same classes as the root tree), `floci gcp`, and `floci az`.

### The three product trees — CRITICAL

The GCP (`commands/gcp/`) and Azure (`commands/az/`) command trees are near-verbatim copies of the root/AWS tree (`commands/`), each with its own `GlobalOptions` variant:

| Tree | Mixin | Default endpoint | Container | Env prefix | Control prefix |
|------|-------|------------------|-----------|------------|----------------|
| root / `aws` | `GlobalOptions` | `http://localhost:4566` | `floci` | `FLOCI_*` | `/_floci` |
| `gcp` | `GcpGlobalOptions` | `http://localhost:4588` | `floci-gcp` | `FLOCI_GCP_*` | `/_floci-gcp` |
| `az` | `AzGlobalOptions` | `http://localhost:4577` | `floci-az` | `FLOCI_AZ_*` | `/_floci` (same as AWS — intentional) |

**Until the trees are unified: any change to a shared command MUST be applied to all three trees** (e.g. `StartCommand`, `GcpStartCommand`, `AzStartCommand`). Drift between the copies is a known bug source — check the sibling trees before and after editing. Only `EnvCommand`/`GcpEnvCommand`/`AzEnvCommand` are genuinely product-specific.

Commands call `global.printer()` at the start of `call()` — never store a `Printer` as a field.

### Two I/O boundaries

**Docker:** `DockerClient` wraps `docker` CLI subprocesses — no docker-java library. This is a deliberate native-image trade-off. `DockerClient` returns plain records (`ContainerInfo`, `ImageInfo`) and throws `DockerException` on non-zero exit codes.

**Floci server:** `FlociHttpClient` wraps `java.net.http.HttpClient` against the Floci REST API. All methods throw `FlociException`. The control-plane prefix is constructor-configurable: AWS and Azure use `/_floci`, GCP uses `/_floci-gcp`. Known live endpoints: `/_floci/health`, `/_floci/info`, `/_floci/init`. Snapshot endpoints (`/_floci/snapshots/*`) do not exist on the server yet.

### Output pipeline

Every command checks `printer.format()` to decide between text and structured output:

```java
if (printer.format() != OutputFormat.text) {
printer.structured(map); // renders as JSON or YAML via Jackson
return 0;
}
// ... build human-readable text with Ansi helpers
```

`Ansi` is a static utility with a global `disable()` call (triggered by `--no-color` or non-TTY). Color is disabled once per JVM invocation; it cannot be re-enabled.

### Doctor checks

`Check` is a `@FunctionalInterface` taking `(endpoint, container)` and returning `CheckResult`. Order matters: Docker checks run before server checks because later checks depend on Docker being up.

The structure differs per tree (do not assume they match):

- **AWS** (`commands/DoctorCommand.java`): `DOCKER_CHECKS` (static list of the 9 Docker/server checks) + `AWS_COMPANION_CHECKS` (AWS CLI checks), combined via constructor into an instance `allChecks` list.
- **GCP/Azure** (`GcpDoctorCommand`/`AzDoctorCommand`): a single static `ALL_CHECKS` list built in a static initializer.

To add a check: create a class in `doctor/checks/`, then append it to the relevant list(s) — in all trees where it applies.

### Config profiles

`ProfileStore` reads/writes YAML files under `~/.floci/profiles/<name>.yaml` via Jackson. `Profile` is a plain bean — keep it `@JsonIgnoreProperties(ignoreUnknown = true)` to stay forward-compatible. The store is shared by all three product trees (no per-product namespacing). `GlobalConfigStore` persists `~/.floci/config.yaml` (currently just `default-product`).

### Self-update

`UpdateCommand` (`floci update`) downloads the release binary for the current platform, verifies its sha256 against the release's `sha256sums.txt`, and atomically replaces the running binary (staged in the same directory, `ATOMIC_MOVE`). It refuses to update Homebrew-managed installs. `--check` exits 0 when up to date, 1 when an update is available. `ReleaseChannel` builds the GitHub release asset URLs.

---

## Code Style and Good Practices

- **Prefer `record` for data carriers.** Any DTO, value object, or result type that just holds data should be a `record` (like `ContainerInfo`, `ImageInfo`, `CheckResult`, `HealthInfo`) — not a mutable bean with getters/setters. Exception: Jackson-mapped config beans that must stay forward-compatible and field-name-bound for the native image (`Profile`, `GlobalConfig`) may stay as plain beans; if you do make a Jackson-(de)serialized type a record, it still needs a `reflect-config.json` entry.
- **Prefer constructor injection over field mutation.** Collaborators (`DockerClient`, `FlociHttpClient`, `ProfileStore`, check lists) should be passed through the constructor and stored in `final` fields — see `DoctorCommand(List<Check> companionChecks)` for the pattern. Provide a no-arg constructor with defaults for Picocli, and a parameterized one as the test seam. Exception: Picocli requires non-final fields for `@Option`, `@Parameters`, and `@Mixin` — that is framework-mandated and fine.
- **Import classes; don't fully-qualify inline.** Add an `import` and use the simple name instead of writing `io.floci.cli.output.Printer` (or `java.util.List`, etc.) inline in signatures and bodies. Avoid wildcard imports except the existing `picocli.CommandLine.*` convention.
- Keep fields `final` wherever possible; prefer immutable collections (`List.of`, `List.copyOf`) over mutable ones exposed outside a method.

---

## Native Image Constraints

- **No new reflection-heavy dependencies.** If adding a library, verify it works under `--no-fallback`.
- **New Jackson DTOs** (anything `ObjectMapper` serializes/deserializes by field name) must be added to `src/main/resources/META-INF/native-image/io.floci/floci-cli/reflect-config.json`. Prefer passing `Map`/`JsonNode` to `printer.structured(...)` — those need no registration.
- **`@Command` classes** do not need manual reflect-config entries — `picocli-codegen` (the annotation processor in `pom.xml`) generates them automatically at compile time.
- **Do not remove** `--enable-url-protocols=http,https` from `native-image.properties`; `java.net.http.HttpClient` requires it.

---

## Scope Rules

- **No cloud resource commands.** This CLI manages Floci itself (lifecycle, config, diagnostics, state). Resource operations belong to the vendor CLIs pointed at the emulator: `aws` + `AWS_ENDPOINT_URL`, `gcloud`/SDK emulator-host vars, `az` + connection string.
- **No telemetry, no TUI, no Floci Cloud commands.**
- **Self-update is in scope** (`floci update`) — but it must never auto-run; updates happen only on explicit user invocation.
- **Snapshot commands:** the AWS `save/load/list/delete` call `/_floci/snapshots/*` and degrade gracefully (404/501 → "not available"); `export/import` and the entire GCP/Azure snapshot subtrees are stubs pending server-side endpoints in `floci-io/floci`, `floci-io/floci-gcp`, and `floci-io/floci-az`. Do not invent client-side snapshot behavior — the server API contract comes first.

---

## Error Message Convention

Every `printer.error(...)` call must end with a suggested next step:

```java
printer.error("Container 'floci' not found.\nRun 'floci start' to launch one.");
```

---

## Documentation Rules

- `README.md` documents all three product trees (AWS, GCP, Azure). If you add/change a command or flag, update the README in the same change.
- `CHANGELOG.md` follows Keep a Changelog. Every user-visible change gets an entry under `[Unreleased]` in the same PR.
- `CLAUDE.md` is gitignored (maintainer-local); this file (`AGENTS.md`) is the tracked source of truth for agent guidance. Update it here.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ This project uses [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Added

- `floci update` — self-update for native-binary installs. Downloads the platform binary from the GitHub release (same source as `install.sh`), verifies its sha256 against the release's `sha256sums.txt`, and atomically replaces the running binary (staged in the same directory so the `ATOMIC_MOVE` can never leave a half-written executable). `--check` reports staleness without installing (exit 0 = up to date, 1 = update available, script-friendly as `floci update --check || floci update`); `--version <v>` pins a target version. Homebrew-managed installs are detected and refused with a pointer to `brew upgrade floci` ([#12](https://github.com/floci-io/floci-cli/pull/12))
- `AGENTS.md` — tracked, repository-specific guidance for AI coding agents (architecture, native-image constraints, scope rules, conventions), following the [AGENTS.md standard](https://agents.md/)
- `CONTRIBUTING.md` — build/test setup, commit message and changelog conventions, PR guidelines, testing policy, and the tag-driven release process — plus a pull request template
- README now documents the `floci gcp` command group (quick start, command reference, global flags, `floci gcp env` variables, CI usage) — it was fully shipped since 0.1.3 but absent from the README, which claimed the CLI covered "AWS and Azure" only

### Fixed

- `ContainerInfo` fields realigned with the `docker inspect` `--format` template order in `DockerClient` — field/value mismatches could surface wrong container metadata (e.g. status/ports swapped) in `status` and port auto-detection ([#11](https://github.com/floci-io/floci-cli/pull/11))
- README and release notes claimed the fat JAR runs on Java 21+, but it is built with `--release 25` and fails with `UnsupportedClassVersionError` on Java 21–24 — corrected to Java 25+

## [0.1.8] — 2026-06-21

### Fixed
Expand Down
159 changes: 159 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Contributing to floci-cli

Thank you for your interest in contributing! `floci-cli` is a community-driven project and all contributions are welcome.

## Ways to Contribute

- **Bug reports** — open an issue with the CLI version (`floci version`), OS/arch, and the exact command + output
- **Feature requests** — open an issue describing the workflow you need (keep the [scope rules](#scope) in mind)
- **Pull requests** — bug fixes, new commands, diagnostics checks, or improvements

## Getting Started

### Prerequisites

- Java 25+
- Maven 3.9+
- Docker (the CLI drives `docker` subprocesses; you'll want a running Floci container to test against)
- GraalVM 25+ (only for building the native binary)

If you need to install a JDK, [SDKMAN](https://sdkman.io/) is a convenient option:

```bash
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install java 25-graal # GraalVM — also covers the plain JDK requirement
```

### Build & Test

```bash
git clone https://github.com/floci-io/floci-cli.git
cd floci-cli

mvn test # run all unit tests
mvn test -Dtest=WaitCommandParseTest # single test class
mvn test -Dtest=DockerVersionCheckTest#testVersionParsing # single method
mvn package # fat JAR → target/floci.jar
mvn package -Pnative -DskipTests # native binary → target/floci (requires GraalVM)
```

### Try it against a real emulator

```bash
java -jar target/floci.jar start # or ./target/floci start
java -jar target/floci.jar doctor
java -jar target/floci.jar stop
```

**If your change touches Jackson serialization or adds a dependency, build and run the native binary at least once** — reflection that works on the JVM can fail under `native-image`. See the Native Image Constraints section of [AGENTS.md](AGENTS.md).

## Architecture

See [AGENTS.md](AGENTS.md) for the architecture: command wiring, the three product trees (AWS/GCP/Azure), the Docker and HTTP I/O boundaries, the output pipeline, native-image constraints, and scope rules.

`AGENTS.md` is the canonical agent instructions file for this repository, following the [AGENTS.md standard](https://agents.md/). If your coding agent expects a different filename, create a local symlink instead of copying the file:

```bash
ln -s AGENTS.md CLAUDE.md
ln -s AGENTS.md GEMINI.md
```

### Rules that trip up most PRs

- **Three product trees.** The `gcp` and `az` command trees mirror the root/AWS tree. A change to a shared command (start, stop, status, logs, wait, doctor, …) must be applied to **all three trees** — `StartCommand`, `GcpStartCommand`, and `AzStartCommand` — until they are unified.
- **Native image.** New Jackson DTOs must be registered in `reflect-config.json`; prefer passing `Map`/`JsonNode` to `printer.structured(...)`, which needs no registration. No reflection-heavy dependencies.
- **Error messages.** Every `printer.error(...)` must end with a suggested next step:

```java
printer.error("Container 'floci' not found.\nRun 'floci start' to launch one.");
```

- **Structured output.** Every command must support `-o json|yaml` via `printer.structured(...)` alongside its text output.

## Scope

`floci-cli` manages Floci itself — lifecycle, config, diagnostics, state. Out of scope (PRs in these areas will be declined):

- Cloud resource commands (creating buckets, tables, topics, …) — that's the job of `aws`/`gcloud`/`az` pointed at the emulator
- Telemetry, TUIs, or Floci Cloud commands
- Client-side snapshot behavior beyond the server's `/_floci/snapshots/*` API

## Commit Message Format

This project uses [Conventional Commits](https://www.conventionalcommits.org/). PR titles should follow the same format, since they become the squash-merge commit message.

```
<type>[optional scope]: <description>
```

- **type** — lowercase, from the table below
- **scope** — optional, identifies the command or area (e.g. `start`, `doctor`, `update`, `gcp`)
- **description** — imperative mood, no trailing period
- Append `!` before the colon for a breaking change: `feat(cli)!:`

| Type | When to use |
|------|-------------|
| `feat` | New command, flag, or check |
| `fix` | Bug fix |
| `perf` | Performance improvement |
| `docs` | Documentation only |
| `refactor` | Code restructure without behavior change |
| `test` | Adding or updating tests |
| `build` / `ci` | Build system or CI workflow changes |
| `chore` | Dependencies, housekeeping, releases |

Examples from this repo's history:

```
feat(update): add self-update command with checksum verification
fix(start): mount host persistent path to /app/data in container
chore: release 0.1.8
```

Do not include `Co-Authored-By` trailers for AI tools in commit messages. Attribution should be limited to human contributors.

## Changelog

`CHANGELOG.md` follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and is maintained **by hand** (no semantic-release). Every PR with a user-visible change must add an entry under `[Unreleased]` in the same PR, written from the user's point of view (what was broken / what's new, not how it was implemented).

## Pull Request Guidelines

1. Branch off `main`: `git checkout -b feature/my-feature`
2. Open a PR targeting `main`
3. CI runs the JVM test suite and a linux/arm64 native smoke test — all checks must pass before merge
4. Keep PRs focused — one feature or fix per PR
5. Update `README.md` in the same PR if you add or change a command or flag
6. Reference related issues in the PR description

### Testing policy

- PRs that introduce new behavior must include tests validating that behavior
- Bug-fix PRs should include a regression test whenever the bug can be covered realistically
- Docs, formatting, and low-risk internal refactors may not need new tests — but the existing suite must pass
- If a PR includes no new tests, explain why in the description

## Release Process (maintainers)

Releases are **tag-driven**. Binaries are never published on PR merge — only when a SemVer tag is pushed.

```bash
# 1. On main: bump <version> in pom.xml, move [Unreleased] entries in
# CHANGELOG.md under the new version heading, and commit
git commit -am "chore: release 0.2.0"
git push

# 2. Tag and push — this triggers the release pipeline
git tag 0.2.0
git push origin 0.2.0
```

The tag push triggers `release.yml`, which builds native binaries for linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, and windows/amd64, plus the fat JAR, and publishes a GitHub Release with `sha256sums.txt`. A successful release then triggers `homebrew-bump.yml`, which opens a version-bump PR against the Homebrew tap automatically.

## Reporting Security Issues

Please do **not** open public issues for security vulnerabilities. Report them privately using [GitHub private vulnerability reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability).

## License

By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
Loading