-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrender.go
More file actions
151 lines (133 loc) · 4.55 KB
/
Copy pathrender.go
File metadata and controls
151 lines (133 loc) · 4.55 KB
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package main
import (
"fmt"
"io"
"text/tabwriter"
"github.com/khanakia/browserctrl/browser"
)
// Table column headers, in display order. Kept as one slice so the header
// row and the data rows cannot drift apart.
var tableHeader = []string{"STATE", "MCP", "DEVICE ID", "BROWSER", "PROFILE", "NAME", "EMAIL", "DISPLAY NAME", columnAccount}
// columnAccount holds the Claude account the extension is signed in as — the
// column that answers "why can this session not see that browser?", since the
// MCP only reports browsers on its own account.
const columnAccount = "CLAUDE ACCOUNT"
// profilesTableHeader is tableHeader plus the EXTENSION column, used by
// `list --profiles` where rows without the extension are included and the
// reason a row has no device id has to be visible.
var profilesTableHeader = append([]string{tableHeader[0], columnExtension}, tableHeader[1:]...)
// columnExtension and its cells: the answer to "why does this profile have no
// device id?" — not installed means the MCP cannot see the profile at all.
const (
columnExtension = "EXTENSION"
cellExtInstalled = "installed"
cellExtMissing = "not installed"
)
// MCP column cells: "yes" = extension has completed an MCP handshake before.
const (
cellMcpYes = "yes"
cellMcpNo = "no"
)
// placeholderEmpty is what an empty cell shows so columns stay aligned and a
// reader can tell "no value" from a rendering glitch.
const placeholderEmpty = "-"
// tabwriter layout: min cell width, tab width, padding, pad char.
const (
tableMinWidth = 0
tableTabWidth = 0
tablePadding = 2
tablePadChar = ' '
)
// accountLabeler turns an entry's Claude account uuid into something a human
// can read. Injected rather than computed here because the mapping comes from
// outside the scan (Claude Code's config, plus user-given aliases) and the
// renderer must stay a pure formatter.
type accountLabeler func(browser.Entry) string
// writeTable renders entries as an aligned text table. An empty slice prints
// only a hint line — an empty table with just a header reads like a bug.
func writeTable(w io.Writer, entries []browser.Entry, label accountLabeler) error {
return renderTable(w, entries, false, label)
}
// writeProfilesTable is writeTable for `list --profiles`: same rows plus the
// EXTENSION column, and an empty-slice hint that talks about profiles rather
// than extension stores (in this mode "nothing found" means no profile at
// all, which is a different problem).
func writeProfilesTable(w io.Writer, entries []browser.Entry, label accountLabeler) error {
return renderTable(w, entries, true, label)
}
// emptyHint lines, one per mode — see writeTable / writeProfilesTable.
const (
emptyHintStores = "no Claude extension stores found (is the extension installed in any profile?)"
emptyHintProfiles = "no browser profiles found (is any Chromium browser installed, or is --root wrong?)"
)
// renderTable is the shared body. showExtension adds the EXTENSION column;
// the header and the row builder read from the same switch so they cannot
// drift apart.
func renderTable(w io.Writer, entries []browser.Entry, showExtension bool, label accountLabeler) error {
header, hint := tableHeader, emptyHintStores
if showExtension {
header, hint = profilesTableHeader, emptyHintProfiles
}
if len(entries) == 0 {
_, err := fmt.Fprintln(w, hint)
return err
}
tw := tabwriter.NewWriter(w, tableMinWidth, tableTabWidth, tablePadding, tablePadChar, 0)
if err := writeRow(tw, header...); err != nil {
return err
}
for _, e := range entries {
row := []string{string(e.State)}
if showExtension {
row = append(row, installedCell(e.Installed))
}
row = append(row,
yesNo(e.McpConnected),
orPlaceholder(e.DeviceID),
string(e.Browser),
e.ProfileDir,
orPlaceholder(e.ProfileName),
orPlaceholder(e.Email),
orPlaceholder(e.DisplayName),
orPlaceholder(label(e)),
)
if e.Error != "" {
// Surface the per-entry read failure inline rather than hiding it.
row = append(row, "ERROR: "+e.Error)
}
if err := writeRow(tw, row...); err != nil {
return err
}
}
return tw.Flush()
}
func writeRow(w io.Writer, cells ...string) error {
for i, c := range cells {
sep := "\t"
if i == len(cells)-1 {
sep = "\n"
}
if _, err := fmt.Fprint(w, c, sep); err != nil {
return err
}
}
return nil
}
func installedCell(installed bool) string {
if installed {
return cellExtInstalled
}
return cellExtMissing
}
func yesNo(b bool) string {
if b {
return cellMcpYes
}
return cellMcpNo
}
func orPlaceholder(s string) string {
if s == "" {
return placeholderEmpty
}
return s
}