Imported from omnistrate-oss/omnistrate-ctl (
AGENTS.md). Install upstream withnpx skills add omnistrate-oss/omnistrate-ctl. Copyright stays with the author.
AGENTS.md - CLI Development Guidelines
Key Documentation
Read DEVELOPMENT.md for the full architecture guide, step-by-step walkthrough for adding new commands, and SDK API reference. The sections below are a quick reference.
Build Commands
- Build:
make build - Run all tests:
make unit-test - Run single test:
go test ./path/to/package -run TestName - Run smoke tests:
make smoke-test(requires TEST_EMAIL, TEST_PASSWORD) - Lint code:
make lint(install withmake lint-install) - Format code:
make pretty(uses prettier and go fmt) - Generate docs:
make gen-doc(regenerates CLI documentation) - Run everything:
make all(includes tidy, build, test, lint, check-dependencies, gen-doc, pretty)
Documentation Generation Requirements
IMPORTANT: After making ANY changes to CLI commands, flags, or help text, you MUST run make gen-doc to regenerate the documentation. This ensures the docs in mkdocs/docs/ stay synchronized with the actual CLI behavior.
- The
gen-doctarget runsgo run doc-gen/main.gowhich auto-generates markdown files - Documentation files are automatically removed and regenerated to stay current
- Always run
make gen-docormake allafter modifying:- Command definitions in
cmd/directory - Flag descriptions or help text
- Command usage examples
- Any cobra command configurations
- Command definitions in
Code Style Guidelines
- Follow Go standard naming conventions: camelCase for variables, PascalCase for exports
- Use error handling with appropriate checks - wrap errors with context when needed
- Format imports with standard Go style (stdlib first, then external, then internal)
- Use descriptive variable/function names and maintain consistent indentation
- Add tests for all new functionality and maintain high coverage
- Follow project structure with cmd/ for commands and internal/ for implementation
- Commit messages should be clear and descriptive (feature/fix/chore: message)
Project-Specific Patterns
- Prefer functional options for configuration
- Use cobra for CLI commands with consistent flags/args pattern
- Use tabwriter for formatted terminal output
Adding New CLI Commands
Refer to DEVELOPMENT.md for the full guide. Summary of the three-layer architecture:
- Model (
internal/model/<entity>.go): Display struct with JSON tags - Data Access (
internal/dataaccess/<entity>.go): SDK API wrapper functions - Command (
cmd/<entity>/): Cobra commands with parent + subcommands
Creating a New Entity — Quick Checklist
- Create
internal/model/<entity>.go— display struct - Create
internal/dataaccess/<entity>.go— SDK wrapper functions usinggetV1Client()orgetFleetClient() - Create
cmd/<entity>/<entity>.go— parent command withvar Cmd+init()wiring - Create
cmd/<entity>/list.go,describe.go,delete.go, etc. — subcommands - Register in
cmd/root.go: add import +RootCmd.AddCommand(<entity>.Cmd) - Create unit tests in
cmd/<entity>/<entity>_test.go— verify command structure, flags, and pure logic - Create smoke tests in
test/smoke_test/<entity>/<entity>_test.go— end-to-end CLI tests - Create integration tests in
test/integration_test/dataaccess/<entity>_test.go— direct API tests - Run
make build && make unit-test && make gen-doc
SDK API Discovery
Browse available SDK operations:
# V1 APIs (service management)
ls $(go env GOMODCACHE)/github.com/omnistrate-oss/omnistrate-sdk-go@v0.0.90/v1/api_*.go
# Fleet APIs (fleet/operational)
ls $(go env GOMODCACHE)/github.com/omnistrate-oss/omnistrate-sdk-go@v0.0.90/fleet/api_*.go
# Find methods on a specific API
grep "func (a \*<EntityName>ApiAPIService)" \
$(go env GOMODCACHE)/github.com/omnistrate-oss/omnistrate-sdk-go@v0.0.90/v1/api_<entity>_api.go
Spinner Usage — Nil Guard Required
Spinners are only created when output != "json", so both spinner and sm will be nil in JSON output mode. Spinner.UpdateMessage(), .Complete(), and .Error() are NOT nil-safe — calling them on a nil receiver will panic.
Rule: Always guard direct spinner/sm method calls with a nil check.
// ✅ Correct — guarded
if spinner != nil {
spinner.UpdateMessage("Processing...")
}
// ✅ Correct — HandleSpinnerError/HandleSpinnerSuccess are already nil-safe
utils.HandleSpinnerError(spinner, sm, err)
utils.HandleSpinnerSuccess(spinner, sm, "Done")
// ❌ Wrong — will panic when output is "json"
spinner.UpdateMessage("Processing...")
spinner.Complete()
sm.Stop()
The same applies to sm.AddSpinner(), sm.Start(), sm.Stop() — guard with if sm != nil.
Reference for correct multi-spinner pattern: cmd/build/build.go (artifact upload section), cmd/instance/version_upgrade.go.
Agent Instructions: Adding New Entity Operations
When asked to add CLI support for a new Omnistrate entity, follow this systematic workflow:
Phase 1: SDK Discovery
- Identify the entity name from the user request (e.g., "notification", "webhook", "report")
- Find the matching SDK API file by searching for the entity name in both V1 and Fleet API directories:
ls $(go env GOMODCACHE)/github.com/omnistrate-oss/omnistrate-sdk-go@v0.0.90/v1/api_*<entity>*.go ls $(go env GOMODCACHE)/github.com/omnistrate-oss/omnistrate-sdk-go@v0.0.90/fleet/api_*<entity>*.go - Extract available operations by reading the API file and listing all exported methods:
grep "func (a \*" <api_file_path> - Find request/response model types used by those operations:
ls $(go env GOMODCACHE)/github.com/omnistrate-oss/omnistrate-sdk-go@v0.0.90/v1/model_*<entity>*.go ls $(go env GOMODCACHE)/github.com/omnistrate-oss/omnistrate-sdk-go@v0.0.90/fleet/model_*<entity>*.go - Read model files to understand fields available for the display struct
Phase 2: Implementation
Follow the patterns documented in DEVELOPMENT.md:
-
Create
internal/model/<entity>.go— Define a display struct with string fields and JSON tags. Pick the most useful fields from the SDK response model for table display. -
Create
internal/dataaccess/<entity>.go— For each SDK operation, create a wrapper function:- Determine the correct client:
getV1Client()orgetFleetClient() - Determine the correct error handler:
handleV1Error()orhandleFleetError() - Follow the
ctxWithToken → apiClient → Execute() → defer close → handle errorpattern - Match the function signature to existing patterns in the codebase (see
internal/dataaccess/secret.goas a clean example)
- Determine the correct client:
-
Create
cmd/<entity>/directory with:<entity>.go— Parent command (var Cmd) wiring subcommands ininit()- One file per subcommand (list.go, describe.go, delete.go, create.go, etc.)
- Each subcommand must:
- Use
RunE(notRun) to return errors - Call
defer config.CleanupArgsAndFlags(cmd, &args)first - Call
common.GetTokenWithLogin()for auth - Use spinner for non-JSON output on list/describe operations
- Convert SDK response to model struct(s) for output
- Include
ExampleandSilenceUsage: true
- Use
-
Register in
cmd/root.go— Add import andRootCmd.AddCommand() -
Create unit tests (
cmd/<entity>/<entity>_test.go) — REQUIRED for every new command:- Test parent command metadata (
Use,Short,Long) - Test all subcommands are registered via
Cmd.Commands() - Test each subcommand's flags exist with correct type, default value, and shorthand
- Test pure logic/helper functions with table-driven tests (valid inputs, invalid inputs, edge cases)
- Use
testify/requirefor assertions - Follow patterns in
cmd/operations/operations_test.go(structure tests) andcmd/instance/common_test.go(table-driven logic tests)
- Test parent command metadata (
-
Create smoke tests (
test/smoke_test/<entity>/<entity>_test.go) — REQUIRED for every new command:- Guard with
testutils.SmokeTest(t), cleanup withdefer testutils.Cleanup() - Login using
testutils.GetTestAccount()thencmd.RootCmd.SetArgs(["login", ...])+ExecuteContext - Test CRUD lifecycle: set args via
cmd.RootCmd.SetArgs(), execute viacmd.RootCmd.ExecuteContext(ctx), assert no error - Test both default and JSON output formats
- Follow pattern in
test/smoke_test/secret/secret_test.go
- Guard with
-
Create integration tests (
test/integration_test/dataaccess/<entity>_test.go) — REQUIRED for every new command:- Guard with
testutils.IntegrationTest(t) - Call
dataaccessfunctions directly (not via CLI) - Use table-driven tests with
wantErrandexpectedErrMsgfields - Test valid operations, invalid tokens, missing required parameters
- Follow pattern in
test/integration_test/dataaccess/signin_test.go
- Guard with
Phase 3: Validation
- Run
make buildto verify compilation - Run
make unit-testto verify all unit tests pass (including new tests) - Run
make gen-docto regenerate CLI documentation - Verify the new command appears in help:
./dist/omnistrate-ctl-* <entity> --help - Run a single new test to confirm:
go test ./cmd/<entity>/ -run Test<Entity>Commands -v
Reference Examples by Complexity
- Simple CRUD (args-based):
cmd/secret/+internal/dataaccess/secret.go+internal/model/secret.go - Simple CRUD (flags-based):
cmd/service/+internal/dataaccess/service.go+internal/model/service.go - Complex with many subcommands:
cmd/instance/+internal/dataaccess/resourceinstance.go+internal/model/instance.go - Fleet API entity:
cmd/workflow/+internal/dataaccess/workflow.go+internal/model/workflow.go
Reference Test Examples
- Command structure unit tests:
cmd/operations/operations_test.go,cmd/workflow/workflow_test.go - Table-driven logic unit tests:
cmd/instance/common_test.go(parseCustomTags, formatTags, matchesTagFilters) - Smoke tests (CLI end-to-end):
test/smoke_test/secret/secret_test.go - Integration tests (direct API):
test/integration_test/dataaccess/signin_test.go - Test utilities:
test/testutils/testutils.go(guards, auth, cleanup)