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
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: ci

on:
push:
branches: [main]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.14"
- uses: astral-sh/setup-uv@v8
with:
enable-cache: true
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- run: uv sync --locked
- run: make cli-sync
- name: Verify generated CLI is current
run: git diff --exit-code -- cmd/appctl/cli.yaml internal/generated skills/appctl go.mod go.sum
- run: make test
- run: uv run --locked python -m compileall -q src tests
- run: go vet ./...
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.cache/
.local/
.venv/
__pycache__/
*.py[cod]
bin/
coverage/
.env
.DS_Store

17 changes: 17 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Agent Development Contract

This repository is CLI-first. The generated `appctl` CLI is the primary acceptance surface for application behavior.

For every API-facing change:

1. Update the Python application under `src/`.
2. Update `openapi/openapi.yaml` in the same change. It is the API contract and CLI source of truth.
3. Run `make check` to regenerate, build, and test through `appctl`.
4. Commit the matching changes under `internal/generated/`, `skills/appctl/`, and `cmd/appctl/cli.yaml`.

Do not hand-edit generated files. Use direct HTTP only for transport-level checks that the generated CLI cannot express.

Before guessing a command or flag, use `appctl search "<intent>" --json`, then `appctl commands show <path...> --json`.

Keep the Python application on the standard library until a concrete requirement needs a package. Use `uv` for Python commands. Do not add database, authentication, deployment, or compatibility scaffolding without an explicit requirement.

21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 lathe-cli

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
17 changes: 17 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.PHONY: cli-sync cli-build test check

cli-sync:
cp cli.yaml cmd/appctl/cli.yaml
go run github.com/lathe-cli/lathe/cmd/lathe@v0.4.4 bootstrap
go mod tidy

cli-build:
go build -o bin/appctl ./cmd/appctl

test: cli-build
uv run --locked python -m unittest discover -s tests

check: cli-sync test
uv run --locked python -m compileall -q src tests
go vet ./...

24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# CLI-first Python app

A minimal Python application where the CLI is ready on day one. Change the application, update OpenAPI, and test through the generated `appctl` CLI.

Requires Python 3.14+, uv, and Go 1.25+.

```sh
uv sync
make check
uv run --locked python -m src.server
```

In another terminal:

```sh
./bin/appctl search "create task" --json
./bin/appctl commands show tasks create --json
./bin/appctl tasks create --set title="Ship from the CLI" -o json
```

`make check` regenerates the CLI from `openapi/openapi.yaml`, builds it, and runs acceptance tests through CLI commands. `make test` only builds and tests the checked-in generated CLI.

Generated output under `internal/generated/` and `skills/appctl/` is committed with API changes. The pipeline uses [Lathe](https://github.com/lathe-cli/lathe), pinned by version.

4 changes: 4 additions & 0 deletions cli.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cli:
name: appctl
short: "CLI for the application API"

4 changes: 4 additions & 0 deletions cmd/appctl/cli.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cli:
name: appctl
short: "CLI for the application API"

20 changes: 20 additions & 0 deletions cmd/appctl/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
_ "embed"
"os"

"github.com/lathe-cli/lathe/pkg/lathe"

"example.com/cli-first-python-app/internal/generated"
)

//go:embed cli.yaml
var manifestBytes []byte

func main() {
os.Exit(lathe.Run(lathe.RunOptions{
Manifest: manifestBytes,
Mount: generated.MountModules,
}))
}
16 changes: 16 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module example.com/cli-first-python-app

go 1.25.7

require (
github.com/lathe-cli/lathe v0.4.4
github.com/spf13/cobra v1.10.2
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/term v0.44.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
19 changes: 19 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/lathe-cli/lathe v0.4.4 h1:5TNnLpLccH7YAxt3n1Lv5GOrf4i+U264BGvb3v2Haws=
github.com/lathe-cli/lathe v0.4.4/go.mod h1:R7LnEOMjrGHDutF3l2VyY5yc7AAoKLsFSj4JeSuLJRc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
113 changes: 113 additions & 0 deletions internal/generated/app/app_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions internal/generated/modules_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading