diff --git a/README.md b/README.md index e85dce59..85a889c5 100644 --- a/README.md +++ b/README.md @@ -437,6 +437,50 @@ The above simulates 5 concurrent rooms, where each room has: Once the specified duration is over (or if the load test is manually stopped), the load test statistics will be displayed in the form of a table. +## Browsing documentation + +The CLI includes a built-in `lk docs` command that lets you search and browse the LiveKit documentation directly from the terminal. It's powered by the [LiveKit docs MCP server](https://docs.livekit.io/mcp) using the official [MCP Go SDK](https://github.com/modelcontextprotocol/go-sdk). + +```shell +# Get a complete overview of the docs site +lk docs overview + +# Search the docs +lk docs search "voice agents" + +# Fetch a specific page as markdown +lk docs get-page /agents/start/voice-ai-quickstart + +# Search code across LiveKit GitHub repos +lk docs code-search "class AgentSession" --repo livekit/agents + +# Get recent releases for an SDK +lk docs changelog pypi:livekit-agents + +# List all LiveKit SDKs +lk docs list-sdks + +# Submit feedback on the docs +lk docs submit-feedback --page /agents/build/tools "Missing info about error handling" +``` + +Run `lk docs --help` for full details on each subcommand. + +### Development options + +For development against staging or preview deployments of the docs MCP server, two hidden flags are available on the `lk docs` command: + +- `--server-url URL`: Override the MCP server endpoint (default: `https://docs.livekit.io/mcp/`) +- `--vercel-header VALUE`: Set the `x-vercel-protection-bypass` header, required for accessing private Vercel preview deployments + +```shell +# Use a staging server +lk docs --server-url https://docs-staging.example.com/mcp/ search "agents" + +# Access a private Vercel preview deploy +lk docs --server-url https://docs-abc123.vercel.app/mcp/ --vercel-header overview +``` + ## Additional notes ### Parameter precedence diff --git a/cmd/lk/docs.go b/cmd/lk/docs.go new file mode 100644 index 00000000..dec431f0 --- /dev/null +++ b/cmd/lk/docs.go @@ -0,0 +1,546 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "errors" + "fmt" + "net/http" + "os" + "strconv" + "strings" + "time" + + "github.com/modelcontextprotocol/go-sdk/jsonrpc" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/urfave/cli/v3" + + livekitcli "github.com/livekit/livekit-cli/v2" + "github.com/livekit/livekit-cli/v2/pkg/config" +) + +const defaultDocsServerURL = "https://docs.livekit.io/mcp/" + +// docsRequestTimeout is the maximum time allowed for a complete docs MCP +// request (connect + call + response). This prevents the CLI from hanging +// indefinitely if the server is unresponsive. +const docsRequestTimeout = 30 * time.Second + +// expectedServerVersion is the major.minor version of the LiveKit docs MCP +// server that this CLI was built against. If the server reports a newer +// major or minor version, a warning is printed to stderr suggesting the +// user update their CLI. +var expectedServerVersion = [2]int{1, 3} + +var ( + DocsCommands = []*cli.Command{ + { + Name: "docs", + Usage: "Search and browse LiveKit documentation", + Description: `Query the LiveKit documentation directly from the terminal. Powered by +the LiveKit docs MCP server (https://docs.livekit.io/mcp). + +Typical workflow: + + 1. Start with an overview of the docs site: + lk docs overview + + 2. Search for a topic: + lk docs search "voice agents" + + 3. Fetch a specific page to read: + lk docs get-page /agents/start/voice-ai-quickstart + + 4. Search for code across LiveKit repositories: + lk docs code-search "class AgentSession" --repo livekit/agents + +All output is rendered as markdown.`, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "json", + Aliases: []string{"j"}, + Usage: "Output as JSON instead of markdown", + }, + &cli.StringFlag{ + Name: "server-url", + Hidden: true, + }, + &cli.StringFlag{ + Name: "vercel-header", + Hidden: true, + }, + }, + Commands: []*cli.Command{ + { + Name: "overview", + Usage: "Get a complete overview of the documentation site and table of contents", + Description: `Returns the full docs site table of contents with page descriptions. +This is a great starting point to load context for browsing conceptual +docs rather than relying wholly on search.`, + Action: docsOverview, + }, + { + Name: "search", + Usage: "Search the LiveKit documentation", + ArgsUsage: "[QUERY]", + Description: `Search the docs for a given query. Returns paged results showing page +titles, hierarchical placement, and (sometimes) a content snippet. +Results can then be fetched via "lk docs get-page" for full content. + +The search index covers a large amount of content in many programming +languages. Search should be used as a complement to browsing docs +directly (via "lk docs overview"), not a replacement.`, + Action: docsSearch, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "query", + Aliases: []string{"q"}, + Usage: "Search `QUERY` text", + }, + &cli.IntFlag{ + Name: "page", + Aliases: []string{"p"}, + Usage: "Page number (starts at 0)", + }, + &cli.IntFlag{ + Name: "hits-per-page", + Usage: "Results per page (1-50, default 20)", + }, + }, + }, + { + Name: "get-page", + Aliases: []string{"get-pages"}, + Usage: "Fetch one or more documentation pages as markdown", + ArgsUsage: "PATH [PATH...]", + Description: `Render one or more docs pages to markdown by relative path. Also +supports fetching code from public LiveKit repositories on GitHub. + +Examples: + lk docs get-page /agents/start/voice-ai-quickstart + lk docs get-page /agents/build/tools /agents/build/vision + lk docs get-page https://github.com/livekit/agents/blob/main/README.md + +Note: auto-generated SDK reference pages (e.g. /reference/client-sdk-js) +are hosted externally and cannot be fetched with this command.`, + Action: docsGetPage, + }, + { + Name: "code-search", + Usage: "Search code across LiveKit GitHub repositories", + ArgsUsage: "[QUERY]", + Description: `High-precision GitHub code search across LiveKit repositories. Search +like code, not like English — use actual class names, function names, +and method calls rather than descriptions. Regex is not supported. + +Good queries: "class AgentSession", "def on_enter", "@function_tool" +Bad queries: "how does handoff work", "agent transfer implementation" + +Results come from default branches; very new code in feature branches +may not appear.`, + Action: docsCodeSearch, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "query", + Aliases: []string{"q"}, + Usage: "Search term (use code identifiers, not natural language)", + }, + &cli.StringFlag{ + Name: "repo", + Aliases: []string{"r"}, + Usage: "Target `REPO` (e.g. livekit/agents) or ALL", + Value: "ALL", + }, + &cli.StringFlag{ + Name: "language", + Aliases: []string{"l"}, + Usage: "Language filter (e.g. Python, TypeScript)", + }, + &cli.StringFlag{ + Name: "scope", + Usage: "Search scope: content, filename, or both", + Value: "content", + }, + &cli.IntFlag{ + Name: "limit", + Usage: "Max results to return (1-50)", + Value: 20, + }, + &cli.BoolFlag{ + Name: "full-file", + Usage: "Return full file content instead of snippets", + }, + }, + }, + { + Name: "changelog", + Usage: "Get recent releases and changelog for a LiveKit SDK or package", + ArgsUsage: "IDENTIFIER", + Description: `Get recent releases for a LiveKit repository or package. Supports +repository IDs (e.g. "livekit/agents") and package identifiers +(e.g. "pypi:livekit-agents", "npm:livekit-client", "cargo:livekit"). + +Examples: + lk docs changelog livekit/agents + lk docs changelog pypi:livekit-agents + lk docs changelog npm:@livekit/components-react + lk docs changelog --releases 5 livekit/client-sdk-js`, + Action: docsChangelog, + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "releases", + Usage: "Number of releases to fetch (1-20)", + Value: 2, + }, + &cli.IntFlag{ + Name: "skip", + Usage: "Number of releases to skip for pagination", + }, + }, + }, + { + Name: "list-sdks", + Usage: "List all LiveKit SDK repositories and package names", + Description: `Returns a list of all LiveKit SDK repositories (client SDKs, server +SDKs, and agent frameworks) with their package names for each platform. +Useful for cross-referencing dependencies and finding the right SDK.`, + Action: docsListSDKs, + }, + { + Name: "submit-feedback", + Usage: "Submit feedback on the LiveKit documentation", + ArgsUsage: "[FEEDBACK]", + Description: `Submit constructive feedback on the LiveKit docs. This feedback is +read by the LiveKit team and used to improve the documentation. +Do not include any personal or proprietary information. + +Examples: + lk docs submit-feedback "The voice agents quickstart needs a Node.js example" + lk docs submit-feedback --page /agents/build/tools "Missing info about error handling"`, + Action: docsSubmitFeedback, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "page", + Usage: "The docs `PAGE` the feedback is about (e.g. /agents/build/tools)", + }, + &cli.StringFlag{ + Name: "feedback", + Aliases: []string{"f"}, + Usage: "Feedback text (max 1024 characters)", + }, + &cli.StringFlag{ + Name: "agent", + Usage: "Identity of the agent submitting feedback (e.g. \"Cursor\", \"Claude Code\")", + }, + &cli.StringFlag{ + Name: "model", + Usage: "Model `ID` used by the agent (e.g. \"gpt-5\", \"claude-4.5-sonnet\")", + }, + }, + }, + }, + }, + } +) + +// --------------------------------------------------------------------------- +// Command handlers +// --------------------------------------------------------------------------- + +func docsOverview(ctx context.Context, cmd *cli.Command) error { + return callDocsToolAndPrint(ctx, cmd, "get_docs_overview", map[string]any{}) +} + +func docsSearch(ctx context.Context, cmd *cli.Command) error { + query := cmd.String("query") + if query == "" && cmd.Args().Len() > 0 { + query = strings.Join(cmd.Args().Slice(), " ") + } + if query == "" { + return cli.ShowSubcommandHelp(cmd) + } + + args := map[string]any{ + "query": query, + } + if p := cmd.Int("page"); p > 0 { + args["page"] = p + } + if hpp := cmd.Int("hits-per-page"); hpp > 0 { + args["hitsPerPage"] = hpp + } + + return callDocsToolAndPrint(ctx, cmd, "docs_search", args) +} + +func docsGetPage(ctx context.Context, cmd *cli.Command) error { + if cmd.Args().Len() == 0 { + return cli.ShowSubcommandHelp(cmd) + } + + return callDocsToolAndPrint(ctx, cmd, "get_pages", map[string]any{ + "paths": cmd.Args().Slice(), + }) +} + +func docsCodeSearch(ctx context.Context, cmd *cli.Command) error { + query := cmd.String("query") + if query == "" && cmd.Args().Len() > 0 { + query = strings.Join(cmd.Args().Slice(), " ") + } + if query == "" { + return cli.ShowSubcommandHelp(cmd) + } + + args := map[string]any{ + "query": query, + "repo": cmd.String("repo"), + } + if lang := cmd.String("language"); lang != "" { + args["language"] = lang + } + if scope := cmd.String("scope"); scope != "" { + args["scope"] = scope + } + if limit := cmd.Int("limit"); limit > 0 { + args["limit"] = limit + } + if cmd.Bool("full-file") { + args["returnFullFile"] = true + } + + return callDocsToolAndPrint(ctx, cmd, "code_search", args) +} + +func docsChangelog(ctx context.Context, cmd *cli.Command) error { + identifier := cmd.Args().First() + if identifier == "" { + return cli.ShowSubcommandHelp(cmd) + } + + args := map[string]any{ + "identifier": identifier, + } + if n := cmd.Int("releases"); n > 0 { + args["releasesToFetch"] = n + } + if s := cmd.Int("skip"); s > 0 { + args["skip"] = s + } + + return callDocsToolAndPrint(ctx, cmd, "get_changelog", args) +} + +func docsListSDKs(ctx context.Context, cmd *cli.Command) error { + return callDocsToolAndPrint(ctx, cmd, "get_sdks", map[string]any{}) +} + +func docsSubmitFeedback(ctx context.Context, cmd *cli.Command) error { + feedback := cmd.String("feedback") + if feedback == "" && cmd.Args().Len() > 0 { + feedback = strings.Join(cmd.Args().Slice(), " ") + } + if feedback == "" { + return cli.ShowSubcommandHelp(cmd) + } + + args := map[string]any{ + "feedback": feedback, + } + if page := cmd.String("page"); page != "" { + args["page"] = page + } + if agent := cmd.String("agent"); agent != "" { + args["agent"] = agent + } + if model := cmd.String("model"); model != "" { + args["model"] = model + } + + return callDocsToolAndPrint(ctx, cmd, "submit_docs_feedback", args) +} + +// --------------------------------------------------------------------------- +// Helpers for calling the MCP server and printing results +// --------------------------------------------------------------------------- + +func callDocsToolAndPrint(ctx context.Context, cmd *cli.Command, tool string, args map[string]any) error { + ctx, cancel := context.WithTimeout(ctx, docsRequestTimeout) + defer cancel() + + session, err := initDocsSession(ctx, cmd) + if err != nil { + return err + } + defer session.Close() + + // Inject lightweight telemetry params for the docs MCP server. + args["lkCliVersion"] = livekitcli.Version + if id := tryLoadProjectID(cmd); id != "" { + args["projectId"] = id + } + if cmd.Bool("json") { + args["format"] = "json" + } + + result, err := session.CallTool(ctx, &mcp.CallToolParams{ + Name: tool, + Arguments: args, + }) + if err != nil { + if isNotFoundErr(err) { + return fmt.Errorf("%w\n\nhint: the docs server does not recognize the %q tool — try updating your lk CLI to the latest version", err, tool) + } + return err + } + if result.IsError { + for _, c := range result.Content { + if tc, ok := c.(*mcp.TextContent); ok { + return fmt.Errorf("tool error: %s", tc.Text) + } + } + return fmt.Errorf("tool returned an error") + } + + for _, c := range result.Content { + if tc, ok := c.(*mcp.TextContent); ok { + fmt.Println(tc.Text) + } + } + return nil +} + +func initDocsSession(ctx context.Context, cmd *cli.Command) (*mcp.ClientSession, error) { + endpoint := defaultDocsServerURL + if u := cmd.String("server-url"); u != "" { + endpoint = u + } + + transport := &mcp.StreamableClientTransport{ + Endpoint: endpoint, + } + if v := cmd.String("vercel-header"); v != "" { + transport.HTTPClient = &http.Client{ + Transport: &headerTransport{ + base: http.DefaultTransport, + headers: map[string]string{"x-vercel-protection-bypass": v}, + }, + } + } + + client := mcp.NewClient( + &mcp.Implementation{Name: "lk", Version: livekitcli.Version}, + nil, + ) + session, err := client.Connect(ctx, transport, nil) + if err != nil { + return nil, fmt.Errorf("could not connect to the LiveKit docs server: %w", err) + } + + checkServerVersion(session) + return session, nil +} + +// tryLoadProjectID attempts to resolve the current LiveKit Cloud project ID +// without producing any console output. It returns an empty string if no +// project is configured. +func tryLoadProjectID(cmd *cli.Command) string { + // Explicit --project flag (inherited from root). + if name := cmd.String("project"); name != "" { + if pc, err := config.LoadProject(name); err == nil && pc.ProjectId != "" { + return pc.ProjectId + } + } + // Fall back to the default project. + if pc, err := config.LoadDefaultProject(); err == nil && pc.ProjectId != "" { + return pc.ProjectId + } + return "" +} + +// headerTransport wraps an http.RoundTripper and injects extra headers. +type headerTransport struct { + base http.RoundTripper + headers map[string]string +} + +func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) { + for k, v := range t.headers { + req.Header.Set(k, v) + } + return t.base.RoundTrip(req) +} + +// checkServerVersion prints a warning to stderr if the docs MCP server +// reports a newer major or minor version than what this CLI expects. +func checkServerVersion(session *mcp.ClientSession) { + info := session.InitializeResult() + if info == nil || info.ServerInfo == nil || info.ServerInfo.Version == "" { + return + } + + major, minor, ok := parseMajorMinor(info.ServerInfo.Version) + if !ok { + return + } + if major > expectedServerVersion[0] || (major == expectedServerVersion[0] && minor > expectedServerVersion[1]) { + fmt.Fprintf(os.Stderr, + "warning: the LiveKit docs server is version %s but this CLI was built for %d.%d.x — consider updating lk to the latest version\n\n", + info.ServerInfo.Version, expectedServerVersion[0], expectedServerVersion[1], + ) + } +} + +// parseMajorMinor extracts the first two numeric components from a semver string. +func parseMajorMinor(version string) (major, minor int, ok bool) { + parts := strings.SplitN(version, ".", 3) + if len(parts) < 2 { + return 0, 0, false + } + major, err := strconv.Atoi(parts[0]) + if err != nil { + return 0, 0, false + } + minor, err = strconv.Atoi(parts[1]) + if err != nil { + return 0, 0, false + } + return major, minor, true +} + +// --------------------------------------------------------------------------- +// Error handling +// --------------------------------------------------------------------------- + +// isNotFoundErr returns true if the error indicates the server does not +// recognize the requested tool, resource, or method. +func isNotFoundErr(err error) bool { + var rpcErr *jsonrpc.Error + if !errors.As(err, &rpcErr) { + return false + } + switch rpcErr.Code { + case jsonrpc.CodeMethodNotFound: // -32601 + return true + case mcp.CodeResourceNotFound: // -32002 + return true + case jsonrpc.CodeInvalidParams: // -32602 — may indicate unknown tool + lower := strings.ToLower(rpcErr.Message) + return strings.Contains(lower, "not found") || strings.Contains(lower, "unknown") + default: + return false + } +} diff --git a/cmd/lk/docs_test.go b/cmd/lk/docs_test.go new file mode 100644 index 00000000..59bcd9de --- /dev/null +++ b/cmd/lk/docs_test.go @@ -0,0 +1,158 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "errors" + "fmt" + "net/http" + "testing" + + "github.com/modelcontextprotocol/go-sdk/jsonrpc" + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +func TestParseMajorMinor(t *testing.T) { + tests := []struct { + input string + wantMajor int + wantMinor int + wantOK bool + }{ + {"1.2.3", 1, 2, true}, + {"0.9.0", 0, 9, true}, + {"10.20.30", 10, 20, true}, + {"1.2", 1, 2, true}, + {"1", 0, 0, false}, + {"", 0, 0, false}, + {"abc.def", 0, 0, false}, + {"1.abc", 0, 0, false}, + {"abc.2", 0, 0, false}, + {"1.2.3-beta.1", 1, 2, true}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + major, minor, ok := parseMajorMinor(tt.input) + if ok != tt.wantOK { + t.Errorf("parseMajorMinor(%q): ok = %v, want %v", tt.input, ok, tt.wantOK) + return + } + if major != tt.wantMajor { + t.Errorf("parseMajorMinor(%q): major = %d, want %d", tt.input, major, tt.wantMajor) + } + if minor != tt.wantMinor { + t.Errorf("parseMajorMinor(%q): minor = %d, want %d", tt.input, minor, tt.wantMinor) + } + }) + } +} + +func TestIsNotFoundErr(t *testing.T) { + tests := []struct { + name string + err error + want bool + }{ + { + name: "nil error", + err: nil, + want: false, + }, + { + name: "non-RPC error", + err: errors.New("something went wrong"), + want: false, + }, + { + name: "method not found", + err: &jsonrpc.Error{Code: jsonrpc.CodeMethodNotFound, Message: "method not found"}, + want: true, + }, + { + name: "resource not found", + err: &jsonrpc.Error{Code: mcp.CodeResourceNotFound, Message: "resource not found"}, + want: true, + }, + { + name: "invalid params with not found", + err: &jsonrpc.Error{Code: jsonrpc.CodeInvalidParams, Message: "tool not found"}, + want: true, + }, + { + name: "invalid params with unknown", + err: &jsonrpc.Error{Code: jsonrpc.CodeInvalidParams, Message: "Unknown tool"}, + want: true, + }, + { + name: "invalid params unrelated", + err: &jsonrpc.Error{Code: jsonrpc.CodeInvalidParams, Message: "missing required param"}, + want: false, + }, + { + name: "other RPC error", + err: &jsonrpc.Error{Code: jsonrpc.CodeInternalError, Message: "internal error"}, + want: false, + }, + { + name: "wrapped RPC error", + err: fmt.Errorf("call failed: %w", &jsonrpc.Error{Code: jsonrpc.CodeMethodNotFound, Message: "method not found"}), + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isNotFoundErr(tt.err) + if got != tt.want { + t.Errorf("isNotFoundErr(%v) = %v, want %v", tt.err, got, tt.want) + } + }) + } +} + +func TestHeaderTransport(t *testing.T) { + transport := &headerTransport{ + base: roundTripFunc(func(req *http.Request) (*http.Response, error) { + if got := req.Header.Get("x-custom"); got != "value" { + t.Errorf("header x-custom = %q, want %q", got, "value") + } + if got := req.Header.Get("x-other"); got != "other-value" { + t.Errorf("header x-other = %q, want %q", got, "other-value") + } + return &http.Response{StatusCode: 200}, nil + }), + headers: map[string]string{ + "x-custom": "value", + "x-other": "other-value", + }, + } + + req, _ := http.NewRequest("GET", "https://example.com", nil) + resp, err := transport.RoundTrip(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != 200 { + t.Errorf("status = %d, want 200", resp.StatusCode) + } +} + +// roundTripFunc adapts a function to the http.RoundTripper interface. +type roundTripFunc func(*http.Request) (*http.Response, error) + +func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req) +} diff --git a/cmd/lk/main.go b/cmd/lk/main.go index faf899ec..26576c29 100644 --- a/cmd/lk/main.go +++ b/cmd/lk/main.go @@ -60,6 +60,7 @@ func main() { app.Commands = append(app.Commands, AppCommands...) app.Commands = append(app.Commands, AgentCommands...) app.Commands = append(app.Commands, CloudCommands...) + app.Commands = append(app.Commands, DocsCommands...) app.Commands = append(app.Commands, ProjectCommands...) app.Commands = append(app.Commands, RoomCommands...) app.Commands = append(app.Commands, TokenCommands...) diff --git a/go.mod b/go.mod index f62fe0e8..4a8b4481 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/livekit/server-sdk-go/v2 v2.14.0 github.com/mattn/go-isatty v0.0.20 github.com/moby/patternmatcher v0.6.0 + github.com/modelcontextprotocol/go-sdk v1.4.0 github.com/pelletier/go-toml v1.9.5 github.com/pion/rtcp v1.2.16 github.com/pion/rtp v1.10.1 @@ -99,6 +100,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/cel-go v0.27.0 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/google/jsonschema-go v0.4.2 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect @@ -159,6 +161,8 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/sajari/fuzzy v1.0.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.9.1 // indirect + github.com/segmentio/asm v1.1.3 // indirect + github.com/segmentio/encoding v0.5.3 // indirect github.com/sergi/go-diff v1.4.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -172,6 +176,7 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/zeebo/xxh3 v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect @@ -191,6 +196,7 @@ require ( golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/net v0.51.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/term v0.40.0 // indirect golang.org/x/text v0.34.0 // indirect diff --git a/go.sum b/go.sum index 72ada4b5..5db77d38 100644 --- a/go.sum +++ b/go.sum @@ -215,6 +215,8 @@ github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -224,6 +226,8 @@ github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXn github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= +github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -313,6 +317,8 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/modelcontextprotocol/go-sdk v1.4.0 h1:u0kr8lbJc1oBcawK7Df+/ajNMpIDFE41OEPxdeTLOn8= +github.com/modelcontextprotocol/go-sdk v1.4.0/go.mod h1:Nxc2n+n/GdCebUaqCOhTetptS17SXXNu9IfNTaLDi1E= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= @@ -415,6 +421,10 @@ github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5 github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL5qTdn9lR8XKHf4RUyG1Sx3g= github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w= +github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= @@ -467,6 +477,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -539,6 +551,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -589,6 +603,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=