mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-13 06:51:27 -10:00
Update the Go module path, imports, install scripts, docs, workflows, packaging and action defaults from Gitlawb/zero to Twigpine/zero. The npm scope (already @twigpine/zero on main, with its @gitlawb/zero migration notes), LICENSE, action author, the OpenGateway provider and historical CHANGELOG links are unchanged. The X-AIMLAPI-Integration-Repo header keeps Gitlawb/zero: it is what AIMLAPI sees for attribution, so changing it needs a maintainer's yes. docs/INSTALL.md keeps `go install github.com/Gitlawb/zero/...`, because every tagged release so far declares that module path. The Homebrew formula comment names the Twigpine/homebrew-tap tap. Co-authored-by: Claude Opus 5.5 <[email protected]>
249 lines
6.3 KiB
Go
249 lines
6.3 KiB
Go
package tui
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/Twigpine/zero/internal/redaction"
|
|
)
|
|
|
|
type commandStatus string
|
|
|
|
const (
|
|
commandStatusOK commandStatus = "ok"
|
|
commandStatusWarning commandStatus = "warning"
|
|
commandStatusBlocked commandStatus = "blocked"
|
|
commandStatusInfo commandStatus = "info"
|
|
)
|
|
|
|
type commandOutput struct {
|
|
Title string
|
|
Status commandStatus
|
|
Sections []commandSection
|
|
Hints []string
|
|
}
|
|
|
|
type commandSection struct {
|
|
Title string
|
|
Fields []commandField
|
|
Lines []string
|
|
Rows []commandRow
|
|
Hints []string
|
|
}
|
|
|
|
type commandField struct {
|
|
Key string
|
|
Value string
|
|
}
|
|
|
|
type commandRow struct {
|
|
Text string
|
|
}
|
|
|
|
type commandCard struct {
|
|
Title string
|
|
Summary []string
|
|
Sections []commandCardSection
|
|
Actions []string
|
|
}
|
|
|
|
type commandCardSection struct {
|
|
Title string
|
|
Fields []commandField
|
|
Lines []string
|
|
Rows []commandRow
|
|
}
|
|
|
|
const commandCardTranscriptPrefix = "\x00command-card\x00"
|
|
|
|
func renderCommandCardTranscript(card commandCard) string {
|
|
return commandCardTranscriptPrefix + renderCommandCard(card)
|
|
}
|
|
|
|
func commandCardTranscriptPayload(text string) (string, bool) {
|
|
if !strings.HasPrefix(text, commandCardTranscriptPrefix) {
|
|
return text, false
|
|
}
|
|
return strings.TrimPrefix(text, commandCardTranscriptPrefix), true
|
|
}
|
|
|
|
// planCardTranscriptPrefix tags the plan-step detail card so it renders through
|
|
// renderPlanCardRow — a minimal variant (dim grey border, calm status-tinted
|
|
// title, no loud lime headers) — instead of the standard command-card styling.
|
|
const planCardTranscriptPrefix = "\x00plan-card\x00"
|
|
|
|
// renderPlanCard formats a plan-step detail card with the same structured text
|
|
// as a command card, but tagged so it renders minimally.
|
|
func renderPlanCard(output commandOutput) string {
|
|
return planCardTranscriptPrefix + formatCommandOutput(output)
|
|
}
|
|
|
|
func planCardTranscriptPayload(text string) (string, bool) {
|
|
if !strings.HasPrefix(text, planCardTranscriptPrefix) {
|
|
return text, false
|
|
}
|
|
return strings.TrimPrefix(text, planCardTranscriptPrefix), true
|
|
}
|
|
|
|
func renderCommandCard(card commandCard) string {
|
|
lines := []string{}
|
|
|
|
title := compactCommandOutputText(card.Title)
|
|
if title == "" {
|
|
title = "Zero"
|
|
}
|
|
lines = append(lines, title)
|
|
|
|
if summary := compactCommandCardList(card.Summary); len(summary) > 0 {
|
|
lines = append(lines, strings.Join(summary, " | "))
|
|
}
|
|
|
|
for _, section := range card.Sections {
|
|
lines = append(lines, formatCommandCardSection(section)...)
|
|
}
|
|
|
|
if actions := compactCommandCardList(card.Actions); len(actions) > 0 {
|
|
lines = append(lines, "actions: "+strings.Join(actions, " | "))
|
|
}
|
|
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
func formatCommandCardSection(section commandCardSection) []string {
|
|
lines := []string{}
|
|
|
|
if title := compactCommandOutputText(section.Title); title != "" {
|
|
lines = append(lines, title)
|
|
}
|
|
|
|
keyWidth := 0
|
|
fields := make([]commandField, 0, len(section.Fields))
|
|
for _, field := range section.Fields {
|
|
key := compactCommandOutputText(field.Key)
|
|
value := compactCommandOutputText(field.Value)
|
|
if key == "" || value == "" {
|
|
continue
|
|
}
|
|
if len(key) > keyWidth {
|
|
keyWidth = len(key)
|
|
}
|
|
fields = append(fields, commandField{Key: key, Value: value})
|
|
}
|
|
for _, field := range fields {
|
|
lines = append(lines, " "+field.Key+strings.Repeat(" ", keyWidth-len(field.Key)+2)+field.Value)
|
|
}
|
|
|
|
for _, line := range section.Lines {
|
|
if text := compactCommandOutputText(line); text != "" {
|
|
lines = append(lines, " "+text)
|
|
}
|
|
}
|
|
for _, row := range section.Rows {
|
|
if text := compactCommandOutputText(row.Text); text != "" {
|
|
lines = append(lines, " - "+text)
|
|
}
|
|
}
|
|
|
|
return lines
|
|
}
|
|
|
|
func compactCommandCardList(values []string) []string {
|
|
compacted := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
if text := compactCommandOutputText(value); text != "" {
|
|
compacted = append(compacted, text)
|
|
}
|
|
}
|
|
return compacted
|
|
}
|
|
|
|
func formatCommandOutput(output commandOutput) string {
|
|
lines := []string{}
|
|
|
|
status := normalizeCommandStatus(output.Status)
|
|
title := compactCommandOutputText(output.Title)
|
|
if title == "" {
|
|
lines = append(lines, "Zero")
|
|
} else {
|
|
lines = append(lines, title)
|
|
}
|
|
lines = append(lines, "status: "+string(status))
|
|
|
|
for _, section := range output.Sections {
|
|
lines = append(lines, formatCommandSection(section)...)
|
|
}
|
|
for _, hint := range output.Hints {
|
|
if text := compactCommandOutputText(hint); text != "" {
|
|
lines = append(lines, "hint: "+text)
|
|
}
|
|
}
|
|
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
func formatCommandSection(section commandSection) []string {
|
|
lines := []string{}
|
|
|
|
if title := compactCommandOutputText(section.Title); title != "" {
|
|
lines = append(lines, title)
|
|
}
|
|
for _, field := range section.Fields {
|
|
key := compactCommandOutputText(field.Key)
|
|
value := compactCommandOutputText(field.Value)
|
|
if key == "" || value == "" {
|
|
continue
|
|
}
|
|
lines = append(lines, " "+key+": "+value)
|
|
}
|
|
for _, line := range section.Lines {
|
|
if text := compactCommandOutputText(line); text != "" {
|
|
lines = append(lines, " "+text)
|
|
}
|
|
}
|
|
for _, row := range section.Rows {
|
|
if text := compactCommandOutputText(row.Text); text != "" {
|
|
lines = append(lines, " - "+text)
|
|
}
|
|
}
|
|
for _, hint := range section.Hints {
|
|
if text := compactCommandOutputText(hint); text != "" {
|
|
lines = append(lines, " hint: "+text)
|
|
}
|
|
}
|
|
|
|
return lines
|
|
}
|
|
|
|
func normalizeCommandStatus(status commandStatus) commandStatus {
|
|
switch status {
|
|
case commandStatusOK, commandStatusWarning, commandStatusBlocked, commandStatusInfo:
|
|
return status
|
|
default:
|
|
return commandStatusInfo
|
|
}
|
|
}
|
|
|
|
func compactCommandOutputText(text string) string {
|
|
text = strings.Join(strings.Fields(text), " ")
|
|
return redaction.RedactString(text, redaction.Options{})
|
|
}
|
|
|
|
func renderCommandOutput(output commandOutput) string {
|
|
// Route every command-info screen (/tools, /permissions, /context, /config,
|
|
// /sessions, doctor, …) through the styled command-card renderer so it gets
|
|
// real visual hierarchy (accent group headers, two-tone command rows) instead
|
|
// of the flat dim-grey system note. The structured text is unchanged — the
|
|
// prefix just selects the card renderer in renderRowModeUncached.
|
|
return commandCardTranscriptPrefix + formatCommandOutput(output)
|
|
}
|
|
|
|
func commandBullet(value string) string {
|
|
return "- " + compactCommandOutputText(value)
|
|
}
|
|
|
|
func boolText(value bool) string {
|
|
if value {
|
|
return "yes"
|
|
}
|
|
return "no"
|
|
}
|