Replace role beads with embedded TOML config files for role definitions. This is Phase 1 of gt-y1uvb - adds the config infrastructure without yet switching the daemon to use it. New files: - internal/config/roles.go: RoleDefinition types, LoadRoleDefinition() with layered override resolution (builtin → town → rig) - internal/config/roles/*.toml: 7 embedded role definitions - internal/config/roles_test.go: unit tests New command: - gt role def <role>: displays effective role configuration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
627 lines
17 KiB
Go
627 lines
17 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/steveyegge/gastown/internal/config"
|
|
"github.com/steveyegge/gastown/internal/style"
|
|
"github.com/steveyegge/gastown/internal/workspace"
|
|
)
|
|
|
|
// Environment variables for role detection
|
|
const (
|
|
EnvGTRole = "GT_ROLE"
|
|
EnvGTRoleHome = "GT_ROLE_HOME"
|
|
)
|
|
|
|
// RoleInfo contains information about a role and its detection source.
|
|
// This is the canonical struct for role detection - used by both GetRole()
|
|
// and detectRole() functions.
|
|
type RoleInfo struct {
|
|
Role Role `json:"role"`
|
|
Source string `json:"source"` // "env", "cwd", or "explicit"
|
|
Home string `json:"home"`
|
|
Rig string `json:"rig,omitempty"`
|
|
Polecat string `json:"polecat,omitempty"`
|
|
EnvRole string `json:"env_role,omitempty"` // Value of GT_ROLE if set
|
|
CwdRole Role `json:"cwd_role,omitempty"` // Role detected from cwd
|
|
Mismatch bool `json:"mismatch,omitempty"` // True if env != cwd detection
|
|
EnvIncomplete bool `json:"env_incomplete,omitempty"` // True if env was set but missing rig/polecat, filled from cwd
|
|
TownRoot string `json:"town_root,omitempty"`
|
|
WorkDir string `json:"work_dir,omitempty"` // Current working directory
|
|
}
|
|
|
|
var roleCmd = &cobra.Command{
|
|
Use: "role",
|
|
GroupID: GroupAgents,
|
|
Short: "Show or manage agent role",
|
|
Long: `Display the current agent role and its detection source.
|
|
|
|
Role is determined by:
|
|
1. GT_ROLE environment variable (authoritative if set)
|
|
2. Current working directory (fallback)
|
|
|
|
If both are available and disagree, a warning is shown.`,
|
|
RunE: runRoleShow,
|
|
}
|
|
|
|
var roleShowCmd = &cobra.Command{
|
|
Use: "show",
|
|
Short: "Show current role",
|
|
RunE: runRoleShow,
|
|
}
|
|
|
|
var roleHomeCmd = &cobra.Command{
|
|
Use: "home [ROLE]",
|
|
Short: "Show home directory for a role",
|
|
Long: `Show the canonical home directory for a role.
|
|
|
|
If no role is specified, shows the home for the current role.
|
|
|
|
Examples:
|
|
gt role home # Home for current role
|
|
gt role home mayor # Home for mayor
|
|
gt role home witness # Home for witness (requires --rig)`,
|
|
Args: cobra.MaximumNArgs(1),
|
|
RunE: runRoleHome,
|
|
}
|
|
|
|
var roleDetectCmd = &cobra.Command{
|
|
Use: "detect",
|
|
Short: "Force cwd-based role detection (debugging)",
|
|
Long: `Detect role from current working directory, ignoring GT_ROLE env var.
|
|
|
|
This is useful for debugging role detection issues.`,
|
|
RunE: runRoleDetect,
|
|
}
|
|
|
|
var roleListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List all known roles",
|
|
RunE: runRoleList,
|
|
}
|
|
|
|
var roleEnvCmd = &cobra.Command{
|
|
Use: "env",
|
|
Short: "Print export statements for current role",
|
|
Long: `Print shell export statements for the current role.
|
|
|
|
Role is determined from GT_ROLE environment variable or current working directory.
|
|
This is a read-only command that displays the current role's env vars.
|
|
|
|
Examples:
|
|
eval $(gt role env) # Export current role's env vars
|
|
gt role env # View what would be exported`,
|
|
RunE: runRoleEnv,
|
|
}
|
|
|
|
var roleDefCmd = &cobra.Command{
|
|
Use: "def <role>",
|
|
Short: "Display role definition (session, health, env config)",
|
|
Long: `Display the effective role definition after all overrides are applied.
|
|
|
|
Role configuration is layered:
|
|
1. Built-in defaults (embedded in binary)
|
|
2. Town-level overrides (~/.gt/roles/<role>.toml)
|
|
3. Rig-level overrides (<rig>/roles/<role>.toml)
|
|
|
|
Examples:
|
|
gt role def witness # Show witness role definition
|
|
gt role def crew # Show crew role definition`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runRoleDef,
|
|
}
|
|
|
|
// Flags for role home command
|
|
var (
|
|
roleRig string
|
|
rolePolecat string
|
|
)
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(roleCmd)
|
|
roleCmd.AddCommand(roleShowCmd)
|
|
roleCmd.AddCommand(roleHomeCmd)
|
|
roleCmd.AddCommand(roleDetectCmd)
|
|
roleCmd.AddCommand(roleListCmd)
|
|
roleCmd.AddCommand(roleEnvCmd)
|
|
roleCmd.AddCommand(roleDefCmd)
|
|
|
|
// Add --rig and --polecat flags to home command for overrides
|
|
roleHomeCmd.Flags().StringVar(&roleRig, "rig", "", "Rig name (required for rig-specific roles)")
|
|
roleHomeCmd.Flags().StringVar(&rolePolecat, "polecat", "", "Polecat/crew member name")
|
|
}
|
|
|
|
// GetRole returns the current role, checking GT_ROLE first then falling back to cwd.
|
|
// This is the canonical function for role detection.
|
|
func GetRole() (RoleInfo, error) {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return RoleInfo{}, fmt.Errorf("getting current directory: %w", err)
|
|
}
|
|
|
|
townRoot, err := workspace.FindFromCwd()
|
|
if err != nil {
|
|
return RoleInfo{}, fmt.Errorf("finding workspace: %w", err)
|
|
}
|
|
if townRoot == "" {
|
|
return RoleInfo{}, fmt.Errorf("not in a Gas Town workspace")
|
|
}
|
|
|
|
return GetRoleWithContext(cwd, townRoot)
|
|
}
|
|
|
|
// GetRoleWithContext returns role info given explicit cwd and town root.
|
|
func GetRoleWithContext(cwd, townRoot string) (RoleInfo, error) {
|
|
info := RoleInfo{
|
|
TownRoot: townRoot,
|
|
WorkDir: cwd,
|
|
}
|
|
|
|
// Check environment variable first
|
|
envRole := os.Getenv(EnvGTRole)
|
|
info.EnvRole = envRole
|
|
|
|
// Always detect from cwd for comparison/fallback
|
|
cwdCtx := detectRole(cwd, townRoot)
|
|
info.CwdRole = cwdCtx.Role
|
|
|
|
// Determine authoritative role
|
|
if envRole != "" {
|
|
// Parse env role - it might be simple ("mayor") or compound ("gastown/witness")
|
|
parsedRole, rig, polecat := parseRoleString(envRole)
|
|
info.Role = parsedRole
|
|
info.Rig = rig
|
|
info.Polecat = polecat
|
|
info.Source = "env"
|
|
|
|
// For simple role strings like "crew" or "polecat", also check
|
|
// GT_RIG and GT_CREW/GT_POLECAT env vars for the full identity
|
|
if info.Rig == "" {
|
|
if envRig := os.Getenv("GT_RIG"); envRig != "" {
|
|
info.Rig = envRig
|
|
}
|
|
}
|
|
if info.Polecat == "" {
|
|
if envCrew := os.Getenv("GT_CREW"); envCrew != "" {
|
|
info.Polecat = envCrew
|
|
} else if envPolecat := os.Getenv("GT_POLECAT"); envPolecat != "" {
|
|
info.Polecat = envPolecat
|
|
}
|
|
}
|
|
|
|
// If env is incomplete (missing rig/polecat for roles that need them),
|
|
// fill gaps from cwd detection and mark as incomplete
|
|
needsRig := parsedRole == RoleWitness || parsedRole == RoleRefinery || parsedRole == RolePolecat || parsedRole == RoleCrew
|
|
needsPolecat := parsedRole == RolePolecat || parsedRole == RoleCrew
|
|
|
|
if needsRig && info.Rig == "" && cwdCtx.Rig != "" {
|
|
info.Rig = cwdCtx.Rig
|
|
info.EnvIncomplete = true
|
|
}
|
|
if needsPolecat && info.Polecat == "" && cwdCtx.Polecat != "" {
|
|
info.Polecat = cwdCtx.Polecat
|
|
info.EnvIncomplete = true
|
|
}
|
|
|
|
// Check for mismatch with cwd detection
|
|
if cwdCtx.Role != RoleUnknown && cwdCtx.Role != parsedRole {
|
|
info.Mismatch = true
|
|
}
|
|
} else {
|
|
// Fall back to cwd detection - copy all fields from cwdCtx
|
|
info.Role = cwdCtx.Role
|
|
info.Rig = cwdCtx.Rig
|
|
info.Polecat = cwdCtx.Polecat
|
|
info.Source = "cwd"
|
|
}
|
|
|
|
// Determine home directory
|
|
info.Home = getRoleHome(info.Role, info.Rig, info.Polecat, townRoot)
|
|
|
|
return info, nil
|
|
}
|
|
|
|
// parseRoleString parses a role string like "mayor", "gastown/witness", or "gastown/polecats/alpha".
|
|
func parseRoleString(s string) (Role, string, string) {
|
|
s = strings.TrimSpace(s)
|
|
|
|
// Simple roles
|
|
switch s {
|
|
case "mayor":
|
|
return RoleMayor, "", ""
|
|
case "deacon":
|
|
return RoleDeacon, "", ""
|
|
}
|
|
|
|
// Compound roles: rig/role or rig/polecats/name or rig/crew/name
|
|
parts := strings.Split(s, "/")
|
|
if len(parts) < 2 {
|
|
// Unknown format, try to match as simple role
|
|
return Role(s), "", ""
|
|
}
|
|
|
|
rig := parts[0]
|
|
|
|
switch parts[1] {
|
|
case "witness":
|
|
return RoleWitness, rig, ""
|
|
case "refinery":
|
|
return RoleRefinery, rig, ""
|
|
case "polecats":
|
|
if len(parts) >= 3 {
|
|
return RolePolecat, rig, parts[2]
|
|
}
|
|
return RolePolecat, rig, ""
|
|
case "crew":
|
|
if len(parts) >= 3 {
|
|
return RoleCrew, rig, parts[2]
|
|
}
|
|
return RoleCrew, rig, ""
|
|
default:
|
|
// Might be rig/polecatName format
|
|
return RolePolecat, rig, parts[1]
|
|
}
|
|
}
|
|
|
|
// ActorString returns the actor identity string for beads attribution.
|
|
// Format matches beads created_by convention:
|
|
// - Simple roles: "mayor", "deacon"
|
|
// - Rig-specific: "gastown/witness", "gastown/refinery"
|
|
// - Workers: "gastown/crew/max", "gastown/polecats/Toast"
|
|
func (info RoleInfo) ActorString() string {
|
|
switch info.Role {
|
|
case RoleMayor:
|
|
return "mayor"
|
|
case RoleDeacon:
|
|
return "deacon"
|
|
case RoleWitness:
|
|
if info.Rig != "" {
|
|
return fmt.Sprintf("%s/witness", info.Rig)
|
|
}
|
|
return "witness"
|
|
case RoleRefinery:
|
|
if info.Rig != "" {
|
|
return fmt.Sprintf("%s/refinery", info.Rig)
|
|
}
|
|
return "refinery"
|
|
case RolePolecat:
|
|
if info.Rig != "" && info.Polecat != "" {
|
|
return fmt.Sprintf("%s/polecats/%s", info.Rig, info.Polecat)
|
|
}
|
|
return "polecat"
|
|
case RoleCrew:
|
|
if info.Rig != "" && info.Polecat != "" {
|
|
return fmt.Sprintf("%s/crew/%s", info.Rig, info.Polecat)
|
|
}
|
|
return "crew"
|
|
default:
|
|
return string(info.Role)
|
|
}
|
|
}
|
|
|
|
// getRoleHome returns the canonical home directory for a role.
|
|
func getRoleHome(role Role, rig, polecat, townRoot string) string {
|
|
switch role {
|
|
case RoleMayor:
|
|
return filepath.Join(townRoot, "mayor")
|
|
case RoleDeacon:
|
|
return filepath.Join(townRoot, "deacon")
|
|
case RoleWitness:
|
|
if rig == "" {
|
|
return ""
|
|
}
|
|
return filepath.Join(townRoot, rig, "witness")
|
|
case RoleRefinery:
|
|
if rig == "" {
|
|
return ""
|
|
}
|
|
return filepath.Join(townRoot, rig, "refinery", "rig")
|
|
case RolePolecat:
|
|
if rig == "" || polecat == "" {
|
|
return ""
|
|
}
|
|
return filepath.Join(townRoot, rig, "polecats", polecat, "rig")
|
|
case RoleCrew:
|
|
if rig == "" || polecat == "" {
|
|
return ""
|
|
}
|
|
return filepath.Join(townRoot, rig, "crew", polecat, "rig")
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func runRoleShow(cmd *cobra.Command, args []string) error {
|
|
info, err := GetRole()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Header
|
|
fmt.Printf("%s\n", style.Bold.Render(string(info.Role)))
|
|
fmt.Printf("Source: %s\n", info.Source)
|
|
|
|
if info.Home != "" {
|
|
fmt.Printf("Home: %s\n", info.Home)
|
|
}
|
|
|
|
if info.Rig != "" {
|
|
fmt.Printf("Rig: %s\n", info.Rig)
|
|
}
|
|
|
|
if info.Polecat != "" {
|
|
fmt.Printf("Worker: %s\n", info.Polecat)
|
|
}
|
|
|
|
// Show mismatch warning
|
|
if info.Mismatch {
|
|
fmt.Println()
|
|
fmt.Printf("%s\n", style.Bold.Render("⚠️ ROLE MISMATCH"))
|
|
fmt.Printf(" GT_ROLE=%s (authoritative)\n", info.EnvRole)
|
|
fmt.Printf(" cwd suggests: %s\n", info.CwdRole)
|
|
fmt.Println()
|
|
fmt.Println("The GT_ROLE env var takes precedence, but you may be in the wrong directory.")
|
|
fmt.Printf("Expected home: %s\n", info.Home)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runRoleHome(cmd *cobra.Command, args []string) error {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("getting current directory: %w", err)
|
|
}
|
|
|
|
townRoot, err := workspace.FindFromCwd()
|
|
if err != nil {
|
|
return fmt.Errorf("finding workspace: %w", err)
|
|
}
|
|
if townRoot == "" {
|
|
return fmt.Errorf("not in a Gas Town workspace")
|
|
}
|
|
|
|
// Validate flag combinations: --polecat requires --rig to prevent strange merges
|
|
if rolePolecat != "" && roleRig == "" {
|
|
return fmt.Errorf("--polecat requires --rig to be specified")
|
|
}
|
|
|
|
// Start with current role detection (from env vars or cwd)
|
|
info, err := GetRole()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
role := info.Role
|
|
rig := info.Rig
|
|
polecat := info.Polecat
|
|
|
|
// Apply overrides from arguments/flags
|
|
if len(args) > 0 {
|
|
role, _, _ = parseRoleString(args[0])
|
|
}
|
|
if roleRig != "" {
|
|
rig = roleRig
|
|
}
|
|
if rolePolecat != "" {
|
|
polecat = rolePolecat
|
|
}
|
|
|
|
home := getRoleHome(role, rig, polecat, townRoot)
|
|
if home == "" {
|
|
return fmt.Errorf("cannot determine home for role %s (rig=%q, polecat=%q)", role, rig, polecat)
|
|
}
|
|
|
|
// Warn if computed home doesn't match cwd
|
|
if home != cwd && !strings.HasPrefix(cwd, home) {
|
|
fmt.Fprintf(os.Stderr, "⚠️ Warning: cwd (%s) is not within role home (%s)\n", cwd, home)
|
|
}
|
|
|
|
fmt.Println(home)
|
|
return nil
|
|
}
|
|
|
|
func runRoleDetect(cmd *cobra.Command, args []string) error {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("getting current directory: %w", err)
|
|
}
|
|
|
|
townRoot, err := workspace.FindFromCwd()
|
|
if err != nil {
|
|
return fmt.Errorf("finding workspace: %w", err)
|
|
}
|
|
if townRoot == "" {
|
|
return fmt.Errorf("not in a Gas Town workspace")
|
|
}
|
|
|
|
ctx := detectRole(cwd, townRoot)
|
|
|
|
fmt.Printf("%s (from cwd)\n", style.Bold.Render(string(ctx.Role)))
|
|
fmt.Printf("Directory: %s\n", cwd)
|
|
|
|
if ctx.Rig != "" {
|
|
fmt.Printf("Rig: %s\n", ctx.Rig)
|
|
}
|
|
if ctx.Polecat != "" {
|
|
fmt.Printf("Worker: %s\n", ctx.Polecat)
|
|
}
|
|
|
|
// Check if env var disagrees
|
|
envRole := os.Getenv(EnvGTRole)
|
|
if envRole != "" {
|
|
parsedRole, _, _ := parseRoleString(envRole)
|
|
if parsedRole != ctx.Role {
|
|
fmt.Println()
|
|
fmt.Printf("%s\n", style.Bold.Render("⚠️ Mismatch with $GT_ROLE"))
|
|
fmt.Printf(" $GT_ROLE=%s\n", envRole)
|
|
fmt.Println(" The env var takes precedence in normal operation.")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runRoleList(cmd *cobra.Command, args []string) error {
|
|
roles := []struct {
|
|
name Role
|
|
desc string
|
|
}{
|
|
{RoleMayor, "Global coordinator at mayor/"},
|
|
{RoleDeacon, "Background supervisor daemon"},
|
|
{RoleWitness, "Per-rig polecat lifecycle manager"},
|
|
{RoleRefinery, "Per-rig merge queue processor"},
|
|
{RolePolecat, "Ephemeral worker with own worktree"},
|
|
{RoleCrew, "Persistent worker with own worktree"},
|
|
}
|
|
|
|
fmt.Println("Available roles:")
|
|
fmt.Println()
|
|
for _, r := range roles {
|
|
fmt.Printf(" %-10s %s\n", style.Bold.Render(string(r.name)), r.desc)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func runRoleEnv(cmd *cobra.Command, args []string) error {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("getting current directory: %w", err)
|
|
}
|
|
|
|
townRoot, err := workspace.FindFromCwd()
|
|
if err != nil {
|
|
return fmt.Errorf("finding workspace: %w", err)
|
|
}
|
|
if townRoot == "" {
|
|
return fmt.Errorf("not in a Gas Town workspace")
|
|
}
|
|
|
|
// Get current role (read-only - from env vars or cwd)
|
|
info, err := GetRole()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
home := getRoleHome(info.Role, info.Rig, info.Polecat, townRoot)
|
|
if home == "" {
|
|
return fmt.Errorf("cannot determine home for role %s (rig=%q, polecat=%q)", info.Role, info.Rig, info.Polecat)
|
|
}
|
|
|
|
// Warn if env was incomplete and we filled from cwd
|
|
if info.EnvIncomplete {
|
|
fmt.Fprintf(os.Stderr, "⚠️ Warning: env vars incomplete, filled from cwd\n")
|
|
}
|
|
|
|
// Warn if computed home doesn't match cwd
|
|
if home != cwd && !strings.HasPrefix(cwd, home) {
|
|
fmt.Fprintf(os.Stderr, "⚠️ Warning: cwd (%s) is not within role home (%s)\n", cwd, home)
|
|
}
|
|
|
|
// Get canonical env vars from shared source of truth
|
|
envVars := config.AgentEnv(config.AgentEnvConfig{
|
|
Role: string(info.Role),
|
|
Rig: info.Rig,
|
|
AgentName: info.Polecat,
|
|
TownRoot: townRoot,
|
|
})
|
|
envVars[EnvGTRoleHome] = home
|
|
|
|
// Output in sorted order for consistent output
|
|
keys := make([]string, 0, len(envVars))
|
|
for k := range envVars {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, k := range keys {
|
|
fmt.Printf("export %s=%s\n", k, envVars[k])
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runRoleDef(cmd *cobra.Command, args []string) error {
|
|
roleName := args[0]
|
|
|
|
// Validate role name
|
|
validRoles := config.AllRoles()
|
|
isValid := false
|
|
for _, r := range validRoles {
|
|
if r == roleName {
|
|
isValid = true
|
|
break
|
|
}
|
|
}
|
|
if !isValid {
|
|
return fmt.Errorf("unknown role %q - valid roles: %s", roleName, strings.Join(validRoles, ", "))
|
|
}
|
|
|
|
// Determine town root and rig path
|
|
townRoot, _ := workspace.FindFromCwd()
|
|
rigPath := ""
|
|
if townRoot != "" {
|
|
// Try to get rig path if we're in a rig directory
|
|
if rigInfo, err := GetRole(); err == nil && rigInfo.Rig != "" {
|
|
rigPath = filepath.Join(townRoot, rigInfo.Rig)
|
|
}
|
|
}
|
|
|
|
// Load role definition with overrides
|
|
def, err := config.LoadRoleDefinition(townRoot, rigPath, roleName)
|
|
if err != nil {
|
|
return fmt.Errorf("loading role definition: %w", err)
|
|
}
|
|
|
|
// Display role info
|
|
fmt.Printf("%s %s\n", style.Bold.Render("Role:"), def.Role)
|
|
fmt.Printf("%s %s\n", style.Bold.Render("Scope:"), def.Scope)
|
|
fmt.Println()
|
|
|
|
// Session config
|
|
fmt.Println(style.Bold.Render("[session]"))
|
|
fmt.Printf(" pattern = %q\n", def.Session.Pattern)
|
|
fmt.Printf(" work_dir = %q\n", def.Session.WorkDir)
|
|
fmt.Printf(" needs_pre_sync = %v\n", def.Session.NeedsPreSync)
|
|
if def.Session.StartCommand != "" {
|
|
fmt.Printf(" start_command = %q\n", def.Session.StartCommand)
|
|
}
|
|
fmt.Println()
|
|
|
|
// Environment variables
|
|
if len(def.Env) > 0 {
|
|
fmt.Println(style.Bold.Render("[env]"))
|
|
envKeys := make([]string, 0, len(def.Env))
|
|
for k := range def.Env {
|
|
envKeys = append(envKeys, k)
|
|
}
|
|
sort.Strings(envKeys)
|
|
for _, k := range envKeys {
|
|
fmt.Printf(" %s = %q\n", k, def.Env[k])
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
// Health config
|
|
fmt.Println(style.Bold.Render("[health]"))
|
|
fmt.Printf(" ping_timeout = %q\n", def.Health.PingTimeout.String())
|
|
fmt.Printf(" consecutive_failures = %d\n", def.Health.ConsecutiveFailures)
|
|
fmt.Printf(" kill_cooldown = %q\n", def.Health.KillCooldown.String())
|
|
fmt.Printf(" stuck_threshold = %q\n", def.Health.StuckThreshold.String())
|
|
fmt.Println()
|
|
|
|
// Prompts
|
|
if def.Nudge != "" {
|
|
fmt.Printf("%s %s\n", style.Bold.Render("Nudge:"), def.Nudge)
|
|
}
|
|
if def.PromptTemplate != "" {
|
|
fmt.Printf("%s %s\n", style.Bold.Render("Template:"), def.PromptTemplate)
|
|
}
|
|
|
|
return nil
|
|
}
|