Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Use the official product names in all user-facing text (documentation, code comm
- **`cmd/tiger/`** - The main CLI binary. Sets up signal handling (SIGINT/SIGTERM cancel the command context), delegates to `cmd.Execute()`, and maps a `common.ExitCodeError` found anywhere in the returned error chain to the process exit code.
- **`cmd/generate-docs/`** - Renders the CLI reference in `docs/cli/` from the command tree. Runs under `go generate` (see [CLI Reference Docs](#cli-reference-docs)).
- **`internal/`** - All core application logic (non-public Go packages).
- **`internal/cmd/`** - Cobra command implementations for all CLI commands (auth, project, service, db, config, mcp, feedback, version, upgrade, completion). Each command lives in its own file, named to match the command in snake_case (see [Command Architecture](#command-architecture)). `root.go` holds the root command, global flags, and command tree construction; files ending in `_helper.go` contain cross-group helpers rather than commands.
- **`internal/cmd/`** - Cobra command implementations for all CLI commands (auth, project, service, db, config, mcp, skills, feedback, version, upgrade, completion). Each command lives in its own file, named to match the command in snake_case (see [Command Architecture](#command-architecture)). `root.go` holds the root command, global flags, and command tree construction; files ending in `_helper.go` contain cross-group helpers rather than commands.
- **`internal/api/`** - API client layer. Includes the OpenAPI-generated REST client (`client.go`, `types.go`), the shared HTTP client (`client_util.go`), and a generated mock of the client interface for testing (`mocks/`). **Never edit the generated files by hand** — see [Code Generation](#code-generation).
- **`internal/config/`** - Configuration management. Handles config file loading (via Viper), the `Config` struct and its write helpers, and credential storage (keyring with a file fallback in the config directory).
- **`internal/common/`** - Shared business logic used by both CLI commands and MCP tools: the per-invocation `App` (config + API client), database password generation and storage (keyring, pgpass), wait/polling operations and the spinner, connection detail helpers (including read replicas), service readiness checks, query execution, log fetching, and error handling with exit codes.
Expand Down Expand Up @@ -158,7 +158,7 @@ Helper functions inside `internal/cmd` take the `*cobra.Command` and print throu

- Commands with structured output take `-o`/`--output` with `json`, `yaml`, and `table` (the default); some commands support extras (`env`, `bare`). Register the flag with the validating flag types in `flag_helper.go` (`new(outputFlag)` and friends, with no bound variable) and read the value from `cfg.Output` — `output` is a config value.
- Serialize with `util.SerializeToJSON` and `util.SerializeToYAML`. **Don't add `yaml:` struct tags to output types** — `SerializeToYAML` encodes to JSON first and converts, so only `json:` tags matter and the two formats stay consistent (including for generated types that only carry `json:` tags).
- Colored output uses lipgloss styles, restricted to the base ANSI colors (`lipgloss.Red`, `lipgloss.Cyan`, …) rather than 256-color or true-color values, which can clash with some terminal themes. Render styles unconditionally: the output writers strip colors when the stream isn't a terminal, and the lifecycle disables color on them when `cfg.Color` is off, so commands need no per-command wiring.
- Colored output uses lipgloss styles, restricted to the eight base ANSI colors (`lipgloss.Blue`, `lipgloss.Green`, …) rather than their bright variants or 256-color and true-color values, and without bold; de-emphasize text with `Faint` rather than a grey. That way output follows the user's terminal theme (some themes render bright colors and bold as greys). Render styles unconditionally: the output writers strip colors when the stream isn't a terminal, and the lifecycle disables color on them when `cfg.Color` is off, so commands need no per-command wiring. The `tiger skills install` picker is the model.

### Tone

Expand All @@ -170,7 +170,7 @@ Warnings and errors follow the same standard. A non-fatal failure prints to stde

Read user input through the helpers in `internal/util/read.go` — `util.ReadLine` (one trimmed line), `util.ReadPassword` (no echo, saves and restores terminal state), and `util.ReadAll` (everything, for piped input) — never by driving `bufio`/`term` directly. All three select on `ctx.Done()`, so Ctrl-C unblocks a waiting prompt instead of hanging.

**Every read that prompts the user in real time must be gated on `util.IsTerminal(cmd.InOrStdin()) && util.IsTerminal(cmd.ErrOrStderr())`** — both streams, so the user can both see the question and answer it — and must return an error naming that command's non-interactive alternative when either check fails (e.g. "use --confirm to skip the prompt", or "use --new-password, --auto-generate, or TIGER_NEW_PASSWORD"). Put the gate immediately before the prompt in the command body, not inside a shared helper, so the error can name the right flag or env var. A prompt that merely *offers* something skips silently instead of erroring, since nothing is being refused — `offerProdProtection` in `auth_login.go` is the one example. The gate is about *prompts*, not reading stdin as such — a read whose whole point is piped input (`util.ReadAll` on a here-doc or a `|`) must not be gated.
**Every read that prompts the user in real time must be gated on `util.IsTerminal(cmd.InOrStdin()) && util.IsTerminal(cmd.ErrOrStderr())`** — both streams, so the user can both see the question and answer it — and must return an error naming that command's non-interactive alternative when either check fails (e.g. "use --confirm to skip the prompt", or "use --new-password, --auto-generate, or TIGER_NEW_PASSWORD"). Put the gate just before the prompt, in the command body or a helper used only by that command — never in a helper shared across commands — so the error can name the right flag or env var. A prompt that merely *offers* something skips silently instead of erroring, since nothing is being refused — `offerProdProtection` in `auth_login.go` is the one example. The gate is about *prompts*, not reading stdin as such — a read whose whole point is piped input (`util.ReadAll` on a here-doc or a `|`) must not be gated.

`util.IsTerminal` and `util.ReadPassword` are `var`s so tests can replace them (the `withIsTerminal` and `withReadPassword` options in `main_test.go`). `ReadPassword` needs stdin to be a real `*os.File`, so password prompts can't be driven with `cmd.SetIn`; `ReadLine`/`ReadAll` take any `io.Reader` and work fine with it.

Expand All @@ -190,6 +190,7 @@ Interactive menus and spinners use BubbleTea v2 (`charm.land/bubbletea/v2`). Alw
- Error strings start with a **lowercase** letter, so they read correctly when a caller wraps them (`fmt.Errorf("failed to get service: %w", err)`). A leading proper noun or initialism keeps its capitalization (`API key validation failed`); if that reads awkwardly, reword so the identifier isn't first (`missing required option: ClientName`). Log messages are the opposite — they start with a capital letter.
- Use `errors.New` for static error strings; use `fmt.Errorf` only when you need format verbs.
- Handle every error — never ignore one or assign it to `_` (at minimum, print a warning).
- Cleanup that can fail (closing a reader, removing a temporary directory) is called explicitly on the success path, with its error returned, and also deferred as best-effort cleanup for the error paths, where a more important error is already being returned. This relies on the cleanup being safe to call twice, so use it only for idempotent operations. `writeSkill` in `skills_install.go` is the model.
- Return errors from `RunE`; never call `os.Exit` in a command. For specific exit codes, wrap with `common.ExitWithCode`, and map API errors to exit codes with `common.ExitWithErrorFromStatusCode`. The exit codes are defined in `internal/common/errors.go`, and `main.go` honors a `common.ExitCodeError` anywhere in the error chain.
- After checking an API response's status code, nil-check the parsed body (`resp.JSON200`, etc.) before using it — a nil body indicates a bug and should return `errors.New("empty response from API")`.
- Operations that connect to a service should call `common.CheckServiceReady` first and translate `common.ErrPaused`/`common.ErrNotReady` into actionable messages.
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ tiger db query -c "SELECT now()"

# Install the MCP server
tiger mcp install

# Install agent skills for your coding agent
tiger skills install
```

## Usage
Expand Down Expand Up @@ -135,6 +138,8 @@ Tiger CLI provides the following commands:
- `start` - Start the MCP server
- `list` - List available MCP tools, prompts, and resources (alias: `ls`)
- `get` - Get detailed information about a specific MCP capability (aliases: `describe`, `show`)
- `tiger skills` - Agent skills for AI coding agents
- `install` - Install PostgreSQL and TimescaleDB skills for AI coding agents (alias: `add`)
- `tiger feedback` - Submit feedback or a bug report to the Tiger Data team (pass the message as an argument or on stdin)
- `tiger version` - Show version information
- `tiger upgrade` - Upgrade the Tiger CLI to the latest version (alias: `update`)
Expand Down Expand Up @@ -247,6 +252,33 @@ To disable the documentation proxy:
tiger config set docs_mcp false
```

## Agent Skills

Tiger CLI can install curated PostgreSQL and TimescaleDB skills directly into
your coding agents:

```bash
# Interactive installation (prompts for install locations)
tiger skills install

# Or name the locations directly
tiger skills install universal claude-code
```

Skills are installed for the current user. The `universal` location,
`~/.agents/skills`, is read by most coding agents (Cursor, Codex, Gemini CLI,
VS Code, GitHub Copilot CLI, Devin). Claude Code, Kiro CLI, and Google
Antigravity read their own skills directories, so they're separate locations
(`claude-code`, `kiro-cli`, `antigravity`). Naming a client that reads
`~/.agents/skills`, such as `cursor`, selects `universal`. Use `--skills-dir`
to install into a directory of your choice instead.

Re-run the command to update the skills to the latest version; skills that are
no longer available are removed. With no arguments, the locations you've
installed to before are selected in the picker to start with. Existing skills
that weren't installed by Tiger CLI are never replaced unless you pass
`--force`.

## Configuration

The CLI stores configuration in `~/.config/tiger/config.yaml` by default, and supports hierarchical configuration through environment variables and command-line flags.
Expand Down
1 change: 1 addition & 0 deletions docs/cli/tiger.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions docs/cli/tiger_skills.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 65 additions & 0 deletions docs/cli/tiger_skills_install.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Tiger CLI is a Go-based command-line interface for managing Tiger resources. The

- **Entry Point**: `cmd/tiger/main.go` - Simple main that delegates to cmd.Execute()
- **Command Structure**: `internal/cmd/` - Cobra-based command definitions for all
CLI commands (auth, project, service, db, config, mcp, version, upgrade). Each command
CLI commands (auth, project, service, db, config, mcp, skills, version, upgrade). Each command
lives in its own file, named to match the command in snake_case
(`tiger service create` → `service_create.go`). `root.go` holds the root
command, global flags, and `wrapCommands`, which gives every command the same
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ tool (
)

require (
charm.land/bubbletea/v2 v2.0.9
charm.land/bubbletea/v2 v2.1.0
charm.land/lipgloss/v2 v2.0.6
github.com/Masterminds/semver/v3 v3.5.0
github.com/charmbracelet/colorprofile v0.4.3
github.com/charmbracelet/x/ansi v0.11.9
github.com/cli/safeexec v1.0.1
github.com/docker/go-units v0.5.0
github.com/google/go-cmp v0.7.0
Expand All @@ -40,8 +41,7 @@ require (
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260903151058-ae99b731b8c5 // indirect
github.com/charmbracelet/x/ansi v0.11.8 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20261008173134-6b8d4baf91b4 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
Expand All @@ -65,7 +65,7 @@ require (
github.com/lucasb-eyer/go-colorful v1.4.1 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/mattn/go-runewidth v0.0.29 // indirect
github.com/mattn/go-runewidth v0.0.30 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/oapi-codegen/oapi-codegen/v2 v2.8.0 // indirect
github.com/oasdiff/yaml v0.1.1 // indirect
Expand Down
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
charm.land/bubbletea/v2 v2.0.9 h1:DpJCMWKgzQK8SJv4zbKKFHAI10ymWy/evClPFk0k0f8=
charm.land/bubbletea/v2 v2.0.9/go.mod h1:2SkdgoTXluXJHOUwAoRlRXF/28vklb1rFl6GcgV1/ss=
charm.land/bubbletea/v2 v2.1.0 h1:A9fj9h5LFrvQhQHFNggp8K8rvY6CUfgn9dMbb6p2HIw=
charm.land/bubbletea/v2 v2.1.0/go.mod h1:xWHeyxIX9/wiFU2kgPW04ojOZyTzrWf+8lGjUCaftiU=
charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ=
charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
Expand All @@ -16,10 +16,10 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/ultraviolet v0.0.0-20260903151058-ae99b731b8c5 h1:omidyaLW3GwtN5N35bPZWCxygynfhMRO2dTJ+OCcjLg=
github.com/charmbracelet/ultraviolet v0.0.0-20260903151058-ae99b731b8c5/go.mod h1:nAw0d9PhFp1qdzi2xhQU5YOu5sVpDIHWlaW2Uz/bCro=
github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ=
github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0=
github.com/charmbracelet/ultraviolet v0.0.0-20261008173134-6b8d4baf91b4 h1:hISLzUbwcvJbGoNR6A3L/TpguRRnACBmZOee8LcHLJw=
github.com/charmbracelet/ultraviolet v0.0.0-20261008173134-6b8d4baf91b4/go.mod h1:aJDVk+WOVM1K04rdc/71PMPoUGfjxjhvXfP3Pjz1Zio=
github.com/charmbracelet/x/ansi v0.11.9 h1:kIrqLBxtTVbLyyHO2Evoi93EjQo1mylWmpNEsfPxXAM=
github.com/charmbracelet/x/ansi v0.11.9/go.mod h1:DG/hpOnaI2QJ/KMMqLL2h0KVhluuJLazf8973P+Vy5w=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
Expand Down Expand Up @@ -125,8 +125,8 @@ github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/mattn/go-runewidth v0.0.29 h1:3oGF3R/S2N9DQ3ptftzVIvg2eicmojCzlwBEmqEPDfQ=
github.com/mattn/go-runewidth v0.0.29/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
github.com/mattn/go-runewidth v0.0.30 h1:+KUuiDA4fF0R1p5FeueHefjDm+GIM+kWfFnDjybOPgk=
github.com/mattn/go-runewidth v0.0.30/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
github.com/modelcontextprotocol/go-sdk v1.8.0 h1:KIvahhYqwtbeniWVPs3TcXEA7b8jEtwfBpOTAI+Urx4=
github.com/modelcontextprotocol/go-sdk v1.8.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
Expand Down
Loading
Loading