Skip to content

Commit da0f160

Browse files
streamline
1 parent 7b26b2a commit da0f160

3 files changed

Lines changed: 154 additions & 550 deletions

File tree

cmd/docs/docs.go

Lines changed: 102 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"encoding/json"
2020
"fmt"
2121
"net/url"
22-
"path/filepath"
2322
"strings"
2423

2524
"github.com/slackapi/slack-cli/internal/search"
@@ -30,192 +29,164 @@ import (
3029
"github.com/spf13/cobra"
3130
)
3231

33-
var searchMode bool
3432
var outputFormat string
3533
var searchLimit int
36-
var searchOffset int
34+
var searchFilter string
3735

3836
func NewCommand(clients *shared.ClientFactory) *cobra.Command {
3937
cmd := &cobra.Command{
4038
Use: "docs",
4139
Short: "Open Slack developer docs",
42-
Long: "Open the Slack developer docs in your browser, with optional search functionality",
40+
Long: "Open the Slack developer docs in your browser, or search docs with subcommands",
4341
Example: style.ExampleCommandsf([]style.ExampleCommand{
4442
{
4543
Meaning: "Open Slack developer docs homepage",
4644
Command: "docs",
4745
},
4846
{
49-
Meaning: "Search Slack developer docs for Block Kit",
50-
Command: "docs --search \"Block Kit\"",
47+
Meaning: "Search and return JSON (default)",
48+
Command: "docs search \"Block Kit\"",
5149
},
5250
{
53-
Meaning: "Search and get JSON results",
54-
Command: "docs --search \"Block Kit\" --output=json",
51+
Meaning: "Search and open in browser",
52+
Command: "docs search \"webhooks\" --output=browser",
53+
},
54+
}),
55+
RunE: func(cmd *cobra.Command, args []string) error {
56+
return runDocsCommand(clients, cmd, args)
57+
},
58+
}
59+
60+
// Add search subcommand
61+
cmd.AddCommand(newSearchCommand(clients))
62+
63+
return cmd
64+
}
65+
66+
// newSearchCommand creates the search subcommand
67+
func newSearchCommand(clients *shared.ClientFactory) *cobra.Command {
68+
cmd := &cobra.Command{
69+
Use: "search [query]",
70+
Short: "Search Slack developer documentation",
71+
Long: "Search the Slack developer documentation and return results in JSON format (default) or open in browser. If no query provided, opens search page in browser.",
72+
Args: cobra.MaximumNArgs(1),
73+
Example: style.ExampleCommandsf([]style.ExampleCommand{
74+
{
75+
Meaning: "Open docs search page in browser",
76+
Command: "docs search",
77+
},
78+
{
79+
Meaning: "Search for Block Kit (returns JSON by default)",
80+
Command: "docs search \"Block Kit\"",
81+
},
82+
{
83+
Meaning: "Search and open in browser",
84+
Command: "docs search \"Block Kit\" --output=browser",
5585
},
5686
{
5787
Meaning: "Search with custom limit",
58-
Command: "docs --search \"Block Kit\" --output=json --limit=50",
88+
Command: "docs search \"webhooks\" --limit=50",
5989
},
6090
{
61-
Meaning: "Search with pagination",
62-
Command: "docs --search \"Block Kit\" --output=json --limit=20 --offset=20",
91+
Meaning: "Search with filter",
92+
Command: "docs search \"webhooks\" --filter=guides",
93+
},
94+
{
95+
Meaning: "Search Python documentation and open in browser",
96+
Command: "docs search \"bolt\" --filter=python --output=browser",
6397
},
6498
}),
6599
RunE: func(cmd *cobra.Command, args []string) error {
66-
return runDocsCommand(clients, cmd, args)
100+
if len(args) == 0 {
101+
return runSearchBrowserCommand(clients, cmd)
102+
}
103+
return runSearchCommand(clients, cmd, args[0])
67104
},
68105
}
69106

70-
cmd.Flags().BoolVar(&searchMode, "search", false, "search Slack docs with optional query")
71-
cmd.Flags().StringVar(&outputFormat, "output", "browser", "output format: browser, json")
107+
cmd.Flags().StringVar(&outputFormat, "output", "json", "output format: json, browser")
72108
cmd.Flags().IntVar(&searchLimit, "limit", 20, "maximum number of results to return")
73-
cmd.Flags().IntVar(&searchOffset, "offset", 0, "number of results to skip (for pagination)")
109+
cmd.Flags().StringVar(&searchFilter, "filter", "", "filter results by content type: guides, reference, changelog, python, javascript, java, slack_cli, slack_github_action, deno_slack_sdk")
74110

75111
return cmd
76112
}
77113

78-
// DocsOutput represents the structured output for --json mode
79-
type DocsOutput struct {
80-
URL string `json:"url"`
81-
Query string `json:"query,omitempty"`
82-
Type string `json:"type"` // "homepage", "search", or "search_with_query"
83-
}
84-
85-
// ProgrammaticSearchOutput represents the output from local docs search
86-
type ProgrammaticSearchOutput = search.SearchResponse
114+
// openSearchInBrowser opens the docs search page in browser
115+
func openSearchInBrowser(clients *shared.ClientFactory, ctx context.Context, searchURL string) error {
116+
clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{
117+
Emoji: "books",
118+
Text: "Docs Search",
119+
Secondary: []string{
120+
searchURL,
121+
},
122+
}))
87123

88-
// findDocsRepo tries to locate the docs repository
89-
func findDocsRepo() string {
90-
return search.FindDocsRepo()
124+
clients.Browser().OpenURL(searchURL)
125+
clients.IO.PrintTrace(ctx, slacktrace.DocsSearchSuccess, "")
126+
return nil
91127
}
92128

93-
// runProgrammaticSearch executes the local search
94-
func runProgrammaticSearch(query string, docsPath string) (*ProgrammaticSearchOutput, error) {
95-
contentDir := filepath.Join(docsPath, "content")
96-
return search.SearchDocs(query, "", searchLimit, searchOffset, contentDir)
129+
// runSearchBrowserCommand opens the docs search page in browser
130+
func runSearchBrowserCommand(clients *shared.ClientFactory, cmd *cobra.Command) error {
131+
ctx := cmd.Context()
132+
searchURL := "https://docs.slack.dev/search"
133+
return openSearchInBrowser(clients, ctx, searchURL)
97134
}
98135

99-
// runDocsCommand opens Slack developer docs in the browser or performs programmatic search
100-
func runDocsCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []string) error {
136+
// runSearchCommand handles the search subcommand
137+
func runSearchCommand(clients *shared.ClientFactory, cmd *cobra.Command, query string) error {
101138
ctx := cmd.Context()
102139

103-
var docsURL string
104-
var sectionText string
105-
var query string
106-
var docType string
107-
108-
// Validate: if there are arguments, --search flag must be used
109-
if len(args) > 0 && !cmd.Flags().Changed("search") {
110-
query := strings.Join(args, " ")
111-
return slackerror.New(slackerror.ErrDocsSearchFlagRequired).WithRemediation(
112-
"Use --search flag: %s",
113-
style.Commandf(fmt.Sprintf("docs --search \"%s\"", query), false),
114-
)
140+
results, err := search.SearchDocs(query, searchFilter, searchLimit)
141+
if err != nil {
142+
return fmt.Errorf("search failed: %w", err)
115143
}
116144

117-
if cmd.Flags().Changed("search") {
118-
if len(args) > 0 {
119-
query = strings.Join(args, " ")
120-
121-
// Check output format
122-
if outputFormat == "json" {
123-
return runProgrammaticSearchCommand(clients, ctx, query)
124-
}
125-
126-
// Default browser search
127-
encodedQuery := url.QueryEscape(query)
128-
docsURL = fmt.Sprintf("https://docs.slack.dev/search/?q=%s", encodedQuery)
129-
sectionText = "Docs Search"
130-
docType = "search_with_query"
131-
} else {
132-
// --search (no argument) - open search page
133-
docsURL = "https://docs.slack.dev/search/"
134-
sectionText = "Docs Search"
135-
docType = "search"
145+
// Output results
146+
if outputFormat == "json" {
147+
jsonBytes, err := json.MarshalIndent(results, "", " ")
148+
if err != nil {
149+
return fmt.Errorf("failed to encode JSON: %w", err)
136150
}
151+
fmt.Println(string(jsonBytes))
137152
} else {
138-
// No search flag: default homepage
139-
docsURL = "https://docs.slack.dev"
140-
sectionText = "Docs Open"
141-
docType = "homepage"
142-
}
143-
144-
// Handle JSON output mode (for browser-based results only)
145-
if outputFormat == "json" && !cmd.Flags().Changed("search") {
146-
output := DocsOutput{
147-
URL: docsURL,
148-
Query: query,
149-
Type: docType,
150-
}
151-
152-
jsonBytes, err := json.MarshalIndent(output, "", " ")
153-
if err != nil {
154-
return slackerror.New(slackerror.ErrDocsJSONEncodeFailed)
153+
// Browser output - open search page with query
154+
searchURL := fmt.Sprintf("https://docs.slack.dev/search/?q=%s", url.QueryEscape(query))
155+
if searchFilter != "" {
156+
searchURL += fmt.Sprintf("&filter=%s", url.QueryEscape(searchFilter))
155157
}
158+
return openSearchInBrowser(clients, ctx, searchURL)
159+
}
156160

157-
fmt.Println(string(jsonBytes))
161+
return nil
162+
}
158163

159-
// Still print trace for analytics
160-
if cmd.Flags().Changed("search") {
161-
traceValue := query
162-
clients.IO.PrintTrace(ctx, slacktrace.DocsSearchSuccess, traceValue)
163-
} else {
164-
clients.IO.PrintTrace(ctx, slacktrace.DocsSuccess)
165-
}
164+
// runDocsCommand opens Slack developer docs in the browser
165+
func runDocsCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []string) error {
166+
ctx := cmd.Context()
166167

167-
return nil
168+
// If any arguments provided, suggest using search subcommand
169+
if len(args) > 0 {
170+
query := strings.Join(args, " ")
171+
return slackerror.New(slackerror.ErrDocsSearchFlagRequired).WithRemediation(
172+
"Use search subcommand: %s",
173+
style.Commandf(fmt.Sprintf("docs search \"%s\"", query), false),
174+
)
168175
}
169176

170-
// Standard browser-opening mode
177+
// Open docs homepage
178+
docsURL := "https://docs.slack.dev"
179+
171180
clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{
172181
Emoji: "books",
173-
Text: sectionText,
182+
Text: "Docs Open",
174183
Secondary: []string{
175184
docsURL,
176185
},
177186
}))
178187

179188
clients.Browser().OpenURL(docsURL)
189+
clients.IO.PrintTrace(ctx, slacktrace.DocsSuccess)
180190

181-
if cmd.Flags().Changed("search") {
182-
traceValue := ""
183-
if len(args) > 0 {
184-
traceValue = strings.Join(args, " ")
185-
}
186-
clients.IO.PrintTrace(ctx, slacktrace.DocsSearchSuccess, traceValue)
187-
} else {
188-
clients.IO.PrintTrace(ctx, slacktrace.DocsSuccess)
189-
}
190-
191-
return nil
192-
}
193-
194-
// runProgrammaticSearchCommand handles local documentation search
195-
func runProgrammaticSearchCommand(clients *shared.ClientFactory, ctx context.Context, query string) error {
196-
// Find the docs repository
197-
docsPath := findDocsRepo()
198-
if docsPath == "" {
199-
clients.IO.PrintError(ctx, "❌ Docs repository not found")
200-
clients.IO.PrintInfo(ctx, false, "💡 Make sure the docs repository is cloned alongside slack-cli")
201-
clients.IO.PrintInfo(ctx, false, " Expected structure:")
202-
clients.IO.PrintInfo(ctx, false, " ├── slack-cli/")
203-
clients.IO.PrintInfo(ctx, false, " └── docs/")
204-
return fmt.Errorf("docs repository not found")
205-
}
206-
207-
// Run the search
208-
results, err := runProgrammaticSearch(query, docsPath)
209-
if err != nil {
210-
clients.IO.PrintError(ctx, "❌ Search failed: %v", err)
211-
return err
212-
}
213-
214-
// Always output JSON for programmatic search
215-
jsonBytes, err := json.MarshalIndent(results, "", " ")
216-
if err != nil {
217-
return fmt.Errorf("failed to encode JSON: %w", err)
218-
}
219-
fmt.Println(string(jsonBytes))
220191
return nil
221192
}

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ go 1.26.0
55
require (
66
github.com/AlecAivazis/survey/v2 v2.3.7
77
github.com/briandowns/spinner v1.23.2
8-
github.com/charmbracelet/bubbles v1.0.0
98
github.com/charmbracelet/bubbletea v1.3.10
109
github.com/charmbracelet/huh v1.0.0
1110
github.com/charmbracelet/lipgloss v1.1.0
@@ -43,6 +42,7 @@ require (
4342
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
4443
github.com/catppuccin/go v0.3.0 // indirect
4544
github.com/chainguard-dev/git-urls v1.0.2 // indirect
45+
github.com/charmbracelet/bubbles v1.0.0 // indirect
4646
github.com/charmbracelet/colorprofile v0.4.2 // indirect
4747
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
4848
github.com/charmbracelet/x/exp/strings v0.1.0 // indirect
@@ -95,6 +95,6 @@ require (
9595
github.com/stretchr/objx v0.5.3 // indirect
9696
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
9797
go.uber.org/atomic v1.11.0 // indirect
98-
golang.org/x/term v0.40.0 // indirect
98+
golang.org/x/term v0.40.0
9999
gopkg.in/yaml.v3 v3.0.1 // indirect
100100
)

0 commit comments

Comments
 (0)