File size: 3,784 Bytes
6a7089a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package apiclient

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

// CheckServerAndGuide checks if pinchtab server is running and provides guidance
func CheckServerAndGuide(client *http.Client, base, token string) bool {
	req, _ := http.NewRequest("GET", base+"/health", nil)
	if token != "" {
		req.Header.Set("Authorization", "Bearer "+token)
	}

	resp, err := client.Do(req)
	if err != nil {
		if strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial tcp") {
			fmt.Fprintf(os.Stderr, "Pinchtab server is not running on %s\n", base)
			fmt.Fprintln(os.Stderr)
			fmt.Fprintln(os.Stderr, "To start the server")
			fmt.Fprintln(os.Stderr, "  pinchtab # Run in foreground (recommended for beginners)")
			fmt.Fprintln(os.Stderr, "  pinchtab & # Run in background")
			fmt.Fprintln(os.Stderr, "  PINCHTAB_PORT=9868 pinchtab # Use different port")
			fmt.Fprintln(os.Stderr)
			fmt.Fprintln(os.Stderr, "Then try your command again")
			fmt.Fprintf(os.Stderr, "  %s\n", strings.Join(os.Args, " "))
			fmt.Fprintln(os.Stderr)
			fmt.Fprintln(os.Stderr, "Learn more: https://github.com/pinchtab/pinchtab#quick-start")
			return false
		}
		// Other connection errors
		fmt.Fprintf(os.Stderr, "Cannot connect to Pinchtab server: %v\n", err)
		return false
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode == 401 {
		fmt.Fprintln(os.Stderr, "Authentication required. Set PINCHTAB_TOKEN.")
		return false
	}

	if resp.StatusCode >= 400 {
		body, _ := io.ReadAll(resp.Body)
		fmt.Fprintf(os.Stderr, "Server error %d: %s\n", resp.StatusCode, string(body))
		return false
	}

	return true
}

// SuggestNextAction provides helpful suggestions based on the current command and state
func SuggestNextAction(cmd string, result map[string]any) {
	switch cmd {
	case "nav", "navigate":
		fmt.Fprintln(os.Stderr)
		fmt.Fprintln(os.Stderr, "Next steps")
		fmt.Fprintln(os.Stderr, "  pinchtab snap # See page structure")
		fmt.Fprintln(os.Stderr, "  pinchtab screenshot # Capture visual")
		fmt.Fprintln(os.Stderr, "  pinchtab click <ref> # Click an element")
		fmt.Fprintln(os.Stderr, "  pinchtab pdf --tab <id> -o output.pdf # Save tab as PDF")

	case "snap", "snapshot":
		refs := extractRefs(result)
		if len(refs) > 0 {
			fmt.Fprintln(os.Stderr)
			fmt.Fprintf(os.Stderr, "Found %d interactive elements\n", len(refs))
			for i, ref := range refs[:min(3, len(refs))] {
				fmt.Fprintf(os.Stderr, "  pinchtab click %s # %s\n", ref.id, ref.desc)
				if i >= 2 {
					break
				}
			}
			if len(refs) > 3 {
				fmt.Fprintf(os.Stderr, "  ... and %d more\n", len(refs)-3)
			}
		}

	case "click", "type", "fill":
		fmt.Fprintln(os.Stderr)
		fmt.Fprintln(os.Stderr, "Action completed")
		fmt.Fprintln(os.Stderr, "  pinchtab snap # See updated page")
		fmt.Fprintln(os.Stderr, "  pinchtab screenshot # Visual confirmation")
	}
}

type refInfo struct {
	id   string
	desc string
}

func extractRefs(data map[string]any) []refInfo {
	var refs []refInfo

	// Handle different snapshot formats
	if elements, ok := data["elements"].([]any); ok {
		for _, elem := range elements {
			if m, ok := elem.(map[string]any); ok {
				if ref, ok := m["ref"].(string); ok && ref != "" {
					desc := ""
					if role, ok := m["role"].(string); ok {
						desc = role
					}
					if name, ok := m["name"].(string); ok && name != "" {
						desc += ": " + name
					}
					// Only include interactive elements
					if role, ok := m["role"].(string); ok {
						if role == "button" || role == "link" || role == "textbox" ||
							role == "checkbox" || role == "radio" || role == "combobox" {
							refs = append(refs, refInfo{id: ref, desc: desc})
						}
					}
				}
			}
		}
	}

	return refs
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}