From 584237c13b3bc2bb1934478f715d8cac67edaa33 Mon Sep 17 00:00:00 2001 From: Rian Stockbower Date: Sat, 30 May 2026 11:36:14 -0400 Subject: [PATCH] docs: index agent guidance sources Closes #126 --- AGENTS.md | 24 +-- CLAUDE.md | 375 ++------------------------------------------ docs/development.md | 141 +++++++++++++++++ 3 files changed, 169 insertions(+), 371 deletions(-) create mode 100644 docs/development.md diff --git a/AGENTS.md b/AGENTS.md index 55dd8ab..5cae43e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,16 +1,22 @@ -# Repository Instructions +# AGENTS.md -Follow the conventions in `CLAUDE.md` and `CONTRIBUTING.md`. +Codex entrypoint for the newrelic-cli repository. -For Codex sessions, prefix shell commands with `rtk`. +## Start Here -## Pull Requests And Releases +Source of truth: https://github.com/open-cli-collective/newrelic-cli/blob/main/docs/development.md +Local convenience copy, if present: `docs/development.md` -Use conventional-commit PR titles. This repository squash-merges PRs, and the squash commit message comes from the PR title. +## Shared Standards -The `Auto Release` workflow only creates a new version tag when both release gates pass: +Source of truth: https://github.com/open-cli-collective/cli-common/tree/main/docs +Local convenience copy, if present: `../cli-common/docs` -- The merge changes Go code, `go.mod`, or `go.sum`. -- The squash commit starts with `feat:`, `feat(...)`, `fix:`, or `fix(...)`. +## Shared Automation -When a Go change should publish new binaries, title the PR with a release-triggering prefix such as `fix(scope): ...` or `feat(scope): ...`. +Source of truth: https://github.com/open-cli-collective/.github +Local convenience copy, if present: `../.github` + +This file is an index. Keep New Relic-specific guidance in +`docs/development.md` and shared conventions in the canonical repositories +above. diff --git a/CLAUDE.md b/CLAUDE.md index 6ec750c..19933b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,371 +1,22 @@ # CLAUDE.md -This file provides guidance for AI agents working with the newrelic-cli codebase. +Claude entrypoint for the newrelic-cli repository. -## Project Overview +## Start Here -newrelic-cli is a command-line interface for New Relic written in Go. It uses the Cobra framework for commands and provides a public `api/` package that can be imported as a Go library. Per cli-common [`docs/output-and-rendering.md`](https://github.com/open-cli-collective/cli-common/blob/main/docs/output-and-rendering.md) §2, resource reads emit text only (`-o table|plain`); JSON is reserved for control-plane envelopes (`set-credential`, `config show`, `config test` via subcommand-local `--json`) and passthrough surfaces (`nerdgraph`, `nrql`). The API key is stored in the OS keyring via the shared `cli-common/credstore` (macOS Keychain / Windows Credential Manager / Linux Secret Service), or an encrypted file with the explicit file-backend opt-in — never in plaintext and never in `config.yml`; non-secret `account_id`/`region` live in `~/.config/newrelic-cli/config.yml`. See "Credentials" below. +Source of truth: https://github.com/open-cli-collective/newrelic-cli/blob/main/docs/development.md +Local convenience copy, if present: `docs/development.md` -## Quick Commands +## Shared Standards -```bash -# Build -make build +Source of truth: https://github.com/open-cli-collective/cli-common/tree/main/docs +Local convenience copy, if present: `../cli-common/docs` -# Run tests -make test +## Shared Automation -# Run tests with coverage -make test-cover +Source of truth: https://github.com/open-cli-collective/.github +Local convenience copy, if present: `../.github` -# Lint -make lint - -# Format code -make fmt - -# All checks (format, lint, test) -make verify - -# Install locally -make install - -# Clean build artifacts -make clean -``` - -## Architecture - -``` -newrelic-cli/ -├── cmd/nrq/main.go # Entry point - registers commands, calls Execute() -├── api/ # Public Go library (importable) -│ ├── client.go # Client struct, New(), NewWithConfig(), HTTP helpers -│ ├── types.go # All data types (Application, AlertPolicy, etc.) -│ ├── errors.go # Error types: APIError, ErrNotFound, ErrUnauthorized -│ ├── applications.go # ListApplications, GetApplication, ListApplicationMetrics -│ ├── alerts.go # ListAlertPolicies, GetAlertPolicy -│ ├── dashboards.go # ListDashboards, GetDashboard -│ ├── deployments.go # ListDeployments, CreateDeployment -│ ├── entities.go # SearchEntities -│ ├── logs.go # ListLogParsingRules, CreateLogParsingRule, DeleteLogParsingRule -│ ├── nrql.go # QueryNRQL -│ ├── synthetics.go # ListSyntheticMonitors, GetSyntheticMonitor -│ └── users.go # ListUsers, GetUser -├── internal/ -│ ├── cmd/ # Cobra commands (one package per resource) -│ │ ├── root/root.go # Root command, Options struct, global flags -│ │ ├── apps/ # apps list, get, metrics -│ │ ├── alerts/ # alerts policies list, get -│ │ ├── configcmd/ # config set/show/clear/test + set-credential ingress -│ │ ├── dashboards/ # dashboards list, get -│ │ ├── deployments/ # deployments list, create -│ │ ├── entities/ # entities search -│ │ ├── logs/ # logs rules list, create, delete -│ │ ├── me/ # me — identity/access check (verifies the key) -│ │ ├── nerdgraph/ # nerdgraph query -│ │ ├── nrql/ # nrql query -│ │ ├── synthetics/ # synthetics list, get -│ │ └── users/ # users list, get -│ ├── config/config.go # Non-secret config.yml (credential_ref, account_id, region) -│ ├── keychain/ # cli-common/credstore adapter + §1.8 migration (always-stderr signal) -│ ├── version/version.go # Build-time version injection via ldflags -│ └── view/view.go # Output formatting (table, plain only — JSON via per-subcommand carve-outs) -├── Makefile # Build, test, lint targets -└── go.mod # Module: github.com/open-cli-collective/newrelic-cli -``` - -## Key Patterns - -### Options Struct Pattern - -Commands use an Options struct for dependency injection: - -```go -// Root options (global flags) -type Options struct { - Output string // table or plain (closed-set; cli-common §2) - NoColor bool - Stdin io.Reader - Stdout io.Writer - Stderr io.Writer -} - -// Command-specific options embed root options -type createOptions struct { - *root.Options - revision string - description string -} -``` - -### Register Pattern - -Each command package exports a Register function: - -```go -func Register(rootCmd *cobra.Command, opts *root.Options) { - cmd := &cobra.Command{ - Use: "apps", - Short: "Manage APM applications", - } - cmd.AddCommand(newListCmd(opts)) - cmd.AddCommand(newGetCmd(opts)) - rootCmd.AddCommand(cmd) -} -``` - -### View Pattern - -Use the View struct for formatted output: - -```go -v := opts.View() - -// Table output (default) or plain — Render switches on v.Format. -headers := []string{"ID", "NAME", "STATUS"} -rows := [][]string{{"123", "app", "green"}} -v.Render(headers, rows) - -// Plain output (tab-separated, no headers) -v.Plain(rows) - -// Messages -v.Success("Created successfully") -v.Warning("Deprecated flag") -v.Error("Failed: %v", err) -``` - -JSON has no global render method by design (cli-common §2). Carve-out -commands (`set-credential`, `config show`, `config test`) declare a local -`--json` boolean and write directly: - -```go -enc := json.NewEncoder(opts.Stdout) -enc.SetIndent("", " ") -return enc.Encode(envelope) -``` - -### Safe Type Assertions - -NerdGraph returns `map[string]interface{}`. Use safe helpers: - -```go -// Safe extraction from interface{} -name := safeString(data["name"]) // Returns "" if not string -count := safeInt(data["count"]) // Returns 0 if not float64 -nested, ok := safeMap(data["nested"]) // Returns map and bool -items, ok := safeSlice(data["items"]) // Returns slice and bool -``` - -### API Client Initialization - -```go -// From environment/config (recommended) -client, err := api.New() - -// With explicit config -client := api.NewWithConfig(api.ClientConfig{ - APIKey: "NRAK-xxx", - AccountID: "12345", - Region: "US", -}) -``` - -### Domain Types - -The API package uses dedicated types for New Relic identifiers to provide type safety and self-documenting code: - -| Type | Purpose | Key Methods | -|------|---------|-------------| -| `EntityGUID` | Entity identifiers (base64 encoded) | `Parse()`, `AppID()`, `Validate()` | -| `APIKey` | User API keys (NRAK- prefix) | `Validate()`, `HasNRAKPrefix()` | -| `AccountID` | Account identifiers (numeric) | `Int()`, `Validate()`, `IsEmpty()` | - -Use constructors (`NewAPIKey`, `NewAccountID`) for validation at boundaries (user input, config loading). Type fields in structs are already validated. - -```go -// At boundaries - validate on creation -key, warning, err := api.NewAPIKey(userInput) -accountID, err := api.NewAccountID(configValue) - -// In structs - types provide documentation and safety -type Client struct { - APIKey APIKey // Not just "string" - AccountID AccountID // Not just "string" -} -``` - -## Testing Philosophy - -- Unit tests in `*_test.go` files alongside source -- Use `testify/assert` for assertions -- Table-driven tests for multiple scenarios -- Injectable clients for command testing (via Options struct) - -Example test structure: - -```go -func TestSomething(t *testing.T) { - tests := []struct { - name string - input string - expected string - }{ - {"case 1", "input1", "output1"}, - {"case 2", "input2", "output2"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := Something(tt.input) - assert.Equal(t, tt.expected, result) - }) - } -} -``` - -## Workflows - -### Branch Naming - -``` -type/description -``` - -Examples: -- `feat/add-alerts-conditions` -- `fix/nrql-timeout` -- `refactor/extract-api-package` -- `docs/update-readme` - -### Commit Messages - -Use conventional commits: - -``` -type(scope): description - -feat(alerts): add conditions list command -fix(nrql): increase timeout for long queries -docs(readme): add scripting examples -refactor(api): extract NerdGraph client -test(apps): add unit tests for list command -chore(deps): update cobra to v1.8.0 -``` - -| Prefix | Purpose | Triggers Release? | -|--------|---------|-------------------| -| `feat:` | New features | Yes | -| `fix:` | Bug fixes | Yes | -| `docs:` | Documentation only | No | -| `test:` | Adding/updating tests | No | -| `refactor:` | Code changes that don't fix bugs or add features | No | -| `chore:` | Maintenance tasks | No | -| `ci:` | CI/CD changes | No | - -### Pull Request Process - -1. Create feature branch from `main` -2. Make changes with tests -3. Run `make verify` -4. Push and create PR targeting `main` -5. Add reviewer - -## CI & Release Workflow - -Releases are automated with a dual-gate system to avoid unnecessary releases: - -**Gate 1 - Path filter:** Only triggers when Go code changes (`**.go`, `go.mod`, `go.sum`) -**Gate 2 - Commit prefix:** Only `feat:` and `fix:` commits create releases - -This means: -- `feat: add command` + Go files changed → release -- `fix: handle edge case` + Go files changed → release -- `docs:`, `ci:`, `test:`, `refactor:` → no release -- Changes only to docs, packaging, workflows → no release - -**After merging a release-triggering PR:** The workflow creates a tag, which triggers GoReleaser to build binaries and publish to Homebrew. Chocolatey and Winget require manual workflow dispatch. - -## Common Tasks - -### Adding a New Command - -1. Create package in `internal/cmd//` -2. Create `.go` with Register function -3. Create subcommand files (`list.go`, `get.go`, etc.) -4. Add Register call to `cmd/nrq/main.go` -5. Add API methods to `api/` if needed -6. Write tests - -### Adding an API Method - -1. Add type definitions to `api/types.go` -2. Add method to appropriate domain file (`api/applications.go`, etc.) -3. Use `doRequest()` for REST or `NerdGraphQuery()` for GraphQL -4. Handle errors with `APIError` or return `ErrNotFound` - -### Changing Output Format - -The View struct handles all output formatting. To add a new format: - -1. Update `internal/view/view.go` -2. Add format constant and case in `Render()` -3. Update `ValidateFormat()` - -## Credentials (Secret-Handling Standard §2.5) - -The API key is stored in the OS keyring via `cli-common/credstore` under ref -`newrelic-cli/default`, key `api_key` (one logical credential, one key — -§1.3). It is **never** stored in plaintext, **never** in `config.yml`, and -**never** read from the environment at runtime. `account_id`/`region` are -non-secret and live in `~/.config/newrelic-cli/config.yml` alongside -`credential_ref` and the optional `keyring.backend` selector. - -**Backend selection** has three user-configurable knobs that fall back to -auto-detect, in precedence order: `--backend ` flag > -`NEWRELIC_CLI_KEYRING_BACKEND` env var > `keyring.backend` in `config.yml` -> auto-detect. Supported names: `keychain`, `wincred`, `secret-service`, -`file`, `memory`. The `file` backend additionally requires -`NEWRELIC_CLI_KEYRING_PASSPHRASE`. This routes the *secret store*; it does -NOT change the secret-source rule above. - -- **Ingress only** (`init` / `set-credential`): stdin, `--*-from-env `, - or an interactive no-echo prompt — never a flag/positional literal (§1.5). - `nrq config set-api-key` is removed (hard error → migration message). -- **Installer / non-interactive:** `nrq init --non-interactive` (init-only) - fails loud instead of prompting for any missing value, incl. the - file-backend passphrase (policy threaded via `keychain.OpenForInit`). - `--account-id-from-env ` is the non-secret twin of - `--api-key-from-env` — same `op→env→--*-from-env` channel, but resolves - into `config.yml`, never the keyring (§2.5). `nrq me` is the scripted - verify (`opts.APIClient()` → `TestConnection`; non-zero on bad key / - inaccessible configured account; never prints the key). -- **Runtime resolution:** API key from the keyring only (the single lazy - chokepoint is `root.Options.APIClient`, which also runs the one-time §1.8 - migration). `account_id`/`region`: env > config.yml. -- **§1.8 migration:** first run moves a legacy macOS Keychain entry (service - `newrelic-cli`) and/or the legacy `~/.config/newrelic-cli/credentials` file - → api_key to the keyring, account_id/region into config.yml, legacy - originals deleted. Divergent legacy secret values fail loudly (never a - silent pick; never the value, masked or not). The one-time signal is a - stderr line, emitted synchronously during the migration itself. -- Tests use `internal/testutil` (hermetic file backend, no real keyring) and - `internal/noleak` (the §1.12/§1.11 acceptance suite). - -## Environment Variables - -| Variable | Description | -|----------|-------------| -| `NEWRELIC_API_KEY` | User API key (NRAK-xxx) — **setup ingress only** (`init`/`set-credential` `--from-env`); not read at runtime | -| `NEWRELIC_ACCOUNT_ID` | Account ID — non-secret runtime override (env > config.yml); also the installer ingress target for `nrq init --account-id-from-env NEWRELIC_ACCOUNT_ID` (resolved into `config.yml`, never the keyring) | -| `NEWRELIC_REGION` | US or EU (non-secret runtime override; env > config.yml) | -| `NEWRELIC_CLI_KEYRING_BACKEND` | Backend selector — one of `keychain`, `wincred`, `secret-service`, `file`, `memory` (§1.4). Lower precedence than `--backend`, higher than `keyring.backend` config. | -| `NEWRELIC_CLI_KEYRING_PASSPHRASE` | File-backend passphrase for headless use (§1.4) | - -## Dependencies - -Key dependencies: -- `github.com/spf13/cobra` - CLI framework -- `github.com/fatih/color` - Colored terminal output -- `github.com/stretchr/testify` - Testing assertions +This file is an index. Keep New Relic-specific guidance in +`docs/development.md` and shared conventions in the canonical repositories +above. diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..3e964c8 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,141 @@ +# newrelic-cli Development Guide + +This is the repo-local guide for New Relic-specific facts. Shared Open CLI +Collective standards and automation remain canonical in their own repositories. + +## Project Overview + +newrelic-cli builds the `nrq` command-line interface for New Relic. It uses Cobra +for commands and exposes a public `api/` package for Go callers. + +`nrq` covers APM applications, alerts, dashboards, deployments, entities, log +parsing rules, NerdGraph, NRQL, synthetic monitors, users, API keys, and +connection checks. + +## Quick Commands + +```bash +make build # build ./nrq from ./cmd/nrq +make test # run tests with race detection +make test-cover # run tests with coverage output +make lint # run golangci-lint +make fmt # go fmt ./... +make verify # fmt + lint + test +make install # move nrq to /usr/local/bin +make clean # remove local build artifacts +``` + +## Repo Structure + +```text +newrelic-cli/ +├── cmd/nrq/main.go +├── api/ # public New Relic API client package +├── internal/ +│ ├── cmd/ # Cobra commands by resource +│ ├── config/ # non-secret config +│ ├── keychain/ # cli-common credstore adapter and legacy migration +│ ├── version/ # build-time version injection +│ └── view/ # text/table rendering helpers +├── Makefile +└── go.mod +``` + +## Command Patterns + +- Commands use options structs for dependency injection. +- Resource command packages expose `Register` functions. +- New command packages live under `internal/cmd//`. +- Add API methods in `api/` when a command needs new New Relic API coverage. +- Use dedicated API identifier types such as `EntityGUID`, `APIKey`, and + `AccountID` at input/config boundaries. + +## Output Contract + +Resource reads emit text output only through table/plain rendering. JSON is +reserved for local control-plane envelopes such as `set-credential`, +`config show`, and `config test`, plus passthrough surfaces such as `nerdgraph` +and `nrql`. + +Shared output policy: + +```md +Source of truth: https://github.com/open-cli-collective/cli-common/blob/main/docs/output-and-rendering.md +Local convenience copy, if present: `../cli-common/docs/output-and-rendering.md` +``` + +## Credentials And Config + +The New Relic API key is stored in the OS keyring through +`cli-common/credstore` under ref `newrelic-cli/default`, key `api_key`. +It is not stored in plaintext, not stored in `config.yml`, and not read from the +environment at runtime. + +Non-secret `account_id` and `region` live in +`~/.config/newrelic-cli/config.yml` alongside `credential_ref` and optional +`keyring.backend`. + +Credential ingress is through `nrq init` or `nrq set-credential`, using stdin, +`--from-env` style inputs, or interactive no-echo prompts. `nrq me` is the +scripted verification command for key/account access. + +Shared credential, state, and scriptability policy: + +```md +Source of truth: https://github.com/open-cli-collective/cli-common/blob/main/docs/working-with-secrets.md +Local convenience copy, if present: `../cli-common/docs/working-with-secrets.md` + +Source of truth: https://github.com/open-cli-collective/cli-common/blob/main/docs/working-with-state.md +Local convenience copy, if present: `../cli-common/docs/working-with-state.md` + +Source of truth: https://github.com/open-cli-collective/cli-common/blob/main/docs/scriptability.md +Local convenience copy, if present: `../cli-common/docs/scriptability.md` +``` + +## Environment Variables + +- `NEWRELIC_API_KEY` - setup ingress only for `init` / `set-credential` + `--from-env`; not read at runtime. +- `NEWRELIC_ACCOUNT_ID` - non-secret runtime override and init ingress target. +- `NEWRELIC_REGION` - non-secret runtime override. +- `NEWRELIC_CLI_KEYRING_BACKEND` - backend selector. +- `NEWRELIC_CLI_KEYRING_PASSPHRASE` - file-backend passphrase for headless use. + +## Shared Repo Standards + +Use these sources for shared repository policy. Do not copy their mechanics into +this guide. + +```md +Source of truth: https://github.com/open-cli-collective/cli-common/blob/main/docs/command-surface.md +Local convenience copy, if present: `../cli-common/docs/command-surface.md` + +Source of truth: https://github.com/open-cli-collective/cli-common/blob/main/docs/repo-layout.md +Local convenience copy, if present: `../cli-common/docs/repo-layout.md` + +Source of truth: https://github.com/open-cli-collective/cli-common/blob/main/docs/ci.md +Local convenience copy, if present: `../cli-common/docs/ci.md` + +Source of truth: https://github.com/open-cli-collective/cli-common/blob/main/docs/release.md +Local convenience copy, if present: `../cli-common/docs/release.md` + +Source of truth: https://github.com/open-cli-collective/cli-common/blob/main/docs/distribution.md +Local convenience copy, if present: `../cli-common/docs/distribution.md` +``` + +## Shared Automation + +Use `open-cli-collective/.github` for shared action and reusable workflow +implementations. + +```md +Source of truth: https://github.com/open-cli-collective/.github +Local convenience copy, if present: `../.github` +``` + +## Dependencies + +- `github.com/open-cli-collective/cli-common` - shared credential storage. +- `github.com/spf13/cobra` - command framework. +- `github.com/fatih/color` - terminal color support. +- `github.com/stretchr/testify` - test assertions.