Browse Source

fix: more commands cleanup

adamdotdevin 6 months ago
parent
commit
6e0e87fb2a

+ 29 - 17
packages/opencode/src/config/config.ts

@@ -127,6 +127,12 @@ export namespace Config {
     if (result.keybinds?.switch_mode_reverse && !result.keybinds.switch_agent_reverse) {
       result.keybinds.switch_agent_reverse = result.keybinds.switch_mode_reverse
     }
+    if (result.keybinds?.switch_agent && !result.keybinds.agent_cycle) {
+      result.keybinds.agent_cycle = result.keybinds.switch_agent
+    }
+    if (result.keybinds?.switch_agent_reverse && !result.keybinds.agent_cycle_reverse) {
+      result.keybinds.agent_cycle_reverse = result.keybinds.switch_agent_reverse
+    }
 
     if (!result.username) {
       const os = await import("os")
@@ -199,9 +205,12 @@ export namespace Config {
     .object({
       leader: z.string().optional().default("ctrl+x").describe("Leader key for keybind combinations"),
       app_help: z.string().optional().default("<leader>h").describe("Show help dialog"),
-      switch_agent: z.string().optional().default("tab").describe("Next agent"),
-      switch_agent_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
+      app_exit: z.string().optional().default("ctrl+c,<leader>q").describe("Exit the application"),
       editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
+      theme_list: z.string().optional().default("<leader>t").describe("List available themes"),
+      project_init: z.string().optional().default("<leader>i").describe("Create/update AGENTS.md"),
+      tool_details: z.string().optional().default("<leader>d").describe("Toggle tool details"),
+      thinking_blocks: z.string().optional().default("<leader>b").describe("Toggle thinking blocks"),
       session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
       session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
       session_list: z.string().optional().default("<leader>l").describe("List all sessions"),
@@ -209,18 +218,6 @@ export namespace Config {
       session_unshare: z.string().optional().default("none").describe("Unshare current session"),
       session_interrupt: z.string().optional().default("esc").describe("Interrupt current session"),
       session_compact: z.string().optional().default("<leader>c").describe("Compact the session"),
-      tool_details: z.string().optional().default("<leader>d").describe("Toggle tool details"),
-      thinking_blocks: z.string().optional().default("<leader>b").describe("Toggle thinking blocks"),
-      model_list: z.string().optional().default("<leader>m").describe("List available models"),
-      agent_list: z.string().optional().default("<leader>a").describe("List agents"),
-      model_cycle_recent: z.string().optional().default("f2").describe("Next recent model"),
-      model_cycle_recent_reverse: z.string().optional().default("shift+f2").describe("Previous recent model"),
-      theme_list: z.string().optional().default("<leader>t").describe("List available themes"),
-      project_init: z.string().optional().default("<leader>i").describe("Create/update AGENTS.md"),
-      input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
-      input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
-      input_submit: z.string().optional().default("enter").describe("Submit input"),
-      input_newline: z.string().optional().default("shift+enter,ctrl+j").describe("Insert newline in input"),
       messages_page_up: z.string().optional().default("pgup").describe("Scroll messages up by one page"),
       messages_page_down: z.string().optional().default("pgdown").describe("Scroll messages down by one page"),
       messages_half_page_up: z.string().optional().default("ctrl+alt+u").describe("Scroll messages up by half page"),
@@ -234,14 +231,29 @@ export namespace Config {
       messages_copy: z.string().optional().default("<leader>y").describe("Copy message"),
       messages_undo: z.string().optional().default("<leader>u").describe("Undo message"),
       messages_redo: z.string().optional().default("<leader>r").describe("Redo message"),
-      app_exit: z.string().optional().default("ctrl+c,<leader>q").describe("Exit the application"),
+      model_list: z.string().optional().default("<leader>m").describe("List available models"),
+      model_cycle_recent: z.string().optional().default("f2").describe("Next recent model"),
+      model_cycle_recent_reverse: z.string().optional().default("shift+f2").describe("Previous recent model"),
+      agent_list: z.string().optional().default("<leader>a").describe("List agents"),
+      agent_cycle: z.string().optional().default("tab").describe("Next agent"),
+      agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
+      input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
+      input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
+      input_submit: z.string().optional().default("enter").describe("Submit input"),
+      input_newline: z.string().optional().default("shift+enter,ctrl+j").describe("Insert newline in input"),
       // Deprecated commands
-      switch_mode: z.string().optional().default("none").describe("@deprecated use switch_agent. Next mode"),
+      switch_mode: z.string().optional().default("none").describe("@deprecated use agent_cycle. Next mode"),
       switch_mode_reverse: z
         .string()
         .optional()
         .default("none")
-        .describe("@deprecated use switch_agent_reverse. Previous mode"),
+        .describe("@deprecated use agent_cycle_reverse. Previous mode"),
+      switch_agent: z.string().optional().default("tab").describe("@deprecated use agent_cycle. Next agent"),
+      switch_agent_reverse: z
+        .string()
+        .optional()
+        .default("shift+tab")
+        .describe("@deprecated use agent_cycle_reverse. Previous agent"),
       file_list: z.string().optional().default("none").describe("@deprecated Currently not available. List files"),
       file_close: z.string().optional().default("none").describe("@deprecated Close file"),
       file_search: z.string().optional().default("none").describe("@deprecated Search file"),

+ 1 - 1
packages/opencode/src/server/server.ts

@@ -1099,7 +1099,7 @@ export namespace Server {
       .post(
         "/tui/execute-command",
         describeRoute({
-          description: "Execute a TUI command (e.g. switch_agent)",
+          description: "Execute a TUI command (e.g. agent_cycle)",
           operationId: "tui.executeCommand",
           responses: {
             200: {

+ 2 - 2
packages/sdk/go/.stats.yml

@@ -1,4 +1,4 @@
 configured_endpoints: 36
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-a881262c7de4ab59bdfbfc6e30a23c47dee465d7270ffb867b760b0103aff8ed.yml
-openapi_spec_hash: 7dbb6f96f5c26a25c849e50298f58586
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-d0eaf92deaa53a25bbbc3181540ad73ed5a4aec6381ac08d8122e24318e5e455.yml
+openapi_spec_hash: 22196d859c0711e564b9538d988abda6
 config_hash: 8d85a768523cff92b85ef06c443d49fa

+ 69 - 54
packages/sdk/go/config.go

@@ -1655,19 +1655,25 @@ func (r ConfigShare) IsKnown() bool {
 }
 
 type KeybindsConfig struct {
+	// Next agent
+	AgentCycle string `json:"agent_cycle,required"`
+	// Previous agent
+	AgentCycleReverse string `json:"agent_cycle_reverse,required"`
+	// List agents
+	AgentList string `json:"agent_list,required"`
 	// Exit the application
 	AppExit string `json:"app_exit,required"`
 	// Show help dialog
 	AppHelp string `json:"app_help,required"`
 	// Open external editor
 	EditorOpen string `json:"editor_open,required"`
-	// Close file
+	// @deprecated Close file
 	FileClose string `json:"file_close,required"`
-	// Split/unified diff
+	// @deprecated Split/unified diff
 	FileDiffToggle string `json:"file_diff_toggle,required"`
-	// List files
+	// @deprecated Currently not available. List files
 	FileList string `json:"file_list,required"`
-	// Search file
+	// @deprecated Search file
 	FileSearch string `json:"file_search,required"`
 	// Clear input field
 	InputClear string `json:"input_clear,required"`
@@ -1689,15 +1695,15 @@ type KeybindsConfig struct {
 	MessagesHalfPageUp string `json:"messages_half_page_up,required"`
 	// Navigate to last message
 	MessagesLast string `json:"messages_last,required"`
-	// Toggle layout
+	// @deprecated Toggle layout
 	MessagesLayoutToggle string `json:"messages_layout_toggle,required"`
-	// Navigate to next message
+	// @deprecated Navigate to next message
 	MessagesNext string `json:"messages_next,required"`
 	// Scroll messages down by one page
 	MessagesPageDown string `json:"messages_page_down,required"`
 	// Scroll messages up by one page
 	MessagesPageUp string `json:"messages_page_up,required"`
-	// Navigate to previous message
+	// @deprecated Navigate to previous message
 	MessagesPrevious string `json:"messages_previous,required"`
 	// Redo message
 	MessagesRedo string `json:"messages_redo,required"`
@@ -1705,6 +1711,10 @@ type KeybindsConfig struct {
 	MessagesRevert string `json:"messages_revert,required"`
 	// Undo message
 	MessagesUndo string `json:"messages_undo,required"`
+	// Next recent model
+	ModelCycleRecent string `json:"model_cycle_recent,required"`
+	// Previous recent model
+	ModelCycleRecentReverse string `json:"model_cycle_recent_reverse,required"`
 	// List available models
 	ModelList string `json:"model_list,required"`
 	// Create/update AGENTS.md
@@ -1723,13 +1733,13 @@ type KeybindsConfig struct {
 	SessionShare string `json:"session_share,required"`
 	// Unshare current session
 	SessionUnshare string `json:"session_unshare,required"`
-	// Next agent
+	// @deprecated use agent_cycle. Next agent
 	SwitchAgent string `json:"switch_agent,required"`
-	// Previous agent
+	// @deprecated use agent_cycle_reverse. Previous agent
 	SwitchAgentReverse string `json:"switch_agent_reverse,required"`
-	// @deprecated use switch_agent. Next mode
+	// @deprecated use agent_cycle. Next mode
 	SwitchMode string `json:"switch_mode,required"`
-	// @deprecated use switch_agent_reverse. Previous mode
+	// @deprecated use agent_cycle_reverse. Previous mode
 	SwitchModeReverse string `json:"switch_mode_reverse,required"`
 	// List available themes
 	ThemeList string `json:"theme_list,required"`
@@ -1742,49 +1752,54 @@ type KeybindsConfig struct {
 
 // keybindsConfigJSON contains the JSON metadata for the struct [KeybindsConfig]
 type keybindsConfigJSON struct {
-	AppExit              apijson.Field
-	AppHelp              apijson.Field
-	EditorOpen           apijson.Field
-	FileClose            apijson.Field
-	FileDiffToggle       apijson.Field
-	FileList             apijson.Field
-	FileSearch           apijson.Field
-	InputClear           apijson.Field
-	InputNewline         apijson.Field
-	InputPaste           apijson.Field
-	InputSubmit          apijson.Field
-	Leader               apijson.Field
-	MessagesCopy         apijson.Field
-	MessagesFirst        apijson.Field
-	MessagesHalfPageDown apijson.Field
-	MessagesHalfPageUp   apijson.Field
-	MessagesLast         apijson.Field
-	MessagesLayoutToggle apijson.Field
-	MessagesNext         apijson.Field
-	MessagesPageDown     apijson.Field
-	MessagesPageUp       apijson.Field
-	MessagesPrevious     apijson.Field
-	MessagesRedo         apijson.Field
-	MessagesRevert       apijson.Field
-	MessagesUndo         apijson.Field
-	ModelList            apijson.Field
-	ProjectInit          apijson.Field
-	SessionCompact       apijson.Field
-	SessionExport        apijson.Field
-	SessionInterrupt     apijson.Field
-	SessionList          apijson.Field
-	SessionNew           apijson.Field
-	SessionShare         apijson.Field
-	SessionUnshare       apijson.Field
-	SwitchAgent          apijson.Field
-	SwitchAgentReverse   apijson.Field
-	SwitchMode           apijson.Field
-	SwitchModeReverse    apijson.Field
-	ThemeList            apijson.Field
-	ThinkingBlocks       apijson.Field
-	ToolDetails          apijson.Field
-	raw                  string
-	ExtraFields          map[string]apijson.Field
+	AgentCycle              apijson.Field
+	AgentCycleReverse       apijson.Field
+	AgentList               apijson.Field
+	AppExit                 apijson.Field
+	AppHelp                 apijson.Field
+	EditorOpen              apijson.Field
+	FileClose               apijson.Field
+	FileDiffToggle          apijson.Field
+	FileList                apijson.Field
+	FileSearch              apijson.Field
+	InputClear              apijson.Field
+	InputNewline            apijson.Field
+	InputPaste              apijson.Field
+	InputSubmit             apijson.Field
+	Leader                  apijson.Field
+	MessagesCopy            apijson.Field
+	MessagesFirst           apijson.Field
+	MessagesHalfPageDown    apijson.Field
+	MessagesHalfPageUp      apijson.Field
+	MessagesLast            apijson.Field
+	MessagesLayoutToggle    apijson.Field
+	MessagesNext            apijson.Field
+	MessagesPageDown        apijson.Field
+	MessagesPageUp          apijson.Field
+	MessagesPrevious        apijson.Field
+	MessagesRedo            apijson.Field
+	MessagesRevert          apijson.Field
+	MessagesUndo            apijson.Field
+	ModelCycleRecent        apijson.Field
+	ModelCycleRecentReverse apijson.Field
+	ModelList               apijson.Field
+	ProjectInit             apijson.Field
+	SessionCompact          apijson.Field
+	SessionExport           apijson.Field
+	SessionInterrupt        apijson.Field
+	SessionList             apijson.Field
+	SessionNew              apijson.Field
+	SessionShare            apijson.Field
+	SessionUnshare          apijson.Field
+	SwitchAgent             apijson.Field
+	SwitchAgentReverse      apijson.Field
+	SwitchMode              apijson.Field
+	SwitchModeReverse       apijson.Field
+	ThemeList               apijson.Field
+	ThinkingBlocks          apijson.Field
+	ToolDetails             apijson.Field
+	raw                     string
+	ExtraFields             map[string]apijson.Field
 }
 
 func (r *KeybindsConfig) UnmarshalJSON(data []byte) (err error) {

+ 121 - 123
packages/sdk/go/event.go

@@ -54,13 +54,13 @@ type EventListResponse struct {
 	// [EventListResponseEventMessageRemovedProperties],
 	// [EventListResponseEventMessagePartUpdatedProperties],
 	// [EventListResponseEventMessagePartRemovedProperties],
-	// [EventListResponseEventStorageWriteProperties],
-	// [EventListResponseEventFileEditedProperties], [interface{}], [Permission],
+	// [EventListResponseEventStorageWriteProperties], [Permission],
 	// [EventListResponseEventPermissionRepliedProperties],
+	// [EventListResponseEventFileEditedProperties],
 	// [EventListResponseEventSessionUpdatedProperties],
 	// [EventListResponseEventSessionDeletedProperties],
 	// [EventListResponseEventSessionIdleProperties],
-	// [EventListResponseEventSessionErrorProperties],
+	// [EventListResponseEventSessionErrorProperties], [interface{}],
 	// [EventListResponseEventFileWatcherUpdatedProperties],
 	// [EventListResponseEventIdeInstalledProperties].
 	Properties interface{}           `json:"properties,required"`
@@ -100,12 +100,11 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
 // [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
 // [EventListResponseEventMessagePartUpdated],
 // [EventListResponseEventMessagePartRemoved],
-// [EventListResponseEventStorageWrite], [EventListResponseEventFileEdited],
-// [EventListResponseEventServerConnected],
-// [EventListResponseEventPermissionUpdated],
-// [EventListResponseEventPermissionReplied],
+// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
+// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
 // [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
 // [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
+// [EventListResponseEventServerConnected],
 // [EventListResponseEventFileWatcherUpdated],
 // [EventListResponseEventIdeInstalled].
 func (r EventListResponse) AsUnion() EventListResponseUnion {
@@ -117,12 +116,11 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
 // [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
 // [EventListResponseEventMessagePartUpdated],
 // [EventListResponseEventMessagePartRemoved],
-// [EventListResponseEventStorageWrite], [EventListResponseEventFileEdited],
-// [EventListResponseEventServerConnected],
-// [EventListResponseEventPermissionUpdated],
-// [EventListResponseEventPermissionReplied],
+// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
+// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
 // [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
 // [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
+// [EventListResponseEventServerConnected],
 // [EventListResponseEventFileWatcherUpdated] or
 // [EventListResponseEventIdeInstalled].
 type EventListResponseUnion interface {
@@ -168,16 +166,6 @@ func init() {
 			Type:               reflect.TypeOf(EventListResponseEventStorageWrite{}),
 			DiscriminatorValue: "storage.write",
 		},
-		apijson.UnionVariant{
-			TypeFilter:         gjson.JSON,
-			Type:               reflect.TypeOf(EventListResponseEventFileEdited{}),
-			DiscriminatorValue: "file.edited",
-		},
-		apijson.UnionVariant{
-			TypeFilter:         gjson.JSON,
-			Type:               reflect.TypeOf(EventListResponseEventServerConnected{}),
-			DiscriminatorValue: "server.connected",
-		},
 		apijson.UnionVariant{
 			TypeFilter:         gjson.JSON,
 			Type:               reflect.TypeOf(EventListResponseEventPermissionUpdated{}),
@@ -188,6 +176,11 @@ func init() {
 			Type:               reflect.TypeOf(EventListResponseEventPermissionReplied{}),
 			DiscriminatorValue: "permission.replied",
 		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventFileEdited{}),
+			DiscriminatorValue: "file.edited",
+		},
 		apijson.UnionVariant{
 			TypeFilter:         gjson.JSON,
 			Type:               reflect.TypeOf(EventListResponseEventSessionUpdated{}),
@@ -208,6 +201,11 @@ func init() {
 			Type:               reflect.TypeOf(EventListResponseEventSessionError{}),
 			DiscriminatorValue: "session.error",
 		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventServerConnected{}),
+			DiscriminatorValue: "server.connected",
+		},
 		apijson.UnionVariant{
 			TypeFilter:         gjson.JSON,
 			Type:               reflect.TypeOf(EventListResponseEventFileWatcherUpdated{}),
@@ -651,105 +649,6 @@ func (r EventListResponseEventStorageWriteType) IsKnown() bool {
 	return false
 }
 
-type EventListResponseEventFileEdited struct {
-	Properties EventListResponseEventFileEditedProperties `json:"properties,required"`
-	Type       EventListResponseEventFileEditedType       `json:"type,required"`
-	JSON       eventListResponseEventFileEditedJSON       `json:"-"`
-}
-
-// eventListResponseEventFileEditedJSON contains the JSON metadata for the struct
-// [EventListResponseEventFileEdited]
-type eventListResponseEventFileEditedJSON struct {
-	Properties  apijson.Field
-	Type        apijson.Field
-	raw         string
-	ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventFileEdited) UnmarshalJSON(data []byte) (err error) {
-	return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventFileEditedJSON) RawJSON() string {
-	return r.raw
-}
-
-func (r EventListResponseEventFileEdited) implementsEventListResponse() {}
-
-type EventListResponseEventFileEditedProperties struct {
-	File string                                         `json:"file,required"`
-	JSON eventListResponseEventFileEditedPropertiesJSON `json:"-"`
-}
-
-// eventListResponseEventFileEditedPropertiesJSON contains the JSON metadata for
-// the struct [EventListResponseEventFileEditedProperties]
-type eventListResponseEventFileEditedPropertiesJSON struct {
-	File        apijson.Field
-	raw         string
-	ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventFileEditedProperties) UnmarshalJSON(data []byte) (err error) {
-	return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventFileEditedPropertiesJSON) RawJSON() string {
-	return r.raw
-}
-
-type EventListResponseEventFileEditedType string
-
-const (
-	EventListResponseEventFileEditedTypeFileEdited EventListResponseEventFileEditedType = "file.edited"
-)
-
-func (r EventListResponseEventFileEditedType) IsKnown() bool {
-	switch r {
-	case EventListResponseEventFileEditedTypeFileEdited:
-		return true
-	}
-	return false
-}
-
-type EventListResponseEventServerConnected struct {
-	Properties interface{}                               `json:"properties,required"`
-	Type       EventListResponseEventServerConnectedType `json:"type,required"`
-	JSON       eventListResponseEventServerConnectedJSON `json:"-"`
-}
-
-// eventListResponseEventServerConnectedJSON contains the JSON metadata for the
-// struct [EventListResponseEventServerConnected]
-type eventListResponseEventServerConnectedJSON struct {
-	Properties  apijson.Field
-	Type        apijson.Field
-	raw         string
-	ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventServerConnected) UnmarshalJSON(data []byte) (err error) {
-	return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventServerConnectedJSON) RawJSON() string {
-	return r.raw
-}
-
-func (r EventListResponseEventServerConnected) implementsEventListResponse() {}
-
-type EventListResponseEventServerConnectedType string
-
-const (
-	EventListResponseEventServerConnectedTypeServerConnected EventListResponseEventServerConnectedType = "server.connected"
-)
-
-func (r EventListResponseEventServerConnectedType) IsKnown() bool {
-	switch r {
-	case EventListResponseEventServerConnectedTypeServerConnected:
-		return true
-	}
-	return false
-}
-
 type EventListResponseEventPermissionUpdated struct {
 	Properties Permission                                  `json:"properties,required"`
 	Type       EventListResponseEventPermissionUpdatedType `json:"type,required"`
@@ -853,6 +752,66 @@ func (r EventListResponseEventPermissionRepliedType) IsKnown() bool {
 	return false
 }
 
+type EventListResponseEventFileEdited struct {
+	Properties EventListResponseEventFileEditedProperties `json:"properties,required"`
+	Type       EventListResponseEventFileEditedType       `json:"type,required"`
+	JSON       eventListResponseEventFileEditedJSON       `json:"-"`
+}
+
+// eventListResponseEventFileEditedJSON contains the JSON metadata for the struct
+// [EventListResponseEventFileEdited]
+type eventListResponseEventFileEditedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventFileEdited) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventFileEditedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventFileEdited) implementsEventListResponse() {}
+
+type EventListResponseEventFileEditedProperties struct {
+	File string                                         `json:"file,required"`
+	JSON eventListResponseEventFileEditedPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventFileEditedPropertiesJSON contains the JSON metadata for
+// the struct [EventListResponseEventFileEditedProperties]
+type eventListResponseEventFileEditedPropertiesJSON struct {
+	File        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventFileEditedProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventFileEditedPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventFileEditedType string
+
+const (
+	EventListResponseEventFileEditedTypeFileEdited EventListResponseEventFileEditedType = "file.edited"
+)
+
+func (r EventListResponseEventFileEditedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventFileEditedTypeFileEdited:
+		return true
+	}
+	return false
+}
+
 type EventListResponseEventSessionUpdated struct {
 	Properties EventListResponseEventSessionUpdatedProperties `json:"properties,required"`
 	Type       EventListResponseEventSessionUpdatedType       `json:"type,required"`
@@ -1229,6 +1188,45 @@ func (r EventListResponseEventSessionErrorType) IsKnown() bool {
 	return false
 }
 
+type EventListResponseEventServerConnected struct {
+	Properties interface{}                               `json:"properties,required"`
+	Type       EventListResponseEventServerConnectedType `json:"type,required"`
+	JSON       eventListResponseEventServerConnectedJSON `json:"-"`
+}
+
+// eventListResponseEventServerConnectedJSON contains the JSON metadata for the
+// struct [EventListResponseEventServerConnected]
+type eventListResponseEventServerConnectedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventServerConnected) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventServerConnectedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventServerConnected) implementsEventListResponse() {}
+
+type EventListResponseEventServerConnectedType string
+
+const (
+	EventListResponseEventServerConnectedTypeServerConnected EventListResponseEventServerConnectedType = "server.connected"
+)
+
+func (r EventListResponseEventServerConnectedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventServerConnectedTypeServerConnected:
+		return true
+	}
+	return false
+}
+
 type EventListResponseEventFileWatcherUpdated struct {
 	Properties EventListResponseEventFileWatcherUpdatedProperties `json:"properties,required"`
 	Type       EventListResponseEventFileWatcherUpdatedType       `json:"type,required"`
@@ -1376,21 +1374,21 @@ const (
 	EventListResponseTypeMessagePartUpdated   EventListResponseType = "message.part.updated"
 	EventListResponseTypeMessagePartRemoved   EventListResponseType = "message.part.removed"
 	EventListResponseTypeStorageWrite         EventListResponseType = "storage.write"
-	EventListResponseTypeFileEdited           EventListResponseType = "file.edited"
-	EventListResponseTypeServerConnected      EventListResponseType = "server.connected"
 	EventListResponseTypePermissionUpdated    EventListResponseType = "permission.updated"
 	EventListResponseTypePermissionReplied    EventListResponseType = "permission.replied"
+	EventListResponseTypeFileEdited           EventListResponseType = "file.edited"
 	EventListResponseTypeSessionUpdated       EventListResponseType = "session.updated"
 	EventListResponseTypeSessionDeleted       EventListResponseType = "session.deleted"
 	EventListResponseTypeSessionIdle          EventListResponseType = "session.idle"
 	EventListResponseTypeSessionError         EventListResponseType = "session.error"
+	EventListResponseTypeServerConnected      EventListResponseType = "server.connected"
 	EventListResponseTypeFileWatcherUpdated   EventListResponseType = "file.watcher.updated"
 	EventListResponseTypeIdeInstalled         EventListResponseType = "ide.installed"
 )
 
 func (r EventListResponseType) IsKnown() bool {
 	switch r {
-	case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeStorageWrite, EventListResponseTypeFileEdited, EventListResponseTypeServerConnected, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeFileWatcherUpdated, EventListResponseTypeIdeInstalled:
+	case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeStorageWrite, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeServerConnected, EventListResponseTypeFileWatcherUpdated, EventListResponseTypeIdeInstalled:
 		return true
 	}
 	return false

+ 2 - 2
packages/sdk/go/session.go

@@ -2025,9 +2025,9 @@ func (r toolStateCompletedTimeJSON) RawJSON() string {
 type ToolStateError struct {
 	Error    string                 `json:"error,required"`
 	Input    map[string]interface{} `json:"input,required"`
-	Metadata map[string]interface{} `json:"metadata"`
 	Status   ToolStateErrorStatus   `json:"status,required"`
 	Time     ToolStateErrorTime     `json:"time,required"`
+	Metadata map[string]interface{} `json:"metadata"`
 	JSON     toolStateErrorJSON     `json:"-"`
 }
 
@@ -2035,9 +2035,9 @@ type ToolStateError struct {
 type toolStateErrorJSON struct {
 	Error       apijson.Field
 	Input       apijson.Field
-	Metadata    apijson.Field
 	Status      apijson.Field
 	Time        apijson.Field
+	Metadata    apijson.Field
 	raw         string
 	ExtraFields map[string]apijson.Field
 }

+ 1 - 1
packages/sdk/go/tui.go

@@ -47,7 +47,7 @@ func (r *TuiService) ClearPrompt(ctx context.Context, opts ...option.RequestOpti
 	return
 }
 
-// Execute a TUI command (e.g. switch_agent)
+// Execute a TUI command (e.g. agent_cycle)
 func (r *TuiService) ExecuteCommand(ctx context.Context, body TuiExecuteCommandParams, opts ...option.RequestOption) (res *bool, err error) {
 	opts = append(r.Options[:], opts...)
 	path := "tui/execute-command"

+ 1 - 1
packages/sdk/js/src/gen/sdk.gen.ts

@@ -506,7 +506,7 @@ class Tui extends _HeyApiClient {
   }
 
   /**
-   * Execute a TUI command (e.g. switch_agent)
+   * Execute a TUI command (e.g. agent_cycle)
    */
   public executeCommand<ThrowOnError extends boolean = false>(options?: Options<TuiExecuteCommandData, ThrowOnError>) {
     return (options?.client ?? this._client).post<TuiExecuteCommandResponses, unknown, ThrowOnError>({

+ 83 - 62
packages/sdk/js/src/gen/types.gen.ts

@@ -347,10 +347,10 @@ export type ToolStateError = {
   input: {
     [key: string]: unknown
   }
-  metadata: {
+  error: string
+  metadata?: {
     [key: string]: unknown
   }
-  error: string
   time: {
     start: number
     end: number
@@ -750,25 +750,29 @@ export type KeybindsConfig = {
    */
   app_help: string
   /**
-   * @deprecated use switch_agent. Next mode
+   * Exit the application
    */
-  switch_mode: string
+  app_exit: string
   /**
-   * @deprecated use switch_agent_reverse. Previous mode
+   * Open external editor
    */
-  switch_mode_reverse: string
+  editor_open: string
   /**
-   * Next agent
+   * List available themes
    */
-  switch_agent: string
+  theme_list: string
   /**
-   * Previous agent
+   * Create/update AGENTS.md
    */
-  switch_agent_reverse: string
+  project_init: string
   /**
-   * Open external editor
+   * Toggle tool details
    */
-  editor_open: string
+  tool_details: string
+  /**
+   * Toggle thinking blocks
+   */
+  thinking_blocks: string
   /**
    * Export session to editor
    */
@@ -798,41 +802,65 @@ export type KeybindsConfig = {
    */
   session_compact: string
   /**
-   * Toggle tool details
+   * Scroll messages up by one page
    */
-  tool_details: string
+  messages_page_up: string
   /**
-   * Toggle thinking blocks
+   * Scroll messages down by one page
    */
-  thinking_blocks: string
+  messages_page_down: string
   /**
-   * List available models
+   * Scroll messages up by half page
    */
-  model_list: string
+  messages_half_page_up: string
   /**
-   * List available themes
+   * Scroll messages down by half page
    */
-  theme_list: string
+  messages_half_page_down: string
   /**
-   * List files
+   * Navigate to first message
    */
-  file_list: string
+  messages_first: string
   /**
-   * Close file
+   * Navigate to last message
    */
-  file_close: string
+  messages_last: string
   /**
-   * Search file
+   * Copy message
    */
-  file_search: string
+  messages_copy: string
   /**
-   * Split/unified diff
+   * Undo message
    */
-  file_diff_toggle: string
+  messages_undo: string
   /**
-   * Create/update AGENTS.md
+   * Redo message
    */
-  project_init: string
+  messages_redo: string
+  /**
+   * List available models
+   */
+  model_list: string
+  /**
+   * Next recent model
+   */
+  model_cycle_recent: string
+  /**
+   * Previous recent model
+   */
+  model_cycle_recent_reverse: string
+  /**
+   * List agents
+   */
+  agent_list: string
+  /**
+   * Next agent
+   */
+  agent_cycle: string
+  /**
+   * Previous agent
+   */
+  agent_cycle_reverse: string
   /**
    * Clear input field
    */
@@ -850,61 +878,53 @@ export type KeybindsConfig = {
    */
   input_newline: string
   /**
-   * Scroll messages up by one page
-   */
-  messages_page_up: string
-  /**
-   * Scroll messages down by one page
-   */
-  messages_page_down: string
-  /**
-   * Scroll messages up by half page
+   * @deprecated use agent_cycle. Next mode
    */
-  messages_half_page_up: string
+  switch_mode: string
   /**
-   * Scroll messages down by half page
+   * @deprecated use agent_cycle_reverse. Previous mode
    */
-  messages_half_page_down: string
+  switch_mode_reverse: string
   /**
-   * Navigate to previous message
+   * @deprecated use agent_cycle. Next agent
    */
-  messages_previous: string
+  switch_agent: string
   /**
-   * Navigate to next message
+   * @deprecated use agent_cycle_reverse. Previous agent
    */
-  messages_next: string
+  switch_agent_reverse: string
   /**
-   * Navigate to first message
+   * @deprecated Currently not available. List files
    */
-  messages_first: string
+  file_list: string
   /**
-   * Navigate to last message
+   * @deprecated Close file
    */
-  messages_last: string
+  file_close: string
   /**
-   * Toggle layout
+   * @deprecated Search file
    */
-  messages_layout_toggle: string
+  file_search: string
   /**
-   * Copy message
+   * @deprecated Split/unified diff
    */
-  messages_copy: string
+  file_diff_toggle: string
   /**
-   * @deprecated use messages_undo. Revert message
+   * @deprecated Navigate to previous message
    */
-  messages_revert: string
+  messages_previous: string
   /**
-   * Undo message
+   * @deprecated Navigate to next message
    */
-  messages_undo: string
+  messages_next: string
   /**
-   * Redo message
+   * @deprecated Toggle layout
    */
-  messages_redo: string
+  messages_layout_toggle: string
   /**
-   * Exit the application
+   * @deprecated use messages_undo. Revert message
    */
-  app_exit: string
+  messages_revert: string
 }
 
 export type AgentConfig = {
@@ -1086,6 +1106,7 @@ export type Agent = {
   name: string
   description?: string
   mode: "subagent" | "primary" | "all"
+  builtIn: boolean
   topP?: number
   temperature?: number
   permission: {

+ 0 - 2
packages/tui/internal/app/state.go

@@ -35,8 +35,6 @@ type State struct {
 	Agent              string                `toml:"agent"`
 	RecentlyUsedModels []ModelUsage          `toml:"recently_used_models"`
 	RecentlyUsedAgents []AgentUsage          `toml:"recently_used_agents"`
-	MessagesRight      bool                  `toml:"messages_right"`
-	SplitDiff          bool                  `toml:"split_diff"`
 	MessageHistory     []Prompt              `toml:"message_history"`
 	ShowToolDetails    *bool                 `toml:"show_tool_details"`
 	ShowThinkingBlocks *bool                 `toml:"show_thinking_blocks"`

+ 31 - 58
packages/tui/internal/commands/command.go

@@ -108,9 +108,12 @@ func (r CommandRegistry) Matches(msg tea.KeyPressMsg, leader bool) []Command {
 
 const (
 	AppHelpCommand                 CommandName = "app_help"
-	SwitchAgentCommand             CommandName = "switch_agent"
-	SwitchAgentReverseCommand      CommandName = "switch_agent_reverse"
+	AppExitCommand                 CommandName = "app_exit"
+	ThemeListCommand               CommandName = "theme_list"
+	ProjectInitCommand             CommandName = "project_init"
 	EditorOpenCommand              CommandName = "editor_open"
+	ToolDetailsCommand             CommandName = "tool_details"
+	ThinkingBlocksCommand          CommandName = "thinking_blocks"
 	SessionNewCommand              CommandName = "session_new"
 	SessionListCommand             CommandName = "session_list"
 	SessionShareCommand            CommandName = "session_share"
@@ -118,34 +121,25 @@ const (
 	SessionInterruptCommand        CommandName = "session_interrupt"
 	SessionCompactCommand          CommandName = "session_compact"
 	SessionExportCommand           CommandName = "session_export"
-	ToolDetailsCommand             CommandName = "tool_details"
-	ThinkingBlocksCommand          CommandName = "thinking_blocks"
+	MessagesPageUpCommand          CommandName = "messages_page_up"
+	MessagesPageDownCommand        CommandName = "messages_page_down"
+	MessagesHalfPageUpCommand      CommandName = "messages_half_page_up"
+	MessagesHalfPageDownCommand    CommandName = "messages_half_page_down"
+	MessagesFirstCommand           CommandName = "messages_first"
+	MessagesLastCommand            CommandName = "messages_last"
+	MessagesCopyCommand            CommandName = "messages_copy"
+	MessagesUndoCommand            CommandName = "messages_undo"
+	MessagesRedoCommand            CommandName = "messages_redo"
 	ModelListCommand               CommandName = "model_list"
-	AgentListCommand               CommandName = "agent_list"
 	ModelCycleRecentCommand        CommandName = "model_cycle_recent"
 	ModelCycleRecentReverseCommand CommandName = "model_cycle_recent_reverse"
-	ThemeListCommand               CommandName = "theme_list"
-	FileListCommand                CommandName = "file_list"
-	FileCloseCommand               CommandName = "file_close"
-	FileSearchCommand              CommandName = "file_search"
-	FileDiffToggleCommand          CommandName = "file_diff_toggle"
-	ProjectInitCommand             CommandName = "project_init"
+	AgentListCommand               CommandName = "agent_list"
+	AgentCycleCommand              CommandName = "agent_cycle"
+	AgentCycleReverseCommand       CommandName = "agent_cycle_reverse"
 	InputClearCommand              CommandName = "input_clear"
 	InputPasteCommand              CommandName = "input_paste"
 	InputSubmitCommand             CommandName = "input_submit"
 	InputNewlineCommand            CommandName = "input_newline"
-	MessagesPageUpCommand          CommandName = "messages_page_up"
-	MessagesPageDownCommand        CommandName = "messages_page_down"
-	MessagesHalfPageUpCommand      CommandName = "messages_half_page_up"
-	MessagesHalfPageDownCommand    CommandName = "messages_half_page_down"
-
-	MessagesFirstCommand CommandName = "messages_first"
-	MessagesLastCommand  CommandName = "messages_last"
-
-	MessagesCopyCommand CommandName = "messages_copy"
-	MessagesUndoCommand CommandName = "messages_undo"
-	MessagesRedoCommand CommandName = "messages_redo"
-	AppExitCommand      CommandName = "app_exit"
 )
 
 func (k Command) Matches(msg tea.KeyPressMsg, leader bool) bool {
@@ -184,16 +178,6 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry {
 			Keybindings: parseBindings("<leader>h"),
 			Trigger:     []string{"help"},
 		},
-		{
-			Name:        SwitchAgentCommand,
-			Description: "next agent",
-			Keybindings: parseBindings("tab"),
-		},
-		{
-			Name:        SwitchAgentReverseCommand,
-			Description: "previous agent",
-			Keybindings: parseBindings("shift+tab"),
-		},
 		{
 			Name:        EditorOpenCommand,
 			Description: "open editor",
@@ -258,12 +242,6 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry {
 			Keybindings: parseBindings("<leader>m"),
 			Trigger:     []string{"models"},
 		},
-		{
-			Name:        AgentListCommand,
-			Description: "list agents",
-			Keybindings: parseBindings("<leader>a"),
-			Trigger:     []string{"agents"},
-		},
 		{
 			Name:        ModelCycleRecentCommand,
 			Description: "next recent model",
@@ -275,31 +253,26 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry {
 			Keybindings: parseBindings("shift+f2"),
 		},
 		{
-			Name:        ThemeListCommand,
-			Description: "list themes",
-			Keybindings: parseBindings("<leader>t"),
-			Trigger:     []string{"themes"},
+			Name:        AgentListCommand,
+			Description: "list agents",
+			Keybindings: parseBindings("<leader>a"),
+			Trigger:     []string{"agents"},
 		},
-		// {
-		// 	Name:        FileListCommand,
-		// 	Description: "list files",
-		// 	Keybindings: parseBindings("<leader>f"),
-		// 	Trigger:     []string{"files"},
-		// },
 		{
-			Name:        FileCloseCommand,
-			Description: "close file",
-			Keybindings: parseBindings("esc"),
+			Name:        AgentCycleCommand,
+			Description: "next agent",
+			Keybindings: parseBindings("tab"),
 		},
 		{
-			Name:        FileSearchCommand,
-			Description: "search file",
-			Keybindings: parseBindings("<leader>/"),
+			Name:        AgentCycleReverseCommand,
+			Description: "previous agent",
+			Keybindings: parseBindings("shift+tab"),
 		},
 		{
-			Name:        FileDiffToggleCommand,
-			Description: "split/unified diff",
-			Keybindings: parseBindings("<leader>v"),
+			Name:        ThemeListCommand,
+			Description: "list themes",
+			Keybindings: parseBindings("<leader>t"),
+			Trigger:     []string{"themes"},
 		},
 		{
 			Name:        ProjectInitCommand,

+ 0 - 236
packages/tui/internal/components/dialog/find.go

@@ -1,236 +0,0 @@
-package dialog
-
-import (
-	"log/slog"
-
-	tea "github.com/charmbracelet/bubbletea/v2"
-	"github.com/sst/opencode/internal/completions"
-	"github.com/sst/opencode/internal/components/list"
-	"github.com/sst/opencode/internal/components/modal"
-	"github.com/sst/opencode/internal/layout"
-	"github.com/sst/opencode/internal/styles"
-	"github.com/sst/opencode/internal/theme"
-	"github.com/sst/opencode/internal/util"
-)
-
-const (
-	findDialogWidth = 76
-)
-
-type FindSelectedMsg struct {
-	FilePath string
-}
-
-type FindDialogCloseMsg struct{}
-
-type findInitialSuggestionsMsg struct {
-	suggestions []completions.CompletionSuggestion
-}
-
-type FindDialog interface {
-	layout.Modal
-	tea.Model
-	tea.ViewModel
-	SetWidth(width int)
-	SetHeight(height int)
-	IsEmpty() bool
-}
-
-// findItem is a custom list item for file suggestions
-type findItem struct {
-	suggestion completions.CompletionSuggestion
-}
-
-func (f findItem) Render(
-	selected bool,
-	width int,
-	baseStyle styles.Style,
-) string {
-	t := theme.CurrentTheme()
-
-	itemStyle := baseStyle.
-		Background(t.BackgroundPanel()).
-		Foreground(t.TextMuted())
-
-	if selected {
-		itemStyle = itemStyle.Foreground(t.Primary())
-	}
-
-	return itemStyle.PaddingLeft(1).Render(f.suggestion.Display(itemStyle))
-}
-
-func (f findItem) Selectable() bool {
-	return true
-}
-
-type findDialogComponent struct {
-	completionProvider completions.CompletionProvider
-	allSuggestions     []completions.CompletionSuggestion
-	width, height      int
-	modal              *modal.Modal
-	searchDialog       *SearchDialog
-	dialogWidth        int
-}
-
-func (f *findDialogComponent) Init() tea.Cmd {
-	return tea.Batch(
-		f.loadInitialSuggestions(),
-		f.searchDialog.Init(),
-	)
-}
-
-func (f *findDialogComponent) loadInitialSuggestions() tea.Cmd {
-	return func() tea.Msg {
-		items, err := f.completionProvider.GetChildEntries("")
-		if err != nil {
-			slog.Error("Failed to get initial completion items", "error", err)
-			return findInitialSuggestionsMsg{suggestions: []completions.CompletionSuggestion{}}
-		}
-		return findInitialSuggestionsMsg{suggestions: items}
-	}
-}
-
-func (f *findDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
-	switch msg := msg.(type) {
-	case findInitialSuggestionsMsg:
-		// Handle initial suggestions setup
-		f.allSuggestions = msg.suggestions
-
-		// Calculate dialog width
-		f.dialogWidth = f.calculateDialogWidth()
-
-		// Initialize search dialog with calculated width
-		f.searchDialog = NewSearchDialog("Search files...", 10)
-		f.searchDialog.SetWidth(f.dialogWidth)
-
-		// Convert to list items
-		items := make([]list.Item, len(f.allSuggestions))
-		for i, suggestion := range f.allSuggestions {
-			items[i] = findItem{suggestion: suggestion}
-		}
-		f.searchDialog.SetItems(items)
-
-		// Update modal with calculated width
-		f.modal = modal.New(
-			modal.WithTitle("Find Files"),
-			modal.WithMaxWidth(f.dialogWidth+4),
-		)
-
-		return f, f.searchDialog.Init()
-
-	case []completions.CompletionSuggestion:
-		// Store suggestions and convert to findItem for the search dialog
-		f.allSuggestions = msg
-		items := make([]list.Item, len(msg))
-		for i, suggestion := range msg {
-			items[i] = findItem{suggestion: suggestion}
-		}
-		f.searchDialog.SetItems(items)
-		return f, nil
-
-	case SearchSelectionMsg:
-		// Handle selection from search dialog - now we can directly access the suggestion
-		if item, ok := msg.Item.(findItem); ok {
-			return f, f.selectFile(item.suggestion)
-		}
-		return f, nil
-
-	case SearchCancelledMsg:
-		return f, f.Close()
-
-	case SearchQueryChangedMsg:
-		// Update completion items based on search query
-		return f, func() tea.Msg {
-			items, err := f.completionProvider.GetChildEntries(msg.Query)
-			if err != nil {
-				slog.Error("Failed to get completion items", "error", err)
-				return []completions.CompletionSuggestion{}
-			}
-			return items
-		}
-
-	case tea.WindowSizeMsg:
-		f.width = msg.Width
-		f.height = msg.Height
-		// Recalculate width based on new viewport size
-		oldWidth := f.dialogWidth
-		f.dialogWidth = f.calculateDialogWidth()
-		if oldWidth != f.dialogWidth {
-			f.searchDialog.SetWidth(f.dialogWidth)
-			// Update modal max width too
-			f.modal = modal.New(
-				modal.WithTitle("Find Files"),
-				modal.WithMaxWidth(f.dialogWidth+4),
-			)
-		}
-		f.searchDialog.SetHeight(msg.Height)
-	}
-
-	// Forward all other messages to the search dialog
-	updatedDialog, cmd := f.searchDialog.Update(msg)
-	f.searchDialog = updatedDialog.(*SearchDialog)
-	return f, cmd
-}
-
-func (f *findDialogComponent) View() string {
-	return f.searchDialog.View()
-}
-
-func (f *findDialogComponent) calculateDialogWidth() int {
-	// Use fixed width unless viewport is smaller
-	if f.width > 0 && f.width < findDialogWidth+10 {
-		return f.width - 10
-	}
-	return findDialogWidth
-}
-
-func (f *findDialogComponent) SetWidth(width int) {
-	f.width = width
-	f.searchDialog.SetWidth(f.dialogWidth)
-}
-
-func (f *findDialogComponent) SetHeight(height int) {
-	f.height = height
-}
-
-func (f *findDialogComponent) IsEmpty() bool {
-	return f.searchDialog.GetQuery() == ""
-}
-
-func (f *findDialogComponent) selectFile(item completions.CompletionSuggestion) tea.Cmd {
-	return tea.Sequence(
-		f.Close(),
-		util.CmdHandler(FindSelectedMsg{
-			FilePath: item.Value,
-		}),
-	)
-}
-
-func (f *findDialogComponent) Render(background string) string {
-	return f.modal.Render(f.View(), background)
-}
-
-func (f *findDialogComponent) Close() tea.Cmd {
-	f.searchDialog.SetQuery("")
-	f.searchDialog.Blur()
-	return util.CmdHandler(modal.CloseModalMsg{})
-}
-
-func NewFindDialog(completionProvider completions.CompletionProvider) FindDialog {
-	component := &findDialogComponent{
-		completionProvider: completionProvider,
-		dialogWidth:        findDialogWidth,
-		allSuggestions:     []completions.CompletionSuggestion{},
-	}
-
-	// Create search dialog and modal with fixed width
-	component.searchDialog = NewSearchDialog("Search files...", 10)
-	component.searchDialog.SetWidth(findDialogWidth)
-
-	component.modal = modal.New(
-		modal.WithTitle("Find Files"),
-		modal.WithMaxWidth(findDialogWidth+4),
-	)
-
-	return component
-}

+ 0 - 184
packages/tui/internal/components/dialog/init.go

@@ -1,184 +0,0 @@
-package dialog
-
-import (
-	"github.com/charmbracelet/bubbles/v2/key"
-	tea "github.com/charmbracelet/bubbletea/v2"
-	"github.com/charmbracelet/lipgloss/v2"
-
-	"github.com/sst/opencode/internal/styles"
-	"github.com/sst/opencode/internal/theme"
-	"github.com/sst/opencode/internal/util"
-)
-
-// InitDialogCmp is a component that asks the user if they want to initialize the project.
-type InitDialogCmp struct {
-	width, height int
-	selected      int
-	keys          initDialogKeyMap
-}
-
-// NewInitDialogCmp creates a new InitDialogCmp.
-func NewInitDialogCmp() InitDialogCmp {
-	return InitDialogCmp{
-		selected: 0,
-		keys:     initDialogKeyMap{},
-	}
-}
-
-type initDialogKeyMap struct {
-	Tab    key.Binding
-	Left   key.Binding
-	Right  key.Binding
-	Enter  key.Binding
-	Escape key.Binding
-	Y      key.Binding
-	N      key.Binding
-}
-
-// ShortHelp implements key.Map.
-func (k initDialogKeyMap) ShortHelp() []key.Binding {
-	return []key.Binding{
-		key.NewBinding(
-			key.WithKeys("tab", "left", "right"),
-			key.WithHelp("tab/←/→", "toggle selection"),
-		),
-		key.NewBinding(
-			key.WithKeys("enter"),
-			key.WithHelp("enter", "confirm"),
-		),
-		key.NewBinding(
-			key.WithKeys("esc", "q"),
-			key.WithHelp("esc/q", "cancel"),
-		),
-		key.NewBinding(
-			key.WithKeys("y", "n"),
-			key.WithHelp("y/n", "yes/no"),
-		),
-	}
-}
-
-// FullHelp implements key.Map.
-func (k initDialogKeyMap) FullHelp() [][]key.Binding {
-	return [][]key.Binding{k.ShortHelp()}
-}
-
-// Init implements tea.Model.
-func (m InitDialogCmp) Init() tea.Cmd {
-	return nil
-}
-
-// Update implements tea.Model.
-func (m InitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
-	switch msg := msg.(type) {
-	case tea.KeyMsg:
-		switch {
-		case key.Matches(msg, key.NewBinding(key.WithKeys("esc"))):
-			return m, util.CmdHandler(CloseInitDialogMsg{Initialize: false})
-		case key.Matches(msg, key.NewBinding(key.WithKeys("tab", "left", "right", "h", "l"))):
-			m.selected = (m.selected + 1) % 2
-			return m, nil
-		case key.Matches(msg, key.NewBinding(key.WithKeys("enter"))):
-			return m, util.CmdHandler(CloseInitDialogMsg{Initialize: m.selected == 0})
-		case key.Matches(msg, key.NewBinding(key.WithKeys("y"))):
-			return m, util.CmdHandler(CloseInitDialogMsg{Initialize: true})
-		case key.Matches(msg, key.NewBinding(key.WithKeys("n"))):
-			return m, util.CmdHandler(CloseInitDialogMsg{Initialize: false})
-		}
-	case tea.WindowSizeMsg:
-		m.width = msg.Width
-		m.height = msg.Height
-	}
-	return m, nil
-}
-
-// View implements tea.Model.
-func (m InitDialogCmp) View() string {
-	t := theme.CurrentTheme()
-	baseStyle := styles.NewStyle().Foreground(t.Text())
-
-	// Calculate width needed for content
-	maxWidth := 60 // Width for explanation text
-
-	title := baseStyle.
-		Foreground(t.Primary()).
-		Bold(true).
-		Width(maxWidth).
-		Padding(0, 1).
-		Render("Initialize Project")
-
-	explanation := baseStyle.
-		Foreground(t.Text()).
-		Width(maxWidth).
-		Padding(0, 1).
-		Render("Initialization generates a new AGENTS.md file that contains information about your codebase, this file serves as memory for each project, you can freely add to it to help the agents be better at their job.")
-
-	question := baseStyle.
-		Foreground(t.Text()).
-		Width(maxWidth).
-		Padding(1, 1).
-		Render("Would you like to initialize this project?")
-
-	maxWidth = min(maxWidth, m.width-10)
-	yesStyle := baseStyle
-	noStyle := baseStyle
-
-	if m.selected == 0 {
-		yesStyle = yesStyle.
-			Background(t.Primary()).
-			Foreground(t.Background()).
-			Bold(true)
-		noStyle = noStyle.
-			Background(t.Background()).
-			Foreground(t.Primary())
-	} else {
-		noStyle = noStyle.
-			Background(t.Primary()).
-			Foreground(t.Background()).
-			Bold(true)
-		yesStyle = yesStyle.
-			Background(t.Background()).
-			Foreground(t.Primary())
-	}
-
-	yes := yesStyle.Padding(0, 3).Render("Yes")
-	no := noStyle.Padding(0, 3).Render("No")
-
-	buttons := lipgloss.JoinHorizontal(lipgloss.Center, yes, baseStyle.Render("  "), no)
-	buttons = baseStyle.
-		Width(maxWidth).
-		Padding(1, 0).
-		Render(buttons)
-
-	content := lipgloss.JoinVertical(
-		lipgloss.Left,
-		title,
-		baseStyle.Width(maxWidth).Render(""),
-		explanation,
-		question,
-		buttons,
-		baseStyle.Width(maxWidth).Render(""),
-	)
-
-	return baseStyle.Padding(1, 2).
-		Border(lipgloss.RoundedBorder()).
-		BorderBackground(t.Background()).
-		BorderForeground(t.TextMuted()).
-		Width(lipgloss.Width(content) + 4).
-		Render(content)
-}
-
-// SetSize sets the size of the component.
-func (m *InitDialogCmp) SetSize(width, height int) {
-	m.width = width
-	m.height = height
-}
-
-// CloseInitDialogMsg is a message that is sent when the init dialog is closed.
-type CloseInitDialogMsg struct {
-	Initialize bool
-}
-
-// ShowInitDialogMsg is a message that is sent to show the init dialog.
-type ShowInitDialogMsg struct {
-	Show bool
-}

+ 0 - 278
packages/tui/internal/components/fileviewer/fileviewer.go

@@ -1,278 +0,0 @@
-package fileviewer
-
-import (
-	"fmt"
-	"strings"
-
-	tea "github.com/charmbracelet/bubbletea/v2"
-
-	"github.com/sst/opencode/internal/app"
-	"github.com/sst/opencode/internal/commands"
-	"github.com/sst/opencode/internal/components/dialog"
-	"github.com/sst/opencode/internal/components/diff"
-	"github.com/sst/opencode/internal/layout"
-	"github.com/sst/opencode/internal/styles"
-	"github.com/sst/opencode/internal/theme"
-	"github.com/sst/opencode/internal/util"
-	"github.com/sst/opencode/internal/viewport"
-)
-
-type DiffStyle int
-
-const (
-	DiffStyleSplit DiffStyle = iota
-	DiffStyleUnified
-)
-
-type Model struct {
-	app           *app.App
-	width, height int
-	viewport      viewport.Model
-	filename      *string
-	content       *string
-	isDiff        *bool
-	diffStyle     DiffStyle
-}
-
-type fileRenderedMsg struct {
-	content string
-}
-
-func New(app *app.App) Model {
-	vp := viewport.New()
-	m := Model{
-		app:       app,
-		viewport:  vp,
-		diffStyle: DiffStyleUnified,
-	}
-	if app.State.SplitDiff {
-		m.diffStyle = DiffStyleSplit
-	}
-	return m
-}
-
-func (m Model) Init() tea.Cmd {
-	return m.viewport.Init()
-}
-
-func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
-	var cmds []tea.Cmd
-
-	switch msg := msg.(type) {
-	case fileRenderedMsg:
-		m.viewport.SetContent(msg.content)
-		return m, util.CmdHandler(app.FileRenderedMsg{
-			FilePath: *m.filename,
-		})
-	case dialog.ThemeSelectedMsg:
-		return m, m.render()
-	case tea.KeyMsg:
-		switch msg.String() {
-		// TODO
-		}
-	}
-
-	vp, cmd := m.viewport.Update(msg)
-	m.viewport = vp
-	cmds = append(cmds, cmd)
-
-	return m, tea.Batch(cmds...)
-}
-
-func (m Model) View() string {
-	if !m.HasFile() {
-		return ""
-	}
-
-	header := *m.filename
-	header = styles.NewStyle().
-		Padding(1, 2).
-		Width(m.width).
-		Background(theme.CurrentTheme().BackgroundElement()).
-		Foreground(theme.CurrentTheme().Text()).
-		Render(header)
-
-	t := theme.CurrentTheme()
-
-	close := m.app.Key(commands.FileCloseCommand)
-	diffToggle := m.app.Key(commands.FileDiffToggleCommand)
-	if m.isDiff == nil || *m.isDiff == false {
-		diffToggle = ""
-	}
-
-	background := t.Background()
-	footer := layout.Render(
-		layout.FlexOptions{
-			Background: &background,
-			Direction:  layout.Row,
-			Justify:    layout.JustifyCenter,
-			Align:      layout.AlignStretch,
-			Width:      m.width - 2,
-			Gap:        5,
-		},
-		layout.FlexItem{
-			View: close,
-		},
-
-		layout.FlexItem{
-			View: diffToggle,
-		},
-	)
-	footer = styles.NewStyle().Background(t.Background()).Padding(0, 1).Render(footer)
-
-	return header + "\n" + m.viewport.View() + "\n" + footer
-}
-
-func (m *Model) Clear() (Model, tea.Cmd) {
-	m.filename = nil
-	m.content = nil
-	m.isDiff = nil
-	return *m, m.render()
-}
-
-func (m *Model) ToggleDiff() (Model, tea.Cmd) {
-	switch m.diffStyle {
-	case DiffStyleSplit:
-		m.diffStyle = DiffStyleUnified
-	default:
-		m.diffStyle = DiffStyleSplit
-	}
-	return *m, m.render()
-}
-
-func (m *Model) DiffStyle() DiffStyle {
-	return m.diffStyle
-}
-
-func (m Model) HasFile() bool {
-	return m.filename != nil && m.content != nil
-}
-
-func (m Model) Filename() string {
-	if m.filename == nil {
-		return ""
-	}
-	return *m.filename
-}
-
-func (m *Model) SetSize(width, height int) (Model, tea.Cmd) {
-	if m.width != width || m.height != height {
-		m.width = width
-		m.height = height
-		m.viewport.SetWidth(width)
-		m.viewport.SetHeight(height - 4)
-		return *m, m.render()
-	}
-	return *m, nil
-}
-
-func (m *Model) SetFile(filename string, content string, isDiff bool) (Model, tea.Cmd) {
-	m.filename = &filename
-	m.content = &content
-	m.isDiff = &isDiff
-	return *m, m.render()
-}
-
-func (m *Model) render() tea.Cmd {
-	if m.filename == nil || m.content == nil {
-		m.viewport.SetContent("")
-		return nil
-	}
-
-	return func() tea.Msg {
-		t := theme.CurrentTheme()
-		var rendered string
-
-		if m.isDiff != nil && *m.isDiff {
-			diffResult := ""
-			var err error
-			if m.diffStyle == DiffStyleSplit {
-				diffResult, err = diff.FormatDiff(
-					*m.filename,
-					*m.content,
-					diff.WithWidth(m.width),
-				)
-			} else if m.diffStyle == DiffStyleUnified {
-				diffResult, err = diff.FormatUnifiedDiff(
-					*m.filename,
-					*m.content,
-					diff.WithWidth(m.width),
-				)
-			}
-			if err != nil {
-				rendered = styles.NewStyle().
-					Foreground(t.Error()).
-					Render(fmt.Sprintf("Error rendering diff: %v", err))
-			} else {
-				rendered = strings.TrimRight(diffResult, "\n")
-			}
-		} else {
-			rendered = util.RenderFile(
-				*m.filename,
-				*m.content,
-				m.width,
-			)
-		}
-
-		rendered = styles.NewStyle().
-			Width(m.width).
-			Background(t.BackgroundPanel()).
-			Render(rendered)
-
-		return fileRenderedMsg{
-			content: rendered,
-		}
-	}
-}
-
-func (m *Model) ScrollTo(line int) {
-	m.viewport.SetYOffset(line)
-}
-
-func (m *Model) ScrollToBottom() {
-	m.viewport.GotoBottom()
-}
-
-func (m *Model) ScrollToTop() {
-	m.viewport.GotoTop()
-}
-
-func (m *Model) PageUp() (Model, tea.Cmd) {
-	m.viewport.ViewUp()
-	return *m, nil
-}
-
-func (m *Model) PageDown() (Model, tea.Cmd) {
-	m.viewport.ViewDown()
-	return *m, nil
-}
-
-func (m *Model) HalfPageUp() (Model, tea.Cmd) {
-	m.viewport.HalfViewUp()
-	return *m, nil
-}
-
-func (m *Model) HalfPageDown() (Model, tea.Cmd) {
-	m.viewport.HalfViewDown()
-	return *m, nil
-}
-
-func (m Model) AtTop() bool {
-	return m.viewport.AtTop()
-}
-
-func (m Model) AtBottom() bool {
-	return m.viewport.AtBottom()
-}
-
-func (m Model) ScrollPercent() float64 {
-	return m.viewport.ScrollPercent()
-}
-
-func (m Model) TotalLineCount() int {
-	return m.viewport.TotalLineCount()
-}
-
-func (m Model) VisibleLineCount() int {
-	return m.viewport.VisibleLineCount()
-}

+ 1 - 1
packages/tui/internal/components/status/status.go

@@ -132,7 +132,7 @@ func (m *statusComponent) View() string {
 		modeForeground = t.BackgroundPanel()
 	}
 
-	command := m.app.Commands[commands.SwitchAgentCommand]
+	command := m.app.Commands[commands.AgentCycleCommand]
 	kb := command.Keybindings[0]
 	key := kb.Key
 	if kb.RequiresLeader {

+ 14 - 93
packages/tui/internal/tui/tui.go

@@ -23,7 +23,6 @@ import (
 	"github.com/sst/opencode/internal/components/chat"
 	cmdcomp "github.com/sst/opencode/internal/components/commands"
 	"github.com/sst/opencode/internal/components/dialog"
-	"github.com/sst/opencode/internal/components/fileviewer"
 	"github.com/sst/opencode/internal/components/modal"
 	"github.com/sst/opencode/internal/components/status"
 	"github.com/sst/opencode/internal/components/toast"
@@ -78,7 +77,6 @@ type Model struct {
 	interruptKeyState    InterruptKeyState
 	exitKeyState         ExitKeyState
 	messagesRight        bool
-	fileViewer           fileviewer.Model
 }
 
 func (a Model) Init() tea.Cmd {
@@ -94,13 +92,6 @@ func (a Model) Init() tea.Cmd {
 	cmds = append(cmds, a.status.Init())
 	cmds = append(cmds, a.completions.Init())
 	cmds = append(cmds, a.toastManager.Init())
-	cmds = append(cmds, a.fileViewer.Init())
-
-	// Check if we should show the init dialog
-	cmds = append(cmds, func() tea.Msg {
-		shouldShow := a.app.Info.Git && a.app.Info.Time.Initialized > 0
-		return dialog.ShowInitDialogMsg{Show: shouldShow}
-	})
 
 	return tea.Batch(cmds...)
 }
@@ -586,12 +577,6 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			slog.Error("Server error", "name", err.Name, "message", err.Data.Message)
 			return a, toast.NewErrorToast(err.Data.Message, toast.WithTitle(string(err.Name)))
 		}
-	case opencode.EventListResponseEventFileWatcherUpdated:
-		if a.fileViewer.HasFile() {
-			if a.fileViewer.Filename() == msg.Properties.File {
-				return a.openFile(msg.Properties.File)
-			}
-		}
 	case tea.WindowSizeMsg:
 		msg.Height -= 2 // Make space for the status bar
 		a.width, a.height = msg.Width, msg.Height
@@ -653,8 +638,6 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		// Reset exit key state after timeout
 		a.exitKeyState = ExitKeyIdle
 		a.editor.SetExitKeyInDebounce(false)
-	case dialog.FindSelectedMsg:
-		return a.openFile(msg.FilePath)
 	case tea.PasteMsg, tea.ClipboardMsg:
 		// Paste events: prioritize modal if active, otherwise editor
 		if a.modal != nil {
@@ -753,10 +736,6 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		cmds = append(cmds, cmd)
 	}
 
-	fv, cmd := a.fileViewer.Update(msg)
-	a.fileViewer = fv
-	cmds = append(cmds, cmd)
-
 	return a, tea.Batch(cmds...)
 }
 
@@ -806,26 +785,6 @@ func (a Model) Cleanup() {
 	a.status.Cleanup()
 }
 
-func (a Model) openFile(filepath string) (tea.Model, tea.Cmd) {
-	var cmd tea.Cmd
-	response, err := a.app.Client.File.Read(
-		context.Background(),
-		opencode.FileReadParams{
-			Path: opencode.F(filepath),
-		},
-	)
-	if err != nil {
-		slog.Error("Failed to read file", "error", err)
-		return a, toast.NewErrorToast("Failed to read file")
-	}
-	a.fileViewer, cmd = a.fileViewer.SetFile(
-		filepath,
-		response.Content,
-		response.Type == "patch",
-	)
-	return a, cmd
-}
-
 func (a Model) home() (string, int, int) {
 	t := theme.CurrentTheme()
 	effectiveWidth := a.width - 4
@@ -1014,11 +973,11 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
 	case commands.AppHelpCommand:
 		helpDialog := dialog.NewHelpDialog(a.app)
 		a.modal = helpDialog
-	case commands.SwitchAgentCommand:
+	case commands.AgentCycleCommand:
 		updated, cmd := a.app.SwitchAgent()
 		a.app = updated
 		cmds = append(cmds, cmd)
-	case commands.SwitchAgentReverseCommand:
+	case commands.AgentCycleReverseCommand:
 		updated, cmd := a.app.SwitchAgentReverse()
 		a.app = updated
 		cmds = append(cmds, cmd)
@@ -1197,21 +1156,6 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
 	case commands.ThemeListCommand:
 		themeDialog := dialog.NewThemeDialog()
 		a.modal = themeDialog
-	// case commands.FileListCommand:
-	// 	a.editor.Blur()
-	// 	findDialog := dialog.NewFindDialog(a.fileProvider)
-	// 	cmds = append(cmds, findDialog.Init())
-	// 	a.modal = findDialog
-	case commands.FileCloseCommand:
-		a.fileViewer, cmd = a.fileViewer.Clear()
-		cmds = append(cmds, cmd)
-	case commands.FileDiffToggleCommand:
-		a.fileViewer, cmd = a.fileViewer.ToggleDiff()
-		cmds = append(cmds, cmd)
-		a.app.State.SplitDiff = a.fileViewer.DiffStyle() == fileviewer.DiffStyleSplit
-		cmds = append(cmds, a.app.SaveState())
-	case commands.FileSearchCommand:
-		return a, nil
 	case commands.ProjectInitCommand:
 		cmds = append(cmds, a.app.InitializeProject(context.Background()))
 	case commands.InputClearCommand:
@@ -1242,42 +1186,21 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
 		a.messages = updated.(chat.MessagesComponent)
 		cmds = append(cmds, cmd)
 	case commands.MessagesPageUpCommand:
-		if a.fileViewer.HasFile() {
-			a.fileViewer, cmd = a.fileViewer.PageUp()
-			cmds = append(cmds, cmd)
-		} else {
-			updated, cmd := a.messages.PageUp()
-			a.messages = updated.(chat.MessagesComponent)
-			cmds = append(cmds, cmd)
-		}
+		updated, cmd := a.messages.PageUp()
+		a.messages = updated.(chat.MessagesComponent)
+		cmds = append(cmds, cmd)
 	case commands.MessagesPageDownCommand:
-		if a.fileViewer.HasFile() {
-			a.fileViewer, cmd = a.fileViewer.PageDown()
-			cmds = append(cmds, cmd)
-		} else {
-			updated, cmd := a.messages.PageDown()
-			a.messages = updated.(chat.MessagesComponent)
-			cmds = append(cmds, cmd)
-		}
+		updated, cmd := a.messages.PageDown()
+		a.messages = updated.(chat.MessagesComponent)
+		cmds = append(cmds, cmd)
 	case commands.MessagesHalfPageUpCommand:
-		if a.fileViewer.HasFile() {
-			a.fileViewer, cmd = a.fileViewer.HalfPageUp()
-			cmds = append(cmds, cmd)
-		} else {
-			updated, cmd := a.messages.HalfPageUp()
-			a.messages = updated.(chat.MessagesComponent)
-			cmds = append(cmds, cmd)
-		}
+		updated, cmd := a.messages.HalfPageUp()
+		a.messages = updated.(chat.MessagesComponent)
+		cmds = append(cmds, cmd)
 	case commands.MessagesHalfPageDownCommand:
-		if a.fileViewer.HasFile() {
-			a.fileViewer, cmd = a.fileViewer.HalfPageDown()
-			cmds = append(cmds, cmd)
-		} else {
-			updated, cmd := a.messages.HalfPageDown()
-			a.messages = updated.(chat.MessagesComponent)
-			cmds = append(cmds, cmd)
-		}
-
+		updated, cmd := a.messages.HalfPageDown()
+		a.messages = updated.(chat.MessagesComponent)
+		cmds = append(cmds, cmd)
 	case commands.MessagesCopyCommand:
 		updated, cmd := a.messages.CopyLastMessage()
 		a.messages = updated.(chat.MessagesComponent)
@@ -1327,8 +1250,6 @@ func NewModel(app *app.App) tea.Model {
 		toastManager:         toast.NewToastManager(),
 		interruptKeyState:    InterruptKeyIdle,
 		exitKeyState:         ExitKeyIdle,
-		fileViewer:           fileviewer.New(app),
-		messagesRight:        app.State.MessagesRight,
 	}
 
 	return model

+ 16 - 22
packages/web/src/content/docs/docs/keybinds.mdx

@@ -11,33 +11,19 @@ opencode has a list of keybinds that you can customize through the opencode conf
   "keybinds": {
     "leader": "ctrl+x",
     "app_help": "<leader>h",
-    "switch_agent": "tab",
-    "switch_agent_reverse": "shift+tab",
-
+    "app_exit": "ctrl+c,<leader>q",
     "editor_open": "<leader>e",
-
+    "theme_list": "<leader>t",
+    "project_init": "<leader>i",
+    "tool_details": "<leader>d",
+    "thinking_blocks": "<leader>b",
+    "session_export": "<leader>x",
     "session_new": "<leader>n",
     "session_list": "<leader>l",
     "session_share": "<leader>s",
     "session_unshare": "none",
-    "session_export": "<leader>x",
     "session_interrupt": "esc",
     "session_compact": "<leader>c",
-
-    "tool_details": "<leader>d",
-    "thinking_blocks": "<leader>b",
-    "model_list": "<leader>m",
-    "agent_list": "<leader>a",
-    "model_cycle_recent": "f2",
-    "model_cycle_recent_reverse": "shift+f2",
-    "theme_list": "<leader>t",
-    "project_init": "<leader>i",
-
-    "input_clear": "ctrl+c",
-    "input_paste": "ctrl+v",
-    "input_submit": "enter",
-    "input_newline": "shift+enter,ctrl+j",
-
     "messages_page_up": "pgup",
     "messages_page_down": "pgdown",
     "messages_half_page_up": "ctrl+alt+u",
@@ -47,8 +33,16 @@ opencode has a list of keybinds that you can customize through the opencode conf
     "messages_copy": "<leader>y",
     "messages_undo": "<leader>u",
     "messages_redo": "<leader>r",
-
-    "app_exit": "ctrl+c,<leader>q"
+    "model_list": "<leader>m",
+    "model_cycle_recent": "f2",
+    "model_cycle_recent_reverse": "shift+f2",
+    "agent_list": "<leader>a",
+    "agent_cycle": "tab",
+    "agent_cycle_reverse": "shift+tab",
+    "input_clear": "ctrl+c",
+    "input_paste": "ctrl+v",
+    "input_submit": "enter",
+    "input_newline": "shift+enter,ctrl+j"
   }
 }
 ```