diff --git a/CLAUDE.md b/CLAUDE.md index 8fd89d1..31c8570 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ - **`internal/api/`** - API client layer. Includes an OpenAPI-generated REST client (`client.go`, `types.go`), shared HTTP client singleton, and request/response types. **Do not edit `client.go` or `types.go` by hand** — they are generated from `openapi.yaml` (see [Code Generation](#code-generation)). The `mock/` subdirectory contains a generated mock of `ClientWithResponsesInterface` for use in tests. - **`internal/config/`** - Configuration management. Handles config file loading (via Viper), credential storage (keyring with file fallback), and version checking. - **`internal/common/`** - Shared business logic used across commands and MCP tools. Includes API client initialization, database connection/schema/query utilities, error handling with exit codes, and version update checks. - - **`internal/mcp/`** - Model Context Protocol (MCP) server. Exposes Ghost database operations as MCP tools for AI/LLM integration, plus a documentation search proxy. Each MCP tool lives in its own file, named to match the tool (e.g. `ghost_status` → `status.go`). Helper files like `util.go`, `errors.go`, and `proxy.go` contain shared utilities. + - **`internal/mcp/`** - Model Context Protocol (MCP) server. Exposes Ghost database operations as MCP tools for AI/LLM integration, plus a documentation search proxy. Each MCP tool lives in its own file, named to match the tool (e.g. `ghost_usage` → `usage.go`). Helper files like `util.go`, `errors.go`, and `proxy.go` contain shared utilities. - **`internal/analytics/`** - Analytics event tracking with sensitive data redaction for flags, positional arguments, and MCP inputs. - **`internal/util/`** - General utilities: type conversion, duration formatting, path helpers, context-aware stdin reading, JSON/YAML serialization, and terminal detection. - **`docs/`** - Documentation. `docs/cli/` contains generated Markdown CLI reference docs (produced by `cmd/generate-docs`). diff --git a/README.md b/README.md index 7501724..01dd1f2 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ ghost list # List all databases | `schema` | Display database schema information | | `share` | Share a database | | `sql` | Execute SQL query on a database | -| `status` | Show space usage | +| `usage` | Show space usage | | `upgrade` | Upgrade the ghost CLI to the latest version | | `version` | Show version information | @@ -118,7 +118,7 @@ The `ghost mcp` command installs a [Model Context Protocol](https://modelcontext | `ghost_share_list` | List database shares | | `ghost_share_revoke` | Revoke a database share | | `ghost_sql` | Execute a SQL query against a database | -| `ghost_status` | Show space usage | +| `ghost_usage` | Show space usage | | `search_docs` | Search PostgreSQL, PostGIS, and TimescaleDB documentation | | `view_skill` | Retrieve skills for PostgreSQL and TimescaleDB best practices | diff --git a/docs/cli/ghost.md b/docs/cli/ghost.md index a484182..42f760b 100644 --- a/docs/cli/ghost.md +++ b/docs/cli/ghost.md @@ -48,6 +48,6 @@ Ghost is a command-line interface for managing PostgreSQL databases. * [ghost schema](ghost_schema.md) - Display database schema information * [ghost share](ghost_share.md) - Share a database * [ghost sql](ghost_sql.md) - Execute SQL query on a database -* [ghost status](ghost_status.md) - Show space usage * [ghost upgrade](ghost_upgrade.md) - Upgrade the ghost CLI to the latest version +* [ghost usage](ghost_usage.md) - Show space usage * [ghost version](ghost_version.md) - Show version information diff --git a/docs/cli/ghost_status.md b/docs/cli/ghost_usage.md similarity index 74% rename from docs/cli/ghost_status.md rename to docs/cli/ghost_usage.md index a943cd8..ac1a263 100644 --- a/docs/cli/ghost_status.md +++ b/docs/cli/ghost_usage.md @@ -1,34 +1,34 @@ --- -title: "ghost status" -slug: "ghost_status" -description: "CLI reference for ghost status" +title: "ghost usage" +slug: "ghost_usage" +description: "CLI reference for ghost usage" --- -## ghost status +## ghost usage Show space usage ``` -ghost status [flags] +ghost usage [flags] ``` ### Examples ``` # Show space usage - ghost status + ghost usage # Output as JSON - ghost status --json + ghost usage --json # Output as YAML - ghost status --yaml + ghost usage --yaml ``` ### Options ``` - -h, --help help for status + -h, --help help for usage --json Output in JSON format --yaml Output in YAML format ``` diff --git a/internal/cmd/mcp_list_test.go b/internal/cmd/mcp_list_test.go index dbe1343..01e9463 100644 --- a/internal/cmd/mcp_list_test.go +++ b/internal/cmd/mcp_list_test.go @@ -43,7 +43,7 @@ func TestMCPListCmd(t *testing.T) { "tool ghost_share_list \n" + "tool ghost_share_revoke \n" + "tool ghost_sql \n" + - "tool ghost_status \n" + + "tool ghost_usage \n" + "tool search_docs \n" + "tool view_skill \n", }, diff --git a/internal/cmd/root.go b/internal/cmd/root.go index e8caec6..53a2bd2 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -108,7 +108,7 @@ func buildRootCmd() (*cobra.Command, *common.App, error) { cmd.AddCommand(buildCreateCmd(app)) cmd.AddCommand(buildForkCmd(app)) cmd.AddCommand(buildListCmd(app)) - cmd.AddCommand(buildStatusCmd(app)) + cmd.AddCommand(buildUsageCmd(app)) cmd.AddCommand(buildDeleteCmd(app)) cmd.AddCommand(buildPauseCmd(app)) cmd.AddCommand(buildResumeCmd(app)) diff --git a/internal/cmd/status.go b/internal/cmd/usage.go similarity index 92% rename from internal/cmd/status.go rename to internal/cmd/usage.go index 83701ad..ca569d9 100644 --- a/internal/cmd/status.go +++ b/internal/cmd/usage.go @@ -10,22 +10,22 @@ import ( "github.com/timescale/ghost/internal/util" ) -func buildStatusCmd(app *common.App) *cobra.Command { +func buildUsageCmd(app *common.App) *cobra.Command { var jsonOutput bool var yamlOutput bool cmd := &cobra.Command{ - Use: "status", - Aliases: []string{"usage"}, + Use: "usage", + Aliases: []string{"status"}, Short: "Show space usage", Example: ` # Show space usage - ghost status + ghost usage # Output as JSON - ghost status --json + ghost usage --json # Output as YAML - ghost status --yaml`, + ghost usage --yaml`, Args: cobra.NoArgs, ValidArgsFunction: cobra.NoFileCompletions, SilenceUsage: true, @@ -46,7 +46,7 @@ func buildStatusCmd(app *common.App) *cobra.Command { case yamlOutput: return util.SerializeToYAML(cmd.OutOrStdout(), status) default: - outputStatus(cmd, status) + outputUsage(cmd, status) return nil } }, @@ -59,7 +59,7 @@ func buildStatusCmd(app *common.App) *cobra.Command { return cmd } -func outputStatus(cmd *cobra.Command, status common.Status) { +func outputUsage(cmd *cobra.Command, status common.Status) { computeHours := float64(status.ComputeMinutes) / 60 computeLimitHours := float64(status.ComputeLimitMinutes) / 60 computePercent := float64(status.ComputeMinutes) / float64(status.ComputeLimitMinutes) * 100 diff --git a/internal/cmd/status_test.go b/internal/cmd/usage_test.go similarity index 94% rename from internal/cmd/status_test.go rename to internal/cmd/usage_test.go index 76ead37..32e306d 100644 --- a/internal/cmd/status_test.go +++ b/internal/cmd/usage_test.go @@ -9,7 +9,7 @@ import ( "github.com/timescale/ghost/internal/api/mock" ) -func TestStatusCmd(t *testing.T) { +func TestUsageCmd(t *testing.T) { successSetup := func(m *mock.MockClientWithResponsesInterface) { m.EXPECT().SpaceStatusWithResponse(validCtx, "test-project"). Return(&api.SpaceStatusResponse{ @@ -39,13 +39,13 @@ func TestStatusCmd(t *testing.T) { tests := []cmdTest{ { name: "not logged in", - args: []string{"status"}, + args: []string{"usage"}, opts: []runOption{withClientError(errors.New("authentication required: no credentials found"))}, wantErr: "authentication required: no credentials found", }, { name: "network error on space status", - args: []string{"status"}, + args: []string{"usage"}, setup: func(m *mock.MockClientWithResponsesInterface) { m.EXPECT().SpaceStatusWithResponse(validCtx, "test-project"). Return(nil, errors.New("connection refused")) @@ -60,7 +60,7 @@ func TestStatusCmd(t *testing.T) { }, { name: "API error on space status", - args: []string{"status"}, + args: []string{"usage"}, setup: func(m *mock.MockClientWithResponsesInterface) { m.EXPECT().SpaceStatusWithResponse(validCtx, "test-project"). Return(&api.SpaceStatusResponse{ @@ -78,7 +78,7 @@ func TestStatusCmd(t *testing.T) { }, { name: "nil space status response body", - args: []string{"status"}, + args: []string{"usage"}, setup: func(m *mock.MockClientWithResponsesInterface) { m.EXPECT().SpaceStatusWithResponse(validCtx, "test-project"). Return(&api.SpaceStatusResponse{ @@ -96,7 +96,7 @@ func TestStatusCmd(t *testing.T) { }, { name: "nil list databases response body", - args: []string{"status"}, + args: []string{"usage"}, setup: func(m *mock.MockClientWithResponsesInterface) { m.EXPECT().SpaceStatusWithResponse(validCtx, "test-project"). Return(&api.SpaceStatusResponse{ @@ -118,7 +118,7 @@ func TestStatusCmd(t *testing.T) { }, { name: "text output", - args: []string{"status"}, + args: []string{"usage"}, setup: successSetup, wantStdout: `Space: test-project Compute: 2/10 hours (20%) @@ -128,7 +128,7 @@ Databases: 2 (1 running, 1 paused) }, { name: "json output", - args: []string{"status", "--json"}, + args: []string{"usage", "--json"}, setup: successSetup, wantStdout: `{ "compute_minutes": 120, @@ -145,7 +145,7 @@ Databases: 2 (1 running, 1 paused) }, { name: "yaml output", - args: []string{"status", "--yaml"}, + args: []string{"usage", "--yaml"}, setup: successSetup, wantStdout: `compute_limit_minutes: 600 compute_minutes: 120 @@ -158,8 +158,8 @@ storage_mib: 512 `, }, { - name: "usage alias", - args: []string{"usage"}, + name: "status alias", + args: []string{"status"}, setup: successSetup, wantStdout: `Space: test-project Compute: 2/10 hours (20%) @@ -169,7 +169,7 @@ Databases: 2 (1 running, 1 paused) }, { name: "text output with cost", - args: []string{"status"}, + args: []string{"usage"}, setup: func(m *mock.MockClientWithResponsesInterface) { m.EXPECT().SpaceStatusWithResponse(validCtx, "test-project"). Return(&api.SpaceStatusResponse{ @@ -199,7 +199,7 @@ Cost: $12.34 so far this cycle ($27.50 estimated total) }, { name: "text output with zero cost is omitted", - args: []string{"status"}, + args: []string{"usage"}, setup: func(m *mock.MockClientWithResponsesInterface) { m.EXPECT().SpaceStatusWithResponse(validCtx, "test-project"). Return(&api.SpaceStatusResponse{ diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 32bd112..740fa5d 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -111,7 +111,7 @@ func (s *Server) registerTools(ctx context.Context) { mcp.AddTool(s.mcpServer, newLoginTool(), s.handleLogin) // Register Ghost database tools - mcp.AddTool(s.mcpServer, newStatusTool(), s.handleStatus) + mcp.AddTool(s.mcpServer, newUsageTool(), s.handleUsage) mcp.AddTool(s.mcpServer, newListTool(), s.handleList) mcp.AddTool(s.mcpServer, newCreateTool(), s.handleCreate) mcp.AddTool(s.mcpServer, newDeleteTool(), s.handleDelete) diff --git a/internal/mcp/status.go b/internal/mcp/usage.go similarity index 78% rename from internal/mcp/status.go rename to internal/mcp/usage.go index 169005f..0d211ce 100644 --- a/internal/mcp/status.go +++ b/internal/mcp/usage.go @@ -11,15 +11,15 @@ import ( "github.com/timescale/ghost/internal/util" ) -// StatusInput represents input for ghost_status (empty - no parameters) -type StatusInput struct{} +// UsageInput represents input for ghost_usage (empty - no parameters) +type UsageInput struct{} -func (StatusInput) Schema() *jsonschema.Schema { - return util.Must(jsonschema.For[StatusInput](nil)) +func (UsageInput) Schema() *jsonschema.Schema { + return util.Must(jsonschema.For[UsageInput](nil)) } -// StatusOutput represents output for ghost_status -type StatusOutput struct { +// UsageOutput represents output for ghost_usage +type UsageOutput struct { ComputeMinutes int64 `json:"compute_minutes"` ComputeLimitMinutes int64 `json:"compute_limit_minutes"` Storage string `json:"storage"` @@ -31,8 +31,8 @@ type StatusOutput struct { BillingPeriodEnd *time.Time `json:"billing_period_end,omitempty"` } -func (StatusOutput) Schema() *jsonschema.Schema { - schema := util.Must(jsonschema.For[StatusOutput](nil)) +func (UsageOutput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[UsageOutput](nil)) schema.Properties["compute_minutes"].Description = "Current compute usage in minutes" schema.Properties["compute_limit_minutes"].Description = "Compute limit in minutes" schema.Properties["storage"].Description = "Current storage usage" @@ -47,13 +47,13 @@ func (StatusOutput) Schema() *jsonschema.Schema { return schema } -func newStatusTool() *mcp.Tool { +func newUsageTool() *mcp.Tool { return &mcp.Tool{ - Name: "ghost_status", + Name: "ghost_usage", Title: "Show Space Usage", Description: "Display database space usage including compute minutes, storage, and database counts by status.", - InputSchema: StatusInput{}.Schema(), - OutputSchema: StatusOutput{}.Schema(), + InputSchema: UsageInput{}.Schema(), + OutputSchema: UsageOutput{}.Schema(), Annotations: &mcp.ToolAnnotations{ ReadOnlyHint: true, OpenWorldHint: new(true), @@ -62,18 +62,18 @@ func newStatusTool() *mcp.Tool { } } -func (s *Server) handleStatus(ctx context.Context, req *mcp.CallToolRequest, input StatusInput) (*mcp.CallToolResult, StatusOutput, error) { +func (s *Server) handleUsage(ctx context.Context, req *mcp.CallToolRequest, input UsageInput) (*mcp.CallToolResult, UsageOutput, error) { client, projectID, err := s.app.GetClient() if err != nil { - return nil, StatusOutput{}, err + return nil, UsageOutput{}, err } status, err := common.FetchStatus(ctx, client, projectID) if err != nil { - return nil, StatusOutput{}, err + return nil, UsageOutput{}, err } - return nil, StatusOutput{ + return nil, UsageOutput{ ComputeMinutes: status.ComputeMinutes, ComputeLimitMinutes: status.ComputeLimitMinutes, Storage: common.FormatStorageSize(new(int(status.StorageMib))),