agentcli-go
Shared Go CLI helpers and framework modules.
Module: github.com/gh-xj/agentcli-go
Repo: github.com/gh-xj/agentcli-go | Versioning: v0.x.y (pre-1.0)
API Surface
| File |
Exported Symbols |
log.go |
InitLogger() — zerolog setup, respects -v/--verbose |
args.go |
ParseArgs(args), RequireArg(args, name), GetArg(args, name), HasFlag(args, name) |
exec.go |
RunCommand(name, args...), RunOsascript(script), Which(bin), CheckDependency(bin) |
fs.go |
FileExists(path), EnsureDir(path), GetBaseName(path) |
core_context.go |
AppContext{Meta, Values}, NewAppContext(ctx) |
lifecycle.go |
Hook interface (Preflight, Postflight), RunLifecycle(app, hook, run) |
errors.go |
CLIError, ResolveExitCode(err), ExitSuccess, ExitUsage |
scaffold.go |
ScaffoldNew(baseDir, name, module), ScaffoldAddCommand(rootDir, name, desc, preset), Doctor(rootDir) DoctorReport |
cobrax/cobrax.go |
Execute(RootSpec, args) int, NewRoot(RootSpec) *cobra.Command, CommandSpec, RootSpec |
configx/configx.go |
Load(Options) map[string]any, Decode[T](raw), NormalizeEnv(prefix, environ) |
Scaffold Workflows
New project
agentcli new --name my-tool --module github.com/me/my-tool
# or programmatically:
agentcli.ScaffoldNew(".", "my-tool", "github.com/me/my-tool")
Generates: main.go, cmd/root.go, internal/app/, internal/config/, internal/io/, internal/tools/smokecheck/, pkg/version/, test/, Taskfile.yml, README.md
Add command
agentcli add command --name sync --preset file-sync
agentcli add command --name deploy --desc "run deploy checks"
Presets: file-sync, http-client, deploy-helper
Doctor check
agentcli doctor [--dir ./my-tool]
# returns DoctorReport JSON with findings
Golden Project Layout
my-tool/
├── main.go # os.Exit(cmd.Execute(os.Args[1:]))
├── cmd/
│ ├── root.go # cobrax.Execute(RootSpec{...})
│ └── <command>.go # func <Name>Command() command
├── internal/
│ ├── app/{bootstrap,lifecycle,errors}.go
│ ├── config/{schema,load}.go
│ ├── io/output.go
│ └── tools/smokecheck/main.go
├── pkg/version/version.go
├── test/
│ ├── e2e/cli_test.go
│ └── smoke/version.schema.json
└── Taskfile.yml
cobrax Pattern
// cmd/root.go
return cobrax.Execute(cobrax.RootSpec{
Use: "my-tool",
Short: "my-tool CLI",
Meta: agentcli.AppMeta{Name: "my-tool", Version: version.Version},
Commands: []cobrax.CommandSpec{
{Use: "sync", Short: "sync files", Run: SyncCommand().Run},
},
}, args)
Persistent flags auto-wired: --verbose/-v, --config, --json, --no-color
Values accessible via app.Values["json"], app.Values["config"], etc.
configx Pattern
raw, err := configx.Load(configx.Options{
Defaults: map[string]any{"env": "default"},
FilePath: configPath, // optional JSON file
Env: configx.NormalizeEnv("MYTOOL_", os.Environ()),
Flags: map[string]string{"env": flagVal},
})
cfg, err := configx.Decode[config.Config](raw)
// Precedence: Defaults < File < Env < Flags
Taskfile Tasks
| Task |
Purpose |
task ci |
Canonical CI: preflight + lint + test + build + smoke + schema checks |
task verify |
Local aggregate (wraps ci) |
task lint |
go vet + golangci-lint |
task smoke |
Deterministic smoke tests (subset of unit tests) |
task schema:check |
Validate JSON contracts against schemas |
task docs:check |
Ensure skill docs match CLI help signatures |
task fmt |
Format all Go files |
Rules
- Flat package — everything in
package agentcli, no sub-packages (except cobrax, configx)
- Exported only — all functions PascalCase; this is a library
- No business logic — generic utilities only; must be reused across 2+ projects to qualify
log.Fatal allowed in RequireArg, CheckDependency (CLI-oriented helpers)
- Minimal deps — zerolog, lo, cobra only; justify new additions
Out of Scope
- Project-specific logic (put that in consuming projects)
- Adding functions used by only one project
1---2name: agentcli-go3description: agentcli-go framework reference for building Go CLI tools. Use when working on agentcli-go itself, scaffolding new CLI projects, adding commands, integrating the library, or debugging framework behavior. Triggers on: agentcli-go, scaffold new CLI, add command, cobrax, configx, AppContext, RunLifecycle, agentcli.4---5
6# agentcli-go
7
8Shared Go CLI helpers and framework modules.
9
10**Module:** `github.com/gh-xj/agentcli-go`
11**Repo:** `github.com/gh-xj/agentcli-go` | **Versioning:** `v0.x.y` (pre-1.0)
12
13---
14
15## API Surface
16
17| File | Exported Symbols |
18|------|-----------------|
19| `log.go` | `InitLogger()` — zerolog setup, respects `-v`/`--verbose` |
20| `args.go` | `ParseArgs(args)`, `RequireArg(args, name)`, `GetArg(args, name)`, `HasFlag(args, name)` |
21| `exec.go` | `RunCommand(name, args...)`, `RunOsascript(script)`, `Which(bin)`, `CheckDependency(bin)` |
22| `fs.go` | `FileExists(path)`, `EnsureDir(path)`, `GetBaseName(path)` |
23| `core_context.go` | `AppContext{Meta, Values}`, `NewAppContext(ctx)` |
24| `lifecycle.go` | `Hook` interface (`Preflight`, `Postflight`), `RunLifecycle(app, hook, run)` |
25| `errors.go` | `CLIError`, `ResolveExitCode(err)`, `ExitSuccess`, `ExitUsage` |
26| `scaffold.go` | `ScaffoldNew(baseDir, name, module)`, `ScaffoldAddCommand(rootDir, name, desc, preset)`, `Doctor(rootDir) DoctorReport` |
27| `cobrax/cobrax.go` | `Execute(RootSpec, args) int`, `NewRoot(RootSpec) *cobra.Command`, `CommandSpec`, `RootSpec` |
28| `configx/configx.go` | `Load(Options) map[string]any`, `Decode[T](raw)`, `NormalizeEnv(prefix, environ)` |
29
30---
31
32## Scaffold Workflows
33
34### New project
35```bash
36agentcli new --name my-tool --module github.com/me/my-tool
37# or programmatically:
38agentcli.ScaffoldNew(".", "my-tool", "github.com/me/my-tool")
39```
40Generates: `main.go`, `cmd/root.go`, `internal/app/`, `internal/config/`, `internal/io/`, `internal/tools/smokecheck/`, `pkg/version/`, `test/`, `Taskfile.yml`, `README.md`
41
42### Add command
43```bash
44agentcli add command --name sync --preset file-sync
45agentcli add command --name deploy --desc "run deploy checks"
46```
47Presets: `file-sync`, `http-client`, `deploy-helper`
48
49### Doctor check
50```bash
51agentcli doctor [--dir ./my-tool]
52# returns DoctorReport JSON with findings
53```
54
55---
56
57## Golden Project Layout
58
59```
60my-tool/
61├── main.go # os.Exit(cmd.Execute(os.Args[1:]))
62├── cmd/
63│ ├── root.go # cobrax.Execute(RootSpec{...})
64│ └── <command>.go # func <Name>Command() command
65├── internal/
66│ ├── app/{bootstrap,lifecycle,errors}.go
67│ ├── config/{schema,load}.go
68│ ├── io/output.go
69│ └── tools/smokecheck/main.go
70├── pkg/version/version.go
71├── test/
72│ ├── e2e/cli_test.go
73│ └── smoke/version.schema.json
74└── Taskfile.yml
75```
76
77---
78
79## cobrax Pattern
80
81```go
82// cmd/root.go
83return cobrax.Execute(cobrax.RootSpec{
84 Use: "my-tool",
85 Short: "my-tool CLI",
86 Meta: agentcli.AppMeta{Name: "my-tool", Version: version.Version},
87 Commands: []cobrax.CommandSpec{
88 {Use: "sync", Short: "sync files", Run: SyncCommand().Run},
89 },
90}, args)
91```
92
93Persistent flags auto-wired: `--verbose/-v`, `--config`, `--json`, `--no-color`
94Values accessible via `app.Values["json"]`, `app.Values["config"]`, etc.
95
96---
97
98## configx Pattern
99
100```go
101raw, err := configx.Load(configx.Options{
102 Defaults: map[string]any{"env": "default"},
103 FilePath: configPath, // optional JSON file
104 Env: configx.NormalizeEnv("MYTOOL_", os.Environ()),
105 Flags: map[string]string{"env": flagVal},
106})
107cfg, err := configx.Decode[config.Config](raw)
108// Precedence: Defaults < File < Env < Flags
109```
110
111---
112
113## Taskfile Tasks
114
115| Task | Purpose |
116|------|---------|
117| `task ci` | Canonical CI: preflight + lint + test + build + smoke + schema checks |
118| `task verify` | Local aggregate (wraps ci) |
119| `task lint` | go vet + golangci-lint |
120| `task smoke` | Deterministic smoke tests (subset of unit tests) |
121| `task schema:check` | Validate JSON contracts against schemas |
122| `task docs:check` | Ensure skill docs match CLI help signatures |
123| `task fmt` | Format all Go files |
124
125---
126
127## Rules
128
129- **Flat package** — everything in `package agentcli`, no sub-packages (except `cobrax`, `configx`)
130- **Exported only** — all functions PascalCase; this is a library
131- **No business logic** — generic utilities only; must be reused across 2+ projects to qualify
132- **`log.Fatal` allowed** in `RequireArg`, `CheckDependency` (CLI-oriented helpers)
133- **Minimal deps** — zerolog, lo, cobra only; justify new additions
134
135## Out of Scope
136
137- Project-specific logic (put that in consuming projects)
138- Adding functions used by only one project