okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
f51cc1a
main
1package tui 2 3import ( 4"albedo/cli/internal/daemon" 5"slices" 6"strings" 7 8 tea"charm.land/bubbletea/v2" 9) 10 11type ChatCommand struct { 12Name string 13Description string 14} 15 16var CommonCommands = []ChatCommand { 17 {Name :"/a" ,Description :"browse sessions" }, 18 {Name :"/t" ,Description :"show or hide thinking" }, 19 {Name :"/v" ,Description :"expand or collapse code, output and diffs" }, 20 {Name :"/status" ,Description :"show state, workspace and usage" }, 21 {Name :"/q" ,Description :"quit" }, 22} 23 24var AppCommands = []ChatCommand { 25 {Name :"/link" ,Description :"inspect and confirm linked workspace groups" }, 26 {Name :"/ttl" ,Description :"inspect provider cache policy" }, 27 {Name :"/quota" ,Description :"inspect observed provider quota" }, 28 {Name :"/requests" ,Description :"inspect provider request diagnostics" }, 29 {Name :"/raise-cap" ,Description :"raise or restore the model context cap" }, 30 {Name :"/effort" ,Description :"choose reasoning effort" }, 31 {Name :"/reload" ,Description :"apply selected configuration and refresh models" }, 32 {Name :"/compact" ,Description :"compact durable context" }, 33 {Name :"/kernel" ,Description :"upgrade the session kernel" }, 34 {Name :"/model" ,Description :"choose a model" }, 35 {Name :"/context" ,Description :"inspect prepared context" }, 36 {Name :"/login" ,Description :"add or select a model provider" }, 37 {Name :"/new" ,Description :"start a new coding session" }, 38 {Name :"/sessions" ,Description :"switch to another session" }, 39 {Name :"/agents" ,Description :"watch and message this session's agents (ctrl+o)" }, 40 {Name :"/extensions" ,Description :"manage this session's extension plugins" }, 41 {Name :"/skills" ,Description :"manage loaded skills and per-skill defaults" }, 42 {Name :"/instructions" ,Description :"manage AGENTS.md and other instruction files" }, 43 {Name :"/mcp" ,Description :"manage MCP servers and authentication" }, 44 {Name :"/webhooks" ,Description :"manage signed webhooks and agent access" }, 45 {Name :"/tree" ,Description :"branch this session from a history checkpoint" }, 46 {Name :"/cd" ,Description :"move this session to another folder" }, 47} 48 49type CommandMenuModel struct { 50Dismissed string 51Catalog []daemon.SessionCommand 52Selected int 53} 54 55func NewCommandMenuModel ()CommandMenuModel { 56return CommandMenuModel {} 57} 58 59func (m CommandMenuModel )Matches (input string ) []ChatCommand { 60if ! strings .HasPrefix (input ,"/" )|| strings .Contains (input ,"\n" )|| input == m .Dismissed { 61return nil 62 } 63 64all := slices .Clone (AppCommands ) 65for _ ,cmd := range m .Catalog { 66all = append (all ,ChatCommand { 67Name :cmd .Name , 68Description :cmd .Description , 69 }) 70 } 71all = append (all ,CommonCommands ... ) 72 73// De-duplicate by name (last occurrence wins), placing exact match first. 74seen := make (map [string ]bool ,len (all )) 75var direct ,others []ChatCommand 76for _ ,cmd := range slices .Backward (all ) { 77if seen [cmd .Name ] { 78continue 79 } 80seen [cmd .Name ]= true 81if cmd .Name == input { 82direct = append (direct ,cmd ) 83 }else if strings .HasPrefix (cmd .Name ,input ) { 84others = append (others ,cmd ) 85 } 86 } 87slices .Reverse (others ) 88return append (direct ,others ... ) 89} 90 91// OnKey handles navigation within the command popup. Returns true if consumed. 92func (m * CommandMenuModel )OnKey (msg tea.KeyPressMsg ,input string ,replace func (string ),submit func (string ))bool { 93matches := m .Matches (input ) 94if len (matches )== 0 { 95return false 96 } 97 98idx := max (0 ,min (m .Selected ,len (matches )- 1 )) 99 100switch msg .String () { 101case "esc" : 102m .Dismissed = input 103case "up" : 104if idx > 0 { 105m .Selected = idx - 1 106 } 107case "down" : 108if idx < len (matches )- 1 { 109m .Selected = idx + 1 110 } 111case "tab" : 112replace (matches [idx ].Name ) 113case "enter" : 114submit (matches [idx ].Name ) 115default : 116m .Selected = 0 117return false 118 } 119return true 120} 121 122func (m CommandMenuModel )View (input string )string { 123matches := m .Matches (input ) 124if len (matches )== 0 { 125return "" 126 } 127 128idx := max (0 ,min (m .Selected ,len (matches )- 1 )) 129start := max (0 ,idx - 3 ) 130end := min (len (matches ),max (4 ,idx + 1 )) 131 132var b strings.Builder 133for i := start ;i < end ;i ++ { 134cmd := matches [i ] 135desc := cmd .Name + " " + DefaultStyles .Faint .Render (cmd .Description ) 136if i == idx { 137b .WriteString (selectedLine (selectBar ()+ " " + desc ,0 )) 138 }else { 139b .WriteString (" " ) 140b .WriteString (desc ) 141 } 142b .WriteByte ('\n' ) 143 } 144return b .String () 145}