Files
hatchet/cmd/hatchet-cli/cli/docs.go
Gabe Ruttner 94066fab98 Feat--llm-readable-docs (#3030)
* feat: generate flat files

* feat: init mcp

* feat: enhance tab labels with themed icons and add new logos for programming languages

* feat: install in onboarding flows

* structural cleanup

* chore: remove desktop and add buttons

* feat: posthog instrumentation

* fix: responsive buttons

* nit: raw

* feat: minisearch

* feat: wip improved search

* feat: update MCP installation documentation and links

* chore: lint

* chore: cleanup comment

* fix: docstring

* fix: improve search highlighting and scrolling behavior

* chore: lint

* chore: review

* feat: add safeBase64Encode function and update origin handling in theme.config.tsx

* chore: feedback

* feat: ruby logo

* chore: lint
2026-02-16 03:07:45 -08:00

301 lines
8.3 KiB
Go

package cli
import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/charmbracelet/huh"
"github.com/spf13/cobra"
"github.com/hatchet-dev/hatchet/cmd/hatchet-cli/cli/internal/config/cli"
"github.com/hatchet-dev/hatchet/cmd/hatchet-cli/cli/internal/styles"
)
const defaultMCPURL = "https://docs.hatchet.run/api/mcp"
const docsBaseURL = "https://docs.hatchet.run"
var mcpURL string
// docsCmd represents the docs command
var docsCmd = &cobra.Command{
Use: "docs",
Aliases: []string{"doc"},
Short: "Hatchet documentation for AI editors and coding agents",
Long: `Hatchet documentation is optimized for LLMs and available as:
• MCP server: ` + defaultMCPURL + `
• llms.txt: ` + docsBaseURL + `/llms.txt
• Full docs: ` + docsBaseURL + `/llms-full.txt
Use "hatchet docs install" to configure your AI editor.`,
Example: ` # Interactive — pick your editor
hatchet docs install
# Configure for Cursor
hatchet docs install cursor
# Configure for Claude Code
hatchet docs install claude-code
# Use a custom MCP URL (self-hosted)
hatchet docs install cursor --url https://my-hatchet.example.com/api/mcp`,
Run: func(cmd *cobra.Command, args []string) {
printAllOptions()
},
}
// docsInstallCmd represents the docs install command
var docsInstallCmd = &cobra.Command{
Use: "install",
Short: "Install Hatchet docs into an AI editor",
Long: `Configure Hatchet documentation as an MCP (Model Context Protocol) server
for AI editors like Cursor and Claude Code.`,
Example: ` # Interactive — pick your editor
hatchet docs install
# Configure for Cursor
hatchet docs install cursor
# Configure for Claude Code
hatchet docs install claude-code`,
Run: func(cmd *cobra.Command, args []string) {
// Interactive mode: let user pick their editor
var editor string
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Which AI editor do you want to configure?").
Options(
huh.NewOption("Cursor", "cursor"),
huh.NewOption("Claude Code", "claude-code"),
).
Value(&editor),
),
).WithTheme(styles.HatchetTheme())
err := form.Run()
if err != nil {
cli.Logger.Fatalf("could not run AI editor selection form: %v", err)
}
switch editor {
case "cursor":
runDocsCursor()
case "claude-code":
runDocsClaudeCode()
}
},
}
// ---------------------------------------------------------------------------
// Subcommands of `docs install`
// ---------------------------------------------------------------------------
var docsInstallCursorCmd = &cobra.Command{
Use: "cursor",
Short: "Configure Hatchet docs for Cursor IDE",
Long: `Set up Hatchet documentation as an MCP server in Cursor.
This creates a .cursor/rules/hatchet-docs.mdc file in your project that
configures the Hatchet MCP docs server, and prints the one-click deeplink.`,
Run: func(cmd *cobra.Command, args []string) {
runDocsCursor()
},
}
var docsInstallClaudeCodeCmd = &cobra.Command{
Use: "claude-code",
Short: "Configure Hatchet docs for Claude Code",
Long: `Set up Hatchet documentation as an MCP server in Claude Code.`,
Run: func(cmd *cobra.Command, args []string) {
runDocsClaudeCode()
},
}
func init() {
rootCmd.AddCommand(docsCmd)
docsCmd.AddCommand(docsInstallCmd)
docsInstallCmd.AddCommand(docsInstallCursorCmd)
docsInstallCmd.AddCommand(docsInstallClaudeCodeCmd)
// Add --url flag to install and its subcommands
for _, cmd := range []*cobra.Command{docsInstallCmd, docsInstallCursorCmd, docsInstallClaudeCodeCmd} {
cmd.Flags().StringVar(&mcpURL, "url", "", "Custom MCP server URL (default: "+defaultMCPURL+")")
}
}
// ---------------------------------------------------------------------------
// Implementation
// ---------------------------------------------------------------------------
func runDocsCursor() {
url := getMCPURL()
fmt.Println(styles.Title("Hatchet Docs → Cursor"))
fmt.Println()
// 1. Write .cursor/rules/hatchet-docs.mdc
rulesDir := filepath.Join(".", ".cursor", "rules")
rulesFile := filepath.Join(rulesDir, "hatchet-docs.mdc")
ruleContent := fmt.Sprintf(`---
description: Hatchet documentation MCP server
alwaysApply: true
---
When working with Hatchet (task queues, workflows, durable execution), use the
Hatchet MCP docs server for accurate, up-to-date API reference and examples.
MCP server URL: %s
Use the search_docs tool to find relevant documentation pages, or get_full_docs
for comprehensive context. Documentation covers Python, TypeScript, and Go SDKs.
`, url)
if err := os.MkdirAll(rulesDir, 0o755); err == nil {
if err := os.WriteFile(rulesFile, []byte(ruleContent), 0o644); err == nil {
fmt.Println(styles.SuccessMessage("Created " + rulesFile))
} else {
fmt.Printf(" ⚠ Could not write %s: %v\n", rulesFile, err)
}
} else {
fmt.Printf(" ⚠ Could not create %s: %v\n", rulesDir, err)
}
// 2. Print the MCP deeplink
fmt.Println()
deeplink := cursorMCPDeeplink(url)
fmt.Println(styles.Section("One-click install"))
fmt.Println(styles.InfoMessage("Open this link in your browser to install the MCP server in Cursor:"))
fmt.Println()
fmt.Println(" " + styles.URL(deeplink))
fmt.Println()
// 3. Offer to open in browser
if promptOpenBrowser() {
openBrowser(deeplink)
}
}
func runDocsClaudeCode() {
url := getMCPURL()
fmt.Println(styles.Title("Hatchet Docs → Claude Code"))
fmt.Println()
claudeCmd := fmt.Sprintf("claude mcp add --transport http hatchet-docs %s", url)
// Try to run claude mcp add directly
if _, err := exec.LookPath("claude"); err == nil {
fmt.Println(styles.InfoMessage("Found claude CLI. Adding MCP server..."))
fmt.Println()
cmd := exec.Command("claude", "mcp", "add", "--transport", "http", "hatchet-docs", url)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err == nil {
fmt.Println()
fmt.Println(styles.SuccessMessage("Hatchet docs MCP server added to Claude Code"))
return
}
fmt.Printf(" ⚠ Command failed. You can run it manually:\n\n")
} else {
fmt.Println(styles.InfoMessage("Claude CLI not found on PATH. Run this command manually:"))
fmt.Println()
}
fmt.Println(styles.Code.Render(claudeCmd))
fmt.Println()
}
func printAllOptions() {
url := getMCPURL()
fmt.Println(styles.Title("Hatchet Docs for AI Editors"))
fmt.Println()
// MCP Server
fmt.Println(styles.Section("MCP Server"))
fmt.Println(styles.KeyValue("URL", url))
fmt.Println()
// Cursor
fmt.Println(styles.Section("Cursor"))
deeplink := cursorMCPDeeplink(url)
fmt.Println(styles.KeyValue("Deeplink", deeplink))
fmt.Println(styles.KeyValue("Or run", "hatchet docs install cursor"))
fmt.Println()
// Claude Code
fmt.Println(styles.Section("Claude Code"))
fmt.Println(styles.Code.Render(fmt.Sprintf("claude mcp add --transport http hatchet-docs %s", url)))
fmt.Println()
// llms.txt
fmt.Println(styles.Section("LLM-Friendly Docs (llms.txt)"))
fmt.Println(styles.KeyValue("Index", docsBaseURL+"/llms.txt"))
fmt.Println(styles.KeyValue("Full docs", docsBaseURL+"/llms-full.txt"))
fmt.Println()
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
func getMCPURL() string {
if mcpURL != "" {
return mcpURL
}
return defaultMCPURL
}
func cursorMCPDeeplink(url string) string {
config := map[string]interface{}{
"command": "npx",
"args": []string{"-y", "mcp-remote", url},
}
configJSON, _ := json.Marshal(config)
encoded := base64.StdEncoding.EncodeToString(configJSON)
return fmt.Sprintf("cursor://anysphere.cursor-deeplink/mcp/install?name=hatchet-docs&config=%s", encoded)
}
func promptOpenBrowser() bool {
var open bool
form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Open in browser?").
Value(&open),
),
).WithTheme(styles.HatchetTheme())
if err := form.Run(); err != nil {
return false
}
return open
}
func openBrowser(url string) {
var cmd *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmd = exec.Command("open", url)
case "windows":
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
default:
cmd = exec.Command("xdg-open", url)
}
if err := cmd.Start(); err != nil {
fmt.Printf(" ⚠ Could not open browser: %v\n", err)
fmt.Println(" Copy the link above and paste it in your browser.")
}
}