diff --git a/.claude/skills/quartr/SKILL.md b/.claude/skills/quartr/SKILL.md index 1b5260a..d052ffd 100644 --- a/.claude/skills/quartr/SKILL.md +++ b/.claude/skills/quartr/SKILL.md @@ -29,10 +29,19 @@ Precedence (highest to lowest): 2. `QUARTR_API_KEY` env var 3. `~/.config/quartr/config.json` (written 0600 by `quartr auth login`) -For durable use: +For durable use, run `quartr auth login` once with `QUARTR_API_KEY` exported — +the command picks up the env var via the standard precedence and persists it +to `~/.config/quartr/config.json`. Avoid `--api-key VALUE` on `auth login`: +the literal key ends up in shell history, `ps`, scrollback, and CI logs. +When piping from a secret store, use `--api-key-stdin`: ```bash -quartr auth login --api-key "$QUARTR_API_KEY" +# Idiomatic +export QUARTR_API_KEY=... +quartr auth login + +# Pipe from a secret store +op read op://Personal/Quartr/api_key | quartr auth login --api-key-stdin ``` For project-scoped use with a `.env` file: diff --git a/Makefile b/Makefile index 60c53a6..e4028dd 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ build: install: go install ./cmd/quartr - @if [ -n "$$QUARTR_API_KEY" ]; then $(GOBIN)/quartr auth login --api-key "$$QUARTR_API_KEY"; fi + @if [ -n "$$QUARTR_API_KEY" ]; then printf '%s' "$$QUARTR_API_KEY" | $(GOBIN)/quartr auth login --api-key-stdin; fi test: go test ./... diff --git a/README.md b/README.md index 4e4fcbb..ff6b9d0 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,22 @@ export QUARTR_API_KEY="your-api-key" Or store it locally: ```bash -quartr auth login --api-key "$QUARTR_API_KEY" +# Reads QUARTR_API_KEY from the environment if set; otherwise prompts. +quartr auth login quartr auth show ``` +The key is written to `~/.config/quartr/config.json` with file mode `0600`. + +For piping the key in (e.g. from a secret store) without exposing it via argv: + +```bash +op read op://Personal/Quartr/api_key | quartr auth login --api-key-stdin +``` + +`--api-key VALUE` is also supported but discouraged for `auth login` because +the value leaks via shell history, `ps`, terminal scrollback, and CI logs. + Config precedence is: 1. global CLI flags diff --git a/internal/cli/handlers.go b/internal/cli/handlers.go index bf68a16..558bb6b 100644 --- a/internal/cli/handlers.go +++ b/internal/cli/handlers.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "io" "net/url" "os" "path/filepath" @@ -23,7 +24,7 @@ func (a *app) handleAuth(args []string) error { switch args[0] { case "login", "set-key": fs := newFlagSet("auth login", a.errOut) - apiKey := fs.String("api-key", "", "Quartr API key") + apiKeyStdin := fs.Bool("api-key-stdin", false, "read API key from stdin (one line, trimmed)") baseURL := fs.String("base-url", a.cfg.BaseURL(), "API base URL") format := fs.String("format", a.cfg.Format(), "default output format") timeout := fs.String("timeout", a.cfg.Timeout(), "default HTTP timeout, e.g. 30s") @@ -31,8 +32,18 @@ func (a *app) handleAuth(args []string) error { return err } - key := strings.TrimSpace(*apiKey) - if key == "" { + // Source priority: --api-key-stdin > resolved config (global flag/env/file) > interactive prompt. + // The global --api-key flag is honored via a.cfg.APIKey() but discouraged for `auth login` + // because the literal value leaks via argv (ps, history, scrollback, CI logs). + var key string + switch { + case *apiKeyStdin: + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + return fmt.Errorf("read api key from stdin: %w", err) + } + key = strings.TrimSpace(line) + default: key = strings.TrimSpace(a.cfg.APIKey()) } if key == "" {