PROJECT_NAME
Generated by kool create go-react-agent-cli — an agent-driven web app: a Go
API server plus a React UI, driven through a CLI with HTTP-verb sub-commands.
Usage
The CLI is a sub-command tree:
go run ./cmd/__PROJECT_NAME__ server # start the web UI + API (first free port from 8080)
go run ./cmd/__PROJECT_NAME__ get /ping # GET - print the response body
go run ./cmd/__PROJECT_NAME__ post /api/counter # allocate the next id
go run ./cmd/__PROJECT_NAME__ put <URI> '<json>' --json # replace a resource
go run ./cmd/__PROJECT_NAME__ delete <URI> # delete a resource
go run ./cmd/__PROJECT_NAME__ skill --show # the __PROJECT_NAME__ agent skill
URIs are bare paths (/api/counter → http://localhost:8080) or full URLs.
Writes are silent on success; --json prints the response body; --dry-run
prints the request without sending it. Install the agent skill with
go run ./cmd/__PROJECT_NAME__ skill --install.
To mount the app under a sub-route, pass --route-prefix:
go run ./cmd/__PROJECT_NAME__ server --route-prefix my-app
Then open http://localhost:<port>/my-app/.
Add --keep-root-route to keep serving the unprefixed root route as well, so
one process answers both a prefixed front door and a direct domain:
go run ./cmd/__PROJECT_NAME__ server --route-prefix my-app --keep-root-route
Then both http://localhost:<port>/my-app/ and http://localhost:<port>/ work.
Without the flag the root route returns 404.
API
Unknown /api/* paths return a JSON 404 (not the UI page), so CLI and agent
callers always get machine-readable errors. Register new routes in
server/server.go (RegisterAPI); the counter in server/store.go shows the
persistent-state pattern (~/.<project>/id.json via the shared id allocator):
go run ./cmd/__PROJECT_NAME__ post /api/counter # silent, allocates the next id
go run ./cmd/__PROJECT_NAME__ get /api/counter --json # {"last":1}
go run ./cmd/__PROJECT_NAME__ get /api/pages/home --json # cards with meta + rows
Ids are sequential, survive restarts, and are safe across processes. Replace
the counter with your own storage when you no longer need it.
Cards carry server-owned meta (server/pagemeta/parts/<Card>.json: title,
hint, empty). /api/pages/home returns the page document — cards in web
order, each with meta, an empty flag and its rows — so an agent reading the
API learns what every card is for without opening a .tsx. The React card
imports the same part over the @pagemeta/ alias.
Images
The scaffold ships a unified image library (server/images*.go: the store, the
routes, the audit, the delete guard, the sniffer; server/gallery.go: the demo
container). One store for the whole app, under
~/.<project>/images/<id>/: the bytes as image.<ext> and the meta.json that
commits the record. Ids come from the same id.json sequence as everything
else, and identical bytes reuse the record — so an id's bytes never change and
its URL is cacheable forever.
go run ./cmd/__PROJECT_NAME__ post /api/images --file photo.jpg --name 成都
# uploaded image 7 · 成都
# path /Users/you/.__PROJECT_NAME__/images/7/image.jpg
# url /api/data/images/7/image.jpg
go run ./cmd/__PROJECT_NAME__ get /api/images # audit: size, usage, state
go run ./cmd/__PROJECT_NAME__ get /api/images/7 # path first, then the verdict
go run ./cmd/__PROJECT_NAME__ delete /api/images/7 # refused while a page shows it
go run ./cmd/__PROJECT_NAME__ delete /api/images/7 --force # detach, then delete
Three properties are enforced, and each has a test in server/images_test.go:
- Bytes decide the format. A file whose bytes are not a picture is rejected
(
not an image: detected text/xml; the file name says .jpg) and the audit
keeps reporting it as NOT AN IMAGE — a 200 and an image/jpeg mime are not
evidence of a picture.
- One registry drives the audit and the delete.
imageContainers in
server/gallery.go lists every file that can reference an image; the demo
gallery.json is one. Deleting a referenced image is refused (the message
names the page) until --force detaches it, so no dangling id is left. A
reference in a file this build does not know about refuses even --force.
- Ids are one sequence.
idalloc with a floor of the largest image id on
disk, so a lost id.json reseeds instead of re-issuing an id.
Add your own container by giving it an image_ids field and registering it in
imageContainers. Full recipe:
go-best-practice skill --show cli/web-like-cli/unified-assets.
Agent skill
skill/SKILL.md is embedded in the binary and teaches agents the whole
workflow. Show it with go run ./cmd/__PROJECT_NAME__ skill --show, install it
with go run ./cmd/__PROJECT_NAME__ skill --install (lands in
~/.agents/skills/__PROJECT_NAME__).
Development
Dev Mode (recommended)
Starts Vite (HMR) and rebuilds the Go API with air on .go changes:
go run ./script/dev
Options:
go run ./script/dev --port 9000
go run ./script/dev --route-prefix my-app # serves http://localhost:<port>/my-app/
go run ./script/dev --route-prefix my-app --keep-root-route # serves /my-app/ and /
go run ./script/dev --no-air # compatibility alias: no backend hot reload
go run ./script/dev --no-open
Plain in-process Vite (no air) is still available:
go run ./script/dev --external-vite --vite-port 5173
External React Source
The generated frontend includes an external_src/ directory next to src/.
Code in that directory can be imported with the @external_src alias:
import { ExternalDemo } from '@external_src/demo-external/frontend/src/ExternalDemo';
Release Mode (Production Build)
- Build the frontend (embeds
__PROJECT_NAME__-react/dist):
go run ./script/build-frontend
- Run the server (embeds the built frontend):
go run ./cmd/__PROJECT_NAME__ server
Build / Install
go run ./script/build-frontend && go run ./script/build
go run ./script/install