From 81bd0b16593a21542710e231e8f411128709b9a2 Mon Sep 17 00:00:00 2001 From: Jacob Sussmilch Date: Tue, 28 Jul 2026 13:15:43 +1000 Subject: [PATCH 1/3] fix: KEEP-1051 prune stale pages so generated docs are authoritative GenMarkdownTree only writes; it never removes. A page therefore outlives its own command: - kh_protocol.md, kh_protocol_get.md, kh_protocol_list.md - the command was renamed to plugin (cmd/protocol/protocol.go declares Use: "plugin"), so cobra generates kh_plugin*.md and drops protocol from the index, but the old pages stayed - kh_serve.md - serve is Deprecated, so IsAvailableCommand() is false and cobra skips it by design All four kept publishing commands that no longer exist. docs-check could not notice: an orphan's content never changes, so it produces no diff. Clearing kh*.md before generating makes the tree authoritative, so a removal now surfaces as a deletion the check can catch. Only kh*.md is swept - the hand-written guides and the generator sources sit in the same directory. --- docs/generate.go | 31 ++++++++++++++++++++++++ docs/kh_protocol.md | 36 ---------------------------- docs/kh_protocol_get.md | 38 ----------------------------- docs/kh_protocol_list.md | 39 ------------------------------ docs/kh_serve.md | 52 ---------------------------------------- 5 files changed, 31 insertions(+), 165 deletions(-) delete mode 100644 docs/kh_protocol.md delete mode 100644 docs/kh_protocol_get.md delete mode 100644 docs/kh_protocol_list.md delete mode 100644 docs/kh_serve.md diff --git a/docs/generate.go b/docs/generate.go index 4cbb7f3..236c253 100644 --- a/docs/generate.go +++ b/docs/generate.go @@ -4,6 +4,8 @@ package main import ( "log" + "os" + "path/filepath" "github.com/keeperhub/cli/cmd" "github.com/keeperhub/cli/pkg/cmdutil" @@ -16,6 +18,17 @@ import ( // scanned. func main() { + // GenMarkdownTree only writes; it never removes. Without this sweep a page + // survives its own command: renaming `protocol` to `plugin` left three + // kh_protocol*.md behind, and deprecating `serve` left kh_serve.md, all + // still published while cobra had correctly dropped them from the index. + // Their content never changes, so the docs check sees no diff and cannot + // notice. Clearing first makes the generated tree authoritative, so a + // removal shows up as a deletion the check can catch. + if err := pruneGeneratedPages("."); err != nil { + log.Fatal(err) + } + ios := iostreams.System() f := &cmdutil.Factory{AppVersion: "dev", IOStreams: ios} root := cmd.NewRootCmd(f) @@ -24,3 +37,21 @@ func main() { log.Fatal(err) } } + +// pruneGeneratedPages removes the generated command reference from dir. +// +// Only `kh*.md` is touched: the hand-written guides (quickstart.md, +// concepts.md) and the generator's own sources live alongside it and must +// survive. +func pruneGeneratedPages(dir string) error { + matches, err := filepath.Glob(filepath.Join(dir, "kh*.md")) + if err != nil { + return err + } + for _, path := range matches { + if err := os.Remove(path); err != nil { + return err + } + } + return nil +} diff --git a/docs/kh_protocol.md b/docs/kh_protocol.md deleted file mode 100644 index fe47f66..0000000 --- a/docs/kh_protocol.md +++ /dev/null @@ -1,36 +0,0 @@ -## kh protocol - -Browse blockchain protocols - -### Examples - -``` - # List all protocols - kh pr ls - - # Get details for a protocol - kh pr g uniswap -``` - -### Options - -``` - -h, --help help for protocol -``` - -### Options inherited from parent commands - -``` - -H, --host string KeeperHub host (default: app.keeperhub.com) - --jq string Filter JSON output with a jq expression - --json Output as JSON - --no-color Disable color output - -y, --yes Skip confirmation prompts -``` - -### SEE ALSO - -* [kh](kh.md) - KeeperHub CLI -* [kh protocol get](kh_protocol_get.md) - Get a protocol -* [kh protocol list](kh_protocol_list.md) - List blockchain protocols - diff --git a/docs/kh_protocol_get.md b/docs/kh_protocol_get.md deleted file mode 100644 index 1156bbc..0000000 --- a/docs/kh_protocol_get.md +++ /dev/null @@ -1,38 +0,0 @@ -## kh protocol get - -Get a protocol - -``` -kh protocol get [flags] -``` - -### Examples - -``` - # Get protocol reference card - kh pr g uniswap - - # Get protocol details as JSON - kh pr g aave --json -``` - -### Options - -``` - -h, --help help for get -``` - -### Options inherited from parent commands - -``` - -H, --host string KeeperHub host (default: app.keeperhub.com) - --jq string Filter JSON output with a jq expression - --json Output as JSON - --no-color Disable color output - -y, --yes Skip confirmation prompts -``` - -### SEE ALSO - -* [kh protocol](kh_protocol.md) - Browse blockchain protocols - diff --git a/docs/kh_protocol_list.md b/docs/kh_protocol_list.md deleted file mode 100644 index 63f21e9..0000000 --- a/docs/kh_protocol_list.md +++ /dev/null @@ -1,39 +0,0 @@ -## kh protocol list - -List blockchain protocols - -``` -kh protocol list [flags] -``` - -### Examples - -``` - # List all protocols (cached) - kh pr ls - - # Force refresh from API - kh pr ls --refresh -``` - -### Options - -``` - -h, --help help for list - --refresh Bypass local cache and fetch fresh data -``` - -### Options inherited from parent commands - -``` - -H, --host string KeeperHub host (default: app.keeperhub.com) - --jq string Filter JSON output with a jq expression - --json Output as JSON - --no-color Disable color output - -y, --yes Skip confirmation prompts -``` - -### SEE ALSO - -* [kh protocol](kh_protocol.md) - Browse blockchain protocols - diff --git a/docs/kh_serve.md b/docs/kh_serve.md deleted file mode 100644 index ef17364..0000000 --- a/docs/kh_serve.md +++ /dev/null @@ -1,52 +0,0 @@ -## kh serve - -Start a server - -**Deprecated:** Use the remote MCP endpoint instead: - -``` -claude mcp add --transport http keeperhub https://app.keeperhub.com/mcp -``` - -### Synopsis - -Start a KeeperHub server process. - -Currently only MCP stdio mode is supported. When started with --mcp, the -server speaks the Model Context Protocol over stdin/stdout and registers -tools dynamically from the /api/mcp/schemas endpoint at startup. - -All diagnostic output (warnings, errors) is written to stderr. Only -valid JSON-RPC 2.0 messages appear on stdout. - -``` -kh serve [flags] -``` - -### Examples - -``` - # Start an MCP stdio server (for use with Claude, Cursor, etc.) - kh serve --mcp -``` - -### Options - -``` - -h, --help help for serve - --mcp Start MCP stdio server -``` - -### Options inherited from parent commands - -``` - -H, --host string KeeperHub host (default: app.keeperhub.com) - --jq string Filter JSON output with a jq expression - --json Output as JSON - --no-color Disable color output - -y, --yes Skip confirmation prompts -``` - -### SEE ALSO - -* [kh](kh.md) - KeeperHub CLI From ca3e3e90ce7bcefcf4696236593f18cdd6d637eb Mon Sep 17 00:00:00 2001 From: Jacob Sussmilch Date: Tue, 28 Jul 2026 13:16:51 +1000 Subject: [PATCH 2/3] fix: KEEP-1051 drop the version tag from kh wallet link help text The help text stated 'v0.1.0 does not launch a browser session handshake', which the generator publishes verbatim to docs.keeperhub.com. Version tags in published docs go stale silently - the sentence describes current behaviour, not behaviour specific to a release, so it now says so without pinning a version. The internal code comment above it keeps its provenance note; only the published string changes. --- cmd/wallet/link.go | 2 +- docs/kh_wallet_link.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/wallet/link.go b/cmd/wallet/link.go index b7f4c16..309b86e 100644 --- a/cmd/wallet/link.go +++ b/cmd/wallet/link.go @@ -21,7 +21,7 @@ func NewLinkCmd(f *cmdutil.Factory) *cobra.Command { Thin wrapper around ` + "`npx @keeperhub/wallet link`" + `. Requires the KH_SESSION_COOKIE env var set to a valid kh session cookie (sign in at app.keeperhub.com, copy the session cookie, export it). -v0.1.0 does not launch a browser session handshake; this env-var contract matches the npm CLI.`, +This command does not launch a browser session handshake; the env-var contract matches the npm CLI.`, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { if os.Getenv("KH_SESSION_COOKIE") == "" { diff --git a/docs/kh_wallet_link.md b/docs/kh_wallet_link.md index 5582a8e..9356a7f 100644 --- a/docs/kh_wallet_link.md +++ b/docs/kh_wallet_link.md @@ -9,7 +9,7 @@ Link the current agentic wallet to your KeeperHub account by calling POST /api/a Thin wrapper around `npx @keeperhub/wallet link`. Requires the KH_SESSION_COOKIE env var set to a valid kh session cookie (sign in at app.keeperhub.com, copy the session cookie, export it). -v0.1.0 does not launch a browser session handshake; this env-var contract matches the npm CLI. +This command does not launch a browser session handshake; the env-var contract matches the npm CLI. ``` kh wallet link [flags] From f1167cdc872e9d62ef61e8ddaac110578dcd219d Mon Sep 17 00:00:00 2001 From: Jacob Sussmilch Date: Tue, 28 Jul 2026 13:19:42 +1000 Subject: [PATCH 3/3] refactor: KEEP-1051 rename cmd/protocol to cmd/plugin to match the command The package declares Use: "plugin" - it was renamed at some point and the directory never followed. That mismatch is what let the stale kh_protocol*.md pages go unnoticed: the generated pages were named after the command, the source after its former name, so neither looked wrong on its own. Renames the directory, protocol.go, the package, and NewProtocolCmd -> NewPluginCmd, plus the cross-reference in cmd/serve/schemas.go. No behaviour change and no generated-docs change: the command has been called plugin all along, so regenerating produces an identical tree. --- cmd/{protocol => plugin}/get.go | 2 +- cmd/{protocol => plugin}/get_test.go | 8 ++++---- cmd/{protocol => plugin}/list.go | 2 +- cmd/{protocol => plugin}/list_test.go | 16 ++++++++-------- cmd/{protocol/protocol.go => plugin/plugin.go} | 4 ++-- cmd/root.go | 4 ++-- cmd/serve/schemas.go | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) rename cmd/{protocol => plugin}/get.go (99%) rename cmd/{protocol => plugin}/get_test.go (94%) rename cmd/{protocol => plugin}/list.go (99%) rename cmd/{protocol => plugin}/list_test.go (96%) rename cmd/{protocol/protocol.go => plugin/plugin.go} (84%) diff --git a/cmd/protocol/get.go b/cmd/plugin/get.go similarity index 99% rename from cmd/protocol/get.go rename to cmd/plugin/get.go index fdda82e..5147d1e 100644 --- a/cmd/protocol/get.go +++ b/cmd/plugin/get.go @@ -1,4 +1,4 @@ -package protocol +package plugin import ( "encoding/json" diff --git a/cmd/protocol/get_test.go b/cmd/plugin/get_test.go similarity index 94% rename from cmd/protocol/get_test.go rename to cmd/plugin/get_test.go index 7a5750b..350e647 100644 --- a/cmd/protocol/get_test.go +++ b/cmd/plugin/get_test.go @@ -1,4 +1,4 @@ -package protocol_test +package plugin_test import ( "encoding/json" @@ -6,7 +6,7 @@ import ( "net/http/httptest" "testing" - "github.com/keeperhub/cli/cmd/protocol" + "github.com/keeperhub/cli/cmd/plugin" "github.com/keeperhub/cli/internal/cache" "github.com/keeperhub/cli/internal/config" khhttp "github.com/keeperhub/cli/internal/http" @@ -43,7 +43,7 @@ func TestGetCmd(t *testing.T) { ios, outBuf, _, _ := iostreams.Test() f := newProtoGetFactory(server, ios) - cmd := protocol.NewProtocolCmd(f) + cmd := plugin.NewPluginCmd(f) cmd.SetArgs([]string{"get", "aave"}) err := cmd.Execute() require.NoError(t, err) @@ -70,7 +70,7 @@ func TestGetCmd_NotFound(t *testing.T) { ios, _, _, _ := iostreams.Test() f := newProtoGetFactory(server, ios) - cmd := protocol.NewProtocolCmd(f) + cmd := plugin.NewPluginCmd(f) cmd.SetArgs([]string{"get", "nonexistent-slug"}) err := cmd.Execute() assert.Error(t, err) diff --git a/cmd/protocol/list.go b/cmd/plugin/list.go similarity index 99% rename from cmd/protocol/list.go rename to cmd/plugin/list.go index 074748e..e6e3f67 100644 --- a/cmd/protocol/list.go +++ b/cmd/plugin/list.go @@ -1,4 +1,4 @@ -package protocol +package plugin import ( "encoding/json" diff --git a/cmd/protocol/list_test.go b/cmd/plugin/list_test.go similarity index 96% rename from cmd/protocol/list_test.go rename to cmd/plugin/list_test.go index 8d9ab8e..9d6673f 100644 --- a/cmd/protocol/list_test.go +++ b/cmd/plugin/list_test.go @@ -1,4 +1,4 @@ -package protocol_test +package plugin_test import ( "encoding/json" @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/keeperhub/cli/cmd/protocol" + "github.com/keeperhub/cli/cmd/plugin" "github.com/keeperhub/cli/internal/cache" "github.com/keeperhub/cli/internal/config" khhttp "github.com/keeperhub/cli/internal/http" @@ -78,7 +78,7 @@ func TestListCmd_CacheMiss(t *testing.T) { ios, outBuf, _, _ := iostreams.Test() f := newProtoFactory(server, ios) - cmd := protocol.NewProtocolCmd(f) + cmd := plugin.NewPluginCmd(f) cmd.SetArgs([]string{"list"}) err := cmd.Execute() require.NoError(t, err) @@ -107,7 +107,7 @@ func TestListCmd_CacheHit(t *testing.T) { ios, outBuf, _, _ := iostreams.Test() f := newProtoFactory(server, ios) - cmd := protocol.NewProtocolCmd(f) + cmd := plugin.NewPluginCmd(f) cmd.SetArgs([]string{"list"}) err := cmd.Execute() require.NoError(t, err) @@ -139,7 +139,7 @@ func TestListCmd_Refresh(t *testing.T) { ios, _, _, _ := iostreams.Test() f := newProtoFactory(server, ios) - cmd := protocol.NewProtocolCmd(f) + cmd := plugin.NewPluginCmd(f) cmd.SetArgs([]string{"list", "--refresh"}) err := cmd.Execute() require.NoError(t, err) @@ -168,7 +168,7 @@ func TestListCmd_StaleWithError(t *testing.T) { ios, outBuf, errBuf, _ := iostreams.Test() f := newProtoFactory(server, ios) - cmd := protocol.NewProtocolCmd(f) + cmd := plugin.NewPluginCmd(f) cmd.SetArgs([]string{"list"}) err := cmd.Execute() require.NoError(t, err, "stale cache with error should not return error") @@ -188,7 +188,7 @@ func TestListCmd_NoCacheWithError(t *testing.T) { ios, _, _, _ := iostreams.Test() f := newProtoFactory(server, ios) - cmd := protocol.NewProtocolCmd(f) + cmd := plugin.NewPluginCmd(f) cmd.SetArgs([]string{"list"}) err := cmd.Execute() assert.Error(t, err, "expected error when no cache and API fails") @@ -211,7 +211,7 @@ func TestListCmd_Table(t *testing.T) { ios, outBuf, _, _ := iostreams.Test() f := newProtoFactory(server, ios) - cmd := protocol.NewProtocolCmd(f) + cmd := plugin.NewPluginCmd(f) cmd.SetArgs([]string{"list"}) err := cmd.Execute() require.NoError(t, err) diff --git a/cmd/protocol/protocol.go b/cmd/plugin/plugin.go similarity index 84% rename from cmd/protocol/protocol.go rename to cmd/plugin/plugin.go index fdbec8d..5a052f8 100644 --- a/cmd/protocol/protocol.go +++ b/cmd/plugin/plugin.go @@ -1,11 +1,11 @@ -package protocol +package plugin import ( "github.com/keeperhub/cli/pkg/cmdutil" "github.com/spf13/cobra" ) -func NewProtocolCmd(f *cmdutil.Factory) *cobra.Command { +func NewPluginCmd(f *cmdutil.Factory) *cobra.Command { cmd := &cobra.Command{ Use: "plugin", Short: "Browse available plugins and integrations", diff --git a/cmd/root.go b/cmd/root.go index 75044c0..a72431a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,7 +12,7 @@ import ( "github.com/keeperhub/cli/cmd/help" "github.com/keeperhub/cli/cmd/org" "github.com/keeperhub/cli/cmd/project" - "github.com/keeperhub/cli/cmd/protocol" + "github.com/keeperhub/cli/cmd/plugin" "github.com/keeperhub/cli/cmd/read" "github.com/keeperhub/cli/cmd/run" "github.com/keeperhub/cli/cmd/serve" @@ -53,7 +53,7 @@ func NewRootCmd(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(execute.NewExecuteCmd(f)) cmd.AddCommand(org.NewOrgCmd(f)) cmd.AddCommand(project.NewProjectCmd(f)) - cmd.AddCommand(protocol.NewProtocolCmd(f)) + cmd.AddCommand(plugin.NewPluginCmd(f)) cmd.AddCommand(read.NewReadCmd(f)) cmd.AddCommand(run.NewRunCmd(f)) cmd.AddCommand(serve.NewServeCmd(f)) diff --git a/cmd/serve/schemas.go b/cmd/serve/schemas.go index 2ddb1d0..e8de9e8 100644 --- a/cmd/serve/schemas.go +++ b/cmd/serve/schemas.go @@ -12,7 +12,7 @@ import ( // SchemasResponse is the correct shape of the /api/mcp/schemas response. // The Actions field is a map keyed by actionType (e.g. "web3/check-balance"). -// NOTE: This is intentionally separate from cmd/protocol/list.go which uses a +// NOTE: This is intentionally separate from cmd/plugin/list.go which uses a // different (incorrect) response shape for the protocol discovery commands. type SchemasResponse struct { Actions map[string]ActionSchema `json:"actions"`