summaryrefslogtreecommitdiff
path: root/internal/appconfig
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-25 16:12:23 +0300
committerPaul Buetow <paul@buetow.org>2026-04-25 16:12:23 +0300
commita1031b761ff0c5cd79f3f0906e5c3b33fa849f37 (patch)
tree18e9401ecc83ebe0c1a35bf92152a81a195e24f2 /internal/appconfig
parentc73a92022d39e73d41b00cf72d959bf0d26c8d6f (diff)
feat: configurable hexai-tmux-action menu via [[tmux_action.menu]]
When [[tmux_action.menu]] is defined in config it fully replaces the built-in menu. Each entry takes a kind (built-in or "custom"), optional title/hotkey overrides, and optional custom_id for embedding custom actions directly in the main menu. Hotkey dispatch is now dynamic so any single-character hotkey works without code changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/appconfig')
-rw-r--r--internal/appconfig/app_sections.go4
-rw-r--r--internal/appconfig/config_load.go18
-rw-r--r--internal/appconfig/config_merge.go8
-rw-r--r--internal/appconfig/config_types.go25
4 files changed, 55 insertions, 0 deletions
diff --git a/internal/appconfig/app_sections.go b/internal/appconfig/app_sections.go
index 5426f2a..6dd60c6 100644
--- a/internal/appconfig/app_sections.go
+++ b/internal/appconfig/app_sections.go
@@ -114,6 +114,8 @@ type FeatureConfig struct {
TmuxEditPopupHeight string `json:"-"`
TmuxEditDefaultAgent string `json:"-"`
TmuxEditAgents []TmuxEditAgentCfg `json:"-"`
+ // TmuxAction: configurable main menu for hexai-tmux-action
+ TmuxActionMenu []TmuxActionMenuEntry `json:"-"`
// MCP: Model Context Protocol server settings
MCPPromptsDir string `json:"-"` // Directory for prompt storage
MCPSlashCommandSync bool `json:"-"` // Enable slash command sync
@@ -205,6 +207,7 @@ func (a *App) FeatureSection() FeatureConfig {
f := a.FeatureConfig
f.IgnoreExtraPatterns = slices.Clone(a.IgnoreExtraPatterns)
f.TmuxEditAgents = append([]TmuxEditAgentCfg{}, a.TmuxEditAgents...)
+ f.TmuxActionMenu = append([]TmuxActionMenuEntry{}, a.TmuxActionMenu...)
return f
}
@@ -214,4 +217,5 @@ func (a *App) ApplyFeatureSection(features FeatureConfig) {
a.FeatureConfig = features
a.IgnoreExtraPatterns = slices.Clone(features.IgnoreExtraPatterns)
a.TmuxEditAgents = append([]TmuxEditAgentCfg{}, features.TmuxEditAgents...)
+ a.TmuxActionMenu = append([]TmuxActionMenuEntry{}, features.TmuxActionMenu...)
}
diff --git a/internal/appconfig/config_load.go b/internal/appconfig/config_load.go
index 88750fd..aa169bc 100644
--- a/internal/appconfig/config_load.go
+++ b/internal/appconfig/config_load.go
@@ -284,6 +284,24 @@ func applyFeatureSections(fc *fileConfig, out *App) {
applyStatsSection(fc, out)
fc.applyTmuxEdit(out)
applyMCPSection(fc, out)
+ applyTmuxActionSection(fc, out)
+}
+
+// applyTmuxActionSection converts [[tmux_action.menu]] entries into App fields.
+func applyTmuxActionSection(fc *fileConfig, out *App) {
+ if len(fc.TmuxAction.Menu) == 0 {
+ return
+ }
+ entries := make([]TmuxActionMenuEntry, 0, len(fc.TmuxAction.Menu))
+ for _, e := range fc.TmuxAction.Menu {
+ entries = append(entries, TmuxActionMenuEntry{
+ Kind: strings.TrimSpace(e.Kind),
+ CustomID: strings.TrimSpace(e.CustomID),
+ Title: strings.TrimSpace(e.Title),
+ Hotkey: strings.TrimSpace(e.Hotkey),
+ })
+ }
+ out.TmuxActionMenu = entries
}
func applyGeneralSection(fc *fileConfig, out *App) {
diff --git a/internal/appconfig/config_merge.go b/internal/appconfig/config_merge.go
index e31e0dc..7a99c94 100644
--- a/internal/appconfig/config_merge.go
+++ b/internal/appconfig/config_merge.go
@@ -11,6 +11,7 @@ func (a *App) mergeWith(other *App) {
a.mergeSurfaceModels(other)
a.mergePrompts(other)
a.mergeTmuxEdit(other)
+ a.mergeTmuxAction(other)
}
// mergeBasics merges general (non-provider) fields.
@@ -227,6 +228,13 @@ func mergeStringField(dst *string, src string) {
}
}
+// mergeTmuxAction replaces the action menu when the incoming config defines one.
+func (a *App) mergeTmuxAction(other *App) {
+ if len(other.TmuxActionMenu) > 0 {
+ a.TmuxActionMenu = append([]TmuxActionMenuEntry{}, other.TmuxActionMenu...)
+ }
+}
+
// mergeTmuxEdit copies non-empty tmux edit settings from other.
func (a *App) mergeTmuxEdit(other *App) {
if s := strings.TrimSpace(other.TmuxEditPopupWidth); s != "" {
diff --git a/internal/appconfig/config_types.go b/internal/appconfig/config_types.go
index 54f9bcf..5c6cf0c 100644
--- a/internal/appconfig/config_types.go
+++ b/internal/appconfig/config_types.go
@@ -35,6 +35,19 @@ type CustomAction struct {
User string // optional; if set, render with available vars
}
+// TmuxActionMenuEntry configures a single entry in the hexai-tmux-action menu.
+// Set Kind to a built-in action kind (rewrite, simplify, document, gotest,
+// fix_typos, custom_prompt, skip) or to "custom" to embed a custom action
+// directly in the main menu (requires CustomID referencing a
+// [[prompts.code_action.custom]] entry). Title and Hotkey are optional
+// overrides; built-in defaults are used when left empty.
+type TmuxActionMenuEntry struct {
+ Kind string // built-in kind or "custom"
+ CustomID string // used when Kind == "custom", references a custom action by id
+ Title string // optional title override
+ Hotkey string // optional single-character hotkey override
+}
+
// TmuxEditAgentCfg describes an AI agent's detection and interaction patterns
// for the tmux popup editor (hexai-tmux-edit).
type TmuxEditAgentCfg struct {
@@ -155,6 +168,7 @@ type fileConfig struct {
Stats sectionStats `toml:"stats"`
Ignore sectionIgnore `toml:"ignore"`
TmuxEdit sectionTmuxEdit `toml:"tmux_edit"`
+ TmuxAction sectionTmuxAction `toml:"tmux_action"`
MCP sectionMCP `toml:"mcp"`
}
@@ -352,3 +366,14 @@ type sectionCustomAction struct {
type sectionTmux struct {
CustomMenuHotkey string `toml:"custom_menu_hotkey"`
}
+
+type sectionTmuxAction struct {
+ Menu []sectionTmuxActionMenuEntry `toml:"menu"`
+}
+
+type sectionTmuxActionMenuEntry struct {
+ Kind string `toml:"kind"`
+ CustomID string `toml:"custom_id"`
+ Title string `toml:"title"`
+ Hotkey string `toml:"hotkey"`
+}