diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5e6db82..672681f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,7 +17,7 @@ jobs: - name: Run linters uses: golangci/golangci-lint-action@v7 with: - version: v2.11 + version: v2.13.2 args: --timeout=3m # goreleaser runs `go mod tidy` before building. If that changes anything, # the release build's tree is dirty and Go stamps the version "+dirty". diff --git a/CHANGELOG.md b/CHANGELOG.md index 320cd94..967d1eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,18 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - **MCP gateway dry-run safety.** `mcp gateway call` now rejects `--dry-run` before it creates a gateway client or sends a request: c1i cannot safely preview or suppress a tool's side effects. +- **MCP gateway compound-host routing.** Default gateway URL derivation now + preserves a compound routing suffix, so `mcp gateway list-tools` and `call` + reach the live gateway without requiring `--gateway-url`. +- **Current Go toolchain and runtime dependencies.** Release builds now use Go + 1.27.1; the lint gate uses golangci-lint 2.13.2, which supports that + toolchain. Updated OAuth, terminal, JOSE, and Go runtime dependencies are + included in the release. +- **Agent runbook discovery.** `docs guide` now lists a concise purpose beside + every embedded workflow, `docs agents` provides a task-to-guide index, and + shell completion offers guide names with the same summaries. The README now + shows the PowerShell loading command. Checks ensure every guide has a summary + and every embedded cross-reference resolves. - **C1.ai branding.** Documentation now uses C1.ai for the product and its corporate website; technical GitHub, package, distribution, and endpoint references retain their existing hosts. diff --git a/README.md b/README.md index dd67242..174a969 100644 --- a/README.md +++ b/README.md @@ -1266,6 +1266,12 @@ c1i completion zsh > "${fpath[1]}/_c1i" c1i completion fish > ~/.config/fish/completions/c1i.fish ``` + +```powershell +# PowerShell (load in the current session) +c1i completion powershell | Out-String | Invoke-Expression +``` + `powershell` is also available. Each generator takes `--no-descriptions` to emit a script that completes names only, without the per-command help text. diff --git a/cmd/agents.md b/cmd/agents.md index 8e78b8b..4e2bd6d 100644 --- a/cmd/agents.md +++ b/cmd/agents.md @@ -182,9 +182,16 @@ authenticating. Which one depends on what you are after: endpoints --filter ` lists matching routes and has a real no-match — an empty result means nothing matched, not "the search gave up" — then `docs endpoint ` prints that route's full request/response schema. -- **A step-by-step runbook** (register an MCP server, configure a new app, - request access): `docs guide` lists the embedded guides and `docs guide - ` prints one. These are static content, no network call. +- **A step-by-step runbook**: `docs guide` lists the embedded guides with a + one-line purpose, and `docs guide ` prints one. These are static + content, no network call. The complete workflow index is: + + | Goal | Guide | + |---|---| + | Register, distribute, or test MCP tools | `register-mcp-server` → `assign-toolset-everyone` → `test-mcp-gateway` | + | Create a manually managed app | `configure-new-app` | + | Request or approve access | `request-access` → `inspect-and-approve-task` | + | Configure delegated provisioning | `delegate-entitlement-provisioning` | - **The raw OpenAPI spec**: `docs openapi` (cached 24h locally). Rule of thumb: a product concept starts at `docs search` → `docs page`; a raw @@ -277,26 +284,15 @@ List commands auto-paginate to completion by default — one invocation gets every page. Pass `--page-token` to opt out and fetch a single page manually. Don't write your own pagination loop. -`--page-size` is a request, not a promise: a page may come back with more -rows than you asked for. That is server behavior, not a c1i bug. How much -more varies per endpoint and per size, so treat any figure you measure as -true of that endpoint, at that size, today. -Three more traps in the same flag: +`--page-size` is a request, not a promise: a page can come back with more +rows. Some endpoints won't return fewer than their server-side floor, but that +is not universal. `--page-size 0` uses the server's default of 25. A value +above the maximum is not an error; c1i clamps it. A negative page size or +limit rejects it before sending. -- Most endpoints won't return fewer than 5 rows however small a positive - value you pass, but that is not universal: `policies list` floors at 6, - and `mcp servers catalog list` has no floor. -- `--page-size 0` does not mean "no paging". The server substitutes its own - default of 25, and the rows returned may then overshoot that. -- A value above the max is not an error: c1i clamps it and sends the max. A - negative `--page-size` or `--limit` is a usage error — c1i rejects it - before sending, at exit 2. - -So never size a batch, count a result set, or infer "there are only N of -these" from `--page-size`. `--limit N` is the exact control: c1i enforces -it client-side, so it holds whatever the server returns, and it stops -auto-pagination once reached. +`--limit N` is the exact control: c1i enforces it client-side, whatever size +the server returns. ## Before you mutate @@ -342,21 +338,11 @@ resource with `--resource-id` likewise means you drop each id is still its own request, but don't loop the CLI per id — pass them all at once (pipe `mcp tools search --state pending --fields id | jq -r .id`). - Owner and grant provisioning are asynchronous. A read immediately after a - write can look like a silent no-op for a couple of minutes (owner writes - observed at 45-150s across set-owners, add-owner, remove-owner and the - owner "apps create" assigns; grants: up to a couple of minutes). Verify - owners with `c1i apps owners `, not `apps get`'s `appOwners` - field, and don't wait for that field to fill — it read [] on every app - checked, including all those `apps owners` reported - owners for. An empty `appOwners` is not evidence an app has no owners. - `apps owners` also - returns zero rows at exit 0 for a well-formed but nonexistent app id, so an - empty result is either "no owners" or "wrong id"; `apps add-owner` on the - same id exits 4. Don't write your own poll loop for this: `apps set-owners` - takes `--wait` (with `--wait-timeout`, default `4m`) and blocks until every - requested owner appears. A `--wait` timeout exits `1` and does not mean the - write failed — provisioning may still be in flight, so re-check with - `apps owners` instead of re-issuing the write. + write can look like a no-op. Verify app owners with `c1i apps owners + `, not `apps get`'s `appOwners` field; an empty `appOwners` is not + evidence an app has no owners. Use `apps set-owners --wait` when the write + must converge before the next step. A wait timeout does not mean the write + failed — re-check before writing again. - `grants list --wait` can report success with zero rows. An empty result is stable, so a filter matching nothing settles in ~10s and exits `0` -- which looks identical to "the grant did not happen" but usually means "not yet". @@ -382,14 +368,10 @@ resource with `--resource-id` likewise means you drop `callFunction.functionId`, same as `accounts list --unmapped-only` above. With `--page-token` a page can come back with zero rows while a matching automation exists on another page. -- Same rule, worse case: a `--fields`/`C1I_FIELDS` spec that matches nothing - anywhere, combined with `--limit`, scans the whole collection before - erroring exit `2` — like `--unmapped-only` above, a post-fetch filter can't - bound the work when nothing has matched yet. A typo is the ordinary way to - hit this. Measured: `tasks list --fields --limit 2` made 193 - requests over ~41s on a ~9,650-row tenant; a 35,000-row `entitlements list` - would take minutes. No cap exists for this on purpose — a first-page-only - check would false-error on a real field that's just sparse. +- A `--fields`/`C1I_FIELDS` projection that matches nothing scans until the + command can prove no row contains that field, even when `--limit` is set. + Correct the field name instead of treating a long-running command as an API + failure. - A task's `outcome` field is omitted while it's unspecified, not while the task is open — a task can be `TASK_STATE_OPEN` and already carry a real, non-UNSPECIFIED outcome (e.g. a provisioning failure mid-flow). Use diff --git a/cmd/docs_guide.go b/cmd/docs_guide.go index d3dea44..637b0df 100644 --- a/cmd/docs_guide.go +++ b/cmd/docs_guide.go @@ -191,10 +191,13 @@ tasks: ## 6. Verify - c1i grants list --app-id "$APP_ID" --entitlement-id "$ENTITLEMENT_ID" +For each user ID from step 4, wait for that user's grant: + + c1i grants list --app-id "$APP_ID" --entitlement-id "$ENTITLEMENT_ID" \ + --user-id "$USER_ID" --wait --wait-min 1 + +Next, verify the caller-facing result with "c1i docs guide test-mcp-gateway". -Grants are eventually consistent — a just-created grant can take up to a -minute or two to appear in this list. ` // guideTestMCPGateway verifies, with c1i, the pieces that must be in place @@ -776,6 +779,18 @@ checking once immediately. what's actually governing a given task. ` +// guideSummaries makes the no-argument guide listing useful for discovering +// the right workflow without loading every runbook. +var guideSummaries = map[string]string{ + "register-mcp-server": "Register a hosted or external MCP server and approve its tools", + "assign-toolset-everyone": "Bind approved tools into a toolset and request it for every user", + "test-mcp-gateway": "Trace a registered tool from configuration through a live gateway call", + "delegate-entitlement-provisioning": "Configure a proxy binding and delegated provisioning", + "configure-new-app": "Create a manually managed app, ownership, and a custom entitlement", + "request-access": "Request, verify, and revoke access through the approval workflow", + "inspect-and-approve-task": "Inspect a task's current policy step and resolve it safely", +} + // docsGuides maps a guide name to its embedded content. Keep names stable — // they're part of the CLI's public surface (an agent may hardcode // "c1i docs guide register-mcp-server" in its own tooling). @@ -799,25 +814,36 @@ func guideNames() []string { return names } +func completeGuideNames(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { + names := guideNames() + completions := make([]string, 0, len(names)) + for _, name := range names { + if strings.HasPrefix(name, toComplete) { + completions = append(completions, name+"\t"+guideSummaries[name]) + } + } + return completions, cobra.ShellCompDirectiveNoFileComp +} + var docsGuideCmd = &cobra.Command{ Use: "guide [name]", Short: "Print an embedded, task-oriented runbook (no auth required)", Long: `Print an embedded, task-oriented runbook — a numbered sequence of actual c1i commands for a common end-to-end workflow. - -Run with no argument to list the available guide names. These are static -content embedded in the c1i binary (no network call), unlike "docs search" / -"docs page" which hit the C1 documentation site. +Run with no argument to list the available guide names and their purposes. +These are static content embedded in the c1i binary (no network call), unlike +"docs search" / "docs page" which hit the C1 documentation site. Examples: c1i docs guide c1i docs guide register-mcp-server`, - Args: cobra.MaximumNArgs(1), + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: completeGuideNames, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { _, _ = fmt.Fprintln(cmd.OutOrStdout(), "Available guides:") for _, name := range guideNames() { - _, _ = fmt.Fprintf(cmd.OutOrStdout(), " %s\n", name) + _, _ = fmt.Fprintf(cmd.OutOrStdout(), " %-34s %s\n", name, guideSummaries[name]) } _, _ = fmt.Fprintln(cmd.OutOrStdout(), "\nRun \"c1i docs guide \" to print one.") return nil diff --git a/cmd/docs_guide_commands_test.go b/cmd/docs_guide_commands_test.go index 9f39353..43ce3cd 100644 --- a/cmd/docs_guide_commands_test.go +++ b/cmd/docs_guide_commands_test.go @@ -56,6 +56,30 @@ type guideInvocation struct { completeExample bool } +func trimOutgoingPipeline(line string) string { + var quote rune + var parentheses int + for i, r := range line { + switch { + case quote != 0: + if r == quote { + quote = 0 + } + case r == '\'' || r == '"': + quote = r + case r == '(': + parentheses++ + case r == ')' && parentheses > 0: + parentheses-- + case r == '|' && parentheses == 0 && i > 0 && (line[i-1] == ' ' || line[i-1] == '\t'): + if rest := strings.TrimSpace(line[i+1:]); rest != "" && !strings.HasPrefix(rest, "-") { + return strings.TrimSpace(line[:i]) + } + } + } + return line +} + // extractGuideInvocations returns every "c1i ..." invocation in guide, in // four recognized shapes: a command block (a line trimmed-starting with // "c1i " — an optional leading shell prompt ("$ " or "> ") stripped first — @@ -94,6 +118,7 @@ func extractGuideInvocations(t *testing.T, guide string) []guideInvocation { line = p } if strings.HasPrefix(line, "c1i ") { + line = trimOutgoingPipeline(line) if loc := redirectRe.FindStringIndex(line); loc != nil { line = line[:loc[0]] } @@ -116,6 +141,16 @@ func extractGuideInvocations(t *testing.T, guide string) []guideInvocation { return invocations } +func TestExtractGuideInvocationsDropsOutgoingPipeline(t *testing.T) { + invocations := extractGuideInvocations(t, "c1i completion powershell | Out-String | Invoke-Expression") + if len(invocations) != 1 { + t.Fatalf("invocation count = %d, want 1", len(invocations)) + } + if got, want := invocations[0].text, "c1i completion powershell"; got != want { + t.Errorf("invocation = %q, want %q", got, want) + } +} + // checkUnclaimedMentions flags a "c1i" mention that falls outside all three // recognized invocation shapes (e.g. unquoted mid-sentence prose, or a line // prefixed with shell logic) but names a "--flag"/"-f"-shaped token later on diff --git a/cmd/docs_guide_test.go b/cmd/docs_guide_test.go index 14b426c..41ab531 100644 --- a/cmd/docs_guide_test.go +++ b/cmd/docs_guide_test.go @@ -3,6 +3,8 @@ package cmd import ( "bytes" "errors" + "github.com/spf13/cobra" + "regexp" "sort" "strings" "testing" @@ -26,6 +28,64 @@ func TestGuideNamesSorted(t *testing.T) { } } +func TestGuideSummariesMatchRegistry(t *testing.T) { + if len(guideSummaries) != len(docsGuides) { + t.Fatalf("guideSummaries has %d entries, want %d", len(guideSummaries), len(docsGuides)) + } + for name := range docsGuides { + if strings.TrimSpace(guideSummaries[name]) == "" { + t.Errorf("guide %q has no discovery summary", name) + } + } +} + +var embeddedGuideReferenceRE = regexp.MustCompile(`(?:c1i )?docs guide ([a-z-]+)`) + +func TestEmbeddedGuideReferencesResolve(t *testing.T) { + sources := make(map[string]string, len(docsGuides)+1) + for name, content := range docsGuides { + sources["guide "+name] = content + } + sources["docs agents"] = agentsTemplate + + for source, content := range sources { + for _, match := range embeddedGuideReferenceRE.FindAllStringSubmatch(content, -1) { + target := match[1] + if _, ok := docsGuides[target]; !ok { + t.Errorf("%s references unknown guide %q", source, target) + } + } + } +} + +func TestGuideCompletionListsNamesAndSummaries(t *testing.T) { + completions, directive := completeGuideNames(docsGuideCmd, nil, "") + if directive != cobra.ShellCompDirectiveNoFileComp { + t.Fatalf("completion directive = %v, want no-file-completion", directive) + } + if len(completions) != len(docsGuides) { + t.Fatalf("completion count = %d, want %d", len(completions), len(docsGuides)) + } + for _, completion := range completions { + name, summary, ok := strings.Cut(completion, "\t") + if !ok || summary != guideSummaries[name] { + t.Errorf("completion %q does not contain the guide summary", completion) + } + } + + completions, _ = completeGuideNames(docsGuideCmd, nil, "test-") + if len(completions) != 1 || !strings.HasPrefix(completions[0], "test-mcp-gateway\t") { + t.Errorf("filtered completions = %q, want test-mcp-gateway only", completions) + } +} + +func TestAssignToolsetGuideWaitsForEachRequestedGrant(t *testing.T) { + verification := regexp.MustCompile(`(?s)c1i grants list --app-id "\$APP_ID" --entitlement-id "\$ENTITLEMENT_ID"\s*\\?\s*--user-id "\$USER_ID" --wait --wait-min 1`) + if !verification.MatchString(guideAssignToolsetEveryone) { + t.Fatalf("assign-toolset-everyone guide does not verify every requested grant with --wait-min 1") + } +} + // TestGuideRegistryLookup pins that every guide the task requires is // registered with non-empty content, so a lookup by name never silently // returns an empty runbook. @@ -71,10 +131,13 @@ func TestDocsGuideCmdNoArgListsNames(t *testing.T) { t.Fatalf("RunE returned unexpected error: %v", err) } out := buf.String() - for name := range docsGuides { + for name, summary := range guideSummaries { if !strings.Contains(out, name) { t.Errorf("no-arg listing missing guide name %q; got:\n%s", name, out) } + if !strings.Contains(out, summary) { + t.Errorf("no-arg listing missing guide summary %q; got:\n%s", summary, out) + } } } diff --git a/cmd/mcp_gateway.go b/cmd/mcp_gateway.go index 69197a0..1f659ef 100644 --- a/cmd/mcp_gateway.go +++ b/cmd/mcp_gateway.go @@ -32,18 +32,16 @@ Subcommands: call - Invoke a tool and print its result`, } -// deriveGatewayURL turns an API base URL into the MCP gateway endpoint by -// inserting "-mcp" before the first dot of the host and appending /v1 -// (https://acme.conductor.one -> https://acme-mcp.conductor.one/v1). +// deriveGatewayURL turns an API base URL into the MCP gateway endpoint. func deriveGatewayURL(baseURL string) (string, error) { u, err := url.Parse(baseURL) if err != nil || u.Host == "" { return "", &usageError{fmt.Errorf("cannot derive gateway URL from %q", baseURL)} } - // Insert -mcp into the hostname (not the port): acme.conductor.one -> - // acme-mcp.conductor.one. hostname := u.Hostname() - if i := strings.Index(hostname, "."); i > 0 { + if i := strings.Index(hostname, "--"); i > 0 { + hostname = hostname[:i] + "-mcp" + hostname[i:] + } else if i := strings.Index(hostname, "."); i > 0 { hostname = hostname[:i] + "-mcp" + hostname[i:] } else { hostname += "-mcp" diff --git a/cmd/mcp_gateway_test.go b/cmd/mcp_gateway_test.go index 5c20873..9c6fcda 100644 --- a/cmd/mcp_gateway_test.go +++ b/cmd/mcp_gateway_test.go @@ -23,6 +23,7 @@ func TestDeriveGatewayURL(t *testing.T) { {"https://acme.conductor.one", "https://acme-mcp.conductor.one/v1"}, {"https://acme.conductor.one/", "https://acme-mcp.conductor.one/v1"}, {"https://localhost:8080", "https://localhost-mcp:8080/v1"}, + {"https://tenant--proxy--environment.example.com", "https://tenant-mcp--proxy--environment.example.com/v1"}, } for _, c := range cases { got, err := deriveGatewayURL(c.in) diff --git a/cmd/root.go b/cmd/root.go index 04ddf0a..7f9bd17 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,8 +16,8 @@ import ( var rootCmd = &cobra.Command{ Use: "c1i", - Short: "C1 (formerly ConductorOne) CLI", - Long: `c1i is a command-line interface for the C1 (formerly ConductorOne) API. + Short: "C1.ai CLI", + Long: `c1i is a command-line interface for the C1.ai API. If you are an AI agent, run "c1i docs agents" first — it covers conventions this help text can't (output contracts, exit codes, when to prefer a diff --git a/cmd/root_test.go b/cmd/root_test.go index 63acec2..0e9f9dd 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -9,6 +9,15 @@ import ( "github.com/spf13/cobra" ) +func TestRootHelpUsesC1AIBrand(t *testing.T) { + if rootCmd.Short != "C1.ai CLI" { + t.Errorf("root short help = %q, want C1.ai CLI", rootCmd.Short) + } + if !strings.Contains(rootCmd.Long, "C1.ai API") { + t.Errorf("root long help must identify the C1.ai API: %q", rootCmd.Long) + } +} + // TestNoSubcommandDefinesOwnPersistentPostRunE guards an invariant the // --fields zero-match-in-list check (checkFieldsMatchedAnyRow in // cmd/fields.go) depends on: cobra runs only the NEAREST ancestor's diff --git a/go.mod b/go.mod index 4cab1ee..6e5d85e 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,15 @@ module github.com/ConductorOne/c1i -go 1.26.7 +go 1.27.1 require ( - github.com/go-jose/go-jose/v4 v4.1.4 + github.com/go-jose/go-jose/v4 v4.1.5 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 github.com/zalando/go-keyring v0.2.8 - golang.org/x/oauth2 v0.36.0 - golang.org/x/term v0.45.0 + golang.org/x/oauth2 v0.37.0 + golang.org/x/term v0.46.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -25,7 +25,7 @@ require ( github.com/spf13/cast v1.10.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/sys v0.47.0 // indirect - golang.org/x/text v0.41.0 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/text v0.42.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/go.sum b/go.sum index 48469aa..50c08a3 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= -github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA= +github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= @@ -51,14 +51,14 @@ github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPc github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= -golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= -golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98= +golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= +golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=