@@ -6,9 +6,13 @@ import (
66 "os"
77 "path/filepath"
88 "runtime"
9+ "sort"
910 "strings"
1011
12+ "github.com/GrayCodeAI/eyrie/catalog/registry"
1113 "github.com/GrayCodeAI/hawk/internal/provider/routing"
14+ "github.com/spf13/cobra"
15+ "github.com/spf13/pflag"
1216)
1317
1418// FlagInfo describes a CLI flag for completion generation.
@@ -50,212 +54,19 @@ func NewCompletionGenerator() *CompletionGenerator {
5054}
5155
5256func (g * CompletionGenerator ) populateCommands () {
53- g .Commands = []CommandInfo {
54- {
55- Name : "exec" ,
56- Description : "Execute a single command non-interactively" ,
57- Flags : []FlagInfo {
58- {Name : "output-format" , Short : "o" , Description : "Output format: text or json" , Type : "string" , Choices : []string {"text" , "json" }},
59- {Name : "auto" , Description : "Autonomy level" , Type : "string" , Choices : []string {"supervised" , "basic" , "semi" , "full" , "yolo" }},
60- {Name : "model" , Short : "m" , Description : "Model ID to use" , Type : "string" },
61- {Name : "max-turns" , Description : "Maximum agentic turns" , Type : "int" },
62- {Name : "cwd" , Description : "Working directory" , Type : "string" },
63- {Name : "agent" , Description : "Agent persona to use" , Type : "string" },
64- {Name : "session-id" , Short : "s" , Description : "Continue an existing session" , Type : "string" },
65- },
66- },
67- {
68- Name : "daemon" ,
69- Description : "Start the hawk background daemon" ,
70- },
71- {
72- Name : "mission" ,
73- Description : "Multi-agent orchestration on parallel git branches" ,
74- },
75- {
76- Name : "search" ,
77- Description : "Search code using semantic and structural queries" ,
78- },
79- {
80- Name : "agent" ,
81- Description : "Manage agent personas" ,
82- },
83- {
84- Name : "doctor" ,
85- Description : "Run local diagnostics" ,
86- },
87- {
88- Name : "config" ,
89- Description : "Show or update settings" ,
90- Subcommands : []CommandInfo {
91- {Name : "get" , Description : "Get a setting value" },
92- {Name : "set" , Description : "Set a setting value" },
93- {Name : "provider" , Description : "Set the LLM provider" },
94- {Name : "model" , Description : "Set the model" },
95- {Name : "keys" , Description : "Show API key configuration" },
96- },
97- },
98- {
99- Name : "sessions" ,
100- Description : "List saved sessions" ,
101- },
102- {
103- Name : "tools" ,
104- Description : "List built-in tools" ,
105- },
106- {
107- Name : "skills" ,
108- Description : "Manage skills" ,
109- Subcommands : []CommandInfo {
110- {Name : "list" , Description : "List installed skills" },
111- {Name : "search" , Description : "Search the community skill registry" },
112- {Name : "install" , Description : "Install a skill" },
113- {Name : "remove" , Description : "Remove a skill" },
114- {Name : "info" , Description : "Show skill details" },
115- {Name : "trending" , Description : "Show trending skills" },
116- {Name : "audit" , Description : "Audit installed skills" },
117- },
118- },
119- {
120- Name : "completion" ,
121- Description : "Generate shell completion script" ,
122- Subcommands : []CommandInfo {
123- {Name : "bash" , Description : "Generate bash completion" },
124- {Name : "zsh" , Description : "Generate zsh completion" },
125- {Name : "fish" , Description : "Generate fish completion" },
126- {Name : "powershell" , Description : "Generate PowerShell completion" },
127- },
128- },
129- {
130- Name : "research" ,
131- Description : "Autonomous research loop" ,
132- Flags : []FlagInfo {
133- {Name : "grep" , Description : "Grep pattern to extract metric" , Type : "string" },
134- {Name : "direction" , Description : "Optimization direction" , Type : "string" , Choices : []string {"lower" , "higher" }},
135- {Name : "budget" , Description : "Time budget per experiment in minutes" , Type : "int" },
136- {Name : "branch" , Description : "Git branch prefix" , Type : "string" },
137- {Name : "results" , Description : "Results TSV file path" , Type : "string" },
138- },
139- },
140- {
141- Name : "context" ,
142- Description : "Export project context as a single document" ,
143- Flags : []FlagInfo {
144- {Name : "focus" , Description : "Focus on a specific area" , Type : "string" },
145- {Name : "output" , Short : "o" , Description : "Write context to a file" , Type : "string" },
146- },
147- },
148- {
149- Name : "version" ,
150- Description : "Print hawk version" ,
151- },
152- {
153- Name : "setup" ,
154- Description : "Run first-time setup again" ,
155- },
156- {
157- Name : "plugin" ,
158- Description : "Manage plugins" ,
159- Subcommands : []CommandInfo {
160- {Name : "list" , Description : "List installed plugins" },
161- {Name : "install" , Description : "Install a plugin" },
162- {Name : "uninstall" , Description : "Uninstall a plugin" },
163- },
164- },
165- {
166- Name : "mcp" ,
167- Description : "Show MCP server configuration" ,
168- },
169- {
170- Name : "inspect" ,
171- Description : "Inspect session or context state" ,
172- },
173- {
174- Name : "plan" ,
175- Description : "Enter plan mode (read-only analysis)" ,
176- },
177- {
178- Name : "rules" ,
179- Description : "Manage project rules" ,
180- },
181- {
182- Name : "sandbox" ,
183- Description : "Bash permission profile (strict/workspace/off); not Docker container mode" ,
184- },
185- {
186- Name : "cost" ,
187- Description : "Show token usage and cost summary" ,
188- },
189- {
190- Name : "snapshot" ,
191- Description : "Manage session snapshots" ,
192- },
193- {
194- Name : "sight" ,
195- Description : "Visual analysis tools" ,
196- },
197- {
198- Name : "fingerprint" ,
199- Description : "Show project fingerprint" ,
200- },
201- }
57+ g .Commands = commandInfosFromCobra (rootCmd .Commands ())
58+ augmentCommandInfos (g .Commands )
20259}
20360
20461func (g * CompletionGenerator ) populateFlags () {
205- g .Flags = []FlagInfo {
206- {Name : "provider" , Description : "LLM provider" , Type : "string" , Choices : nil }, // choices filled from Providers
207- {Name : "model" , Short : "m" , Description : "Model to use" , Type : "string" },
208- {Name : "print" , Short : "p" , Description : "Print response and exit" , Type : "bool" },
209- {Name : "resume" , Short : "r" , Description : "Resume a saved session by ID" , Type : "string" },
210- {Name : "continue" , Short : "c" , Description : "Continue the most recent conversation" , Type : "bool" },
211- {Name : "mcp" , Description : "MCP server command" , Type : "string" },
212- {Name : "allowed-tools" , Description : "Tool permission rules to allow" , Type : "string" },
213- {Name : "disallowed-tools" , Description : "Tool permission rules to deny" , Type : "string" },
214- {Name : "permission-mode" , Description : "Permission mode" , Type : "string" , Choices : []string {"default" , "acceptEdits" , "bypassPermissions" , "dontAsk" , "plan" }},
215- {Name : "dangerously-skip-permissions" , Description : "Bypass all permission checks" , Type : "bool" },
216- {Name : "max-turns" , Description : "Maximum number of agentic turns" , Type : "int" },
217- {Name : "max-budget-usd" , Description : "Maximum estimated API spend in USD" , Type : "string" },
218- {Name : "system-prompt" , Description : "System prompt to use" , Type : "string" },
219- {Name : "system-prompt-file" , Description : "Read system prompt from a file" , Type : "string" },
220- {Name : "append-system-prompt" , Description : "Append text to system prompt" , Type : "string" },
221- {Name : "append-system-prompt-file" , Description : "Read text from a file and append it to the system prompt" , Type : "string" },
222- {Name : "output-format" , Description : "Output format for --print" , Type : "string" , Choices : []string {"text" , "json" , "stream-json" }},
223- {Name : "input-format" , Description : "Input format for --print" , Type : "string" , Choices : []string {"text" , "stream-json" }},
224- {Name : "no-session-persistence" , Description : "Disable session persistence in print mode" , Type : "bool" },
225- {Name : "session-id" , Description : "Use a specific session ID" , Type : "string" },
226- {Name : "settings" , Description : "Path to a settings JSON file" , Type : "string" },
227- {Name : "add-dir" , Description : "Additional directories to include" , Type : "string" },
228- {Name : "tools" , Description : "Available tools configuration" , Type : "string" },
229- {Name : "sandbox" , Description : "Bash permission profile (not Docker; use --no-container for host)" , Type : "string" , Choices : []string {"strict" , "workspace" , "off" }},
230- {Name : "auto-commit" , Description : "Auto-commit file changes" , Type : "bool" },
231- {Name : "watch" , Description : "Watch working directory for file changes" , Type : "bool" },
232- {Name : "vibe" , Description : "Vibe coding mode" , Type : "bool" },
233- {Name : "power" , Description : "Power level 1-10" , Type : "int" },
234- {Name : "timeout" , Description : "Time budget for the operation" , Type : "string" },
235- {Name : "council" , Description : "Consult multiple models" , Type : "bool" },
236- {Name : "teach" , Description : "Explain reasoning as the agent works" , Type : "bool" },
237- {Name : "teach-depth" , Description : "Explanation depth: 1=what, 2=why, 3=how" , Type : "int" },
238- {Name : "auto-skill" , Description : "Auto-detect project and install matching skills" , Type : "bool" },
239- {Name : "container" , Description : "Force container mode" , Type : "bool" },
240- {Name : "no-container" , Description : "Disable container mode" , Type : "bool" },
241- {Name : "version" , Short : "v" , Description : "Output the version number" , Type : "bool" },
242- {Name : "fork-session" , Description : "Create a new session ID when resuming" , Type : "bool" },
243- }
62+ g .Flags = flagsFromFlagSet (rootCmd .Flags ())
24463}
24564
24665func (g * CompletionGenerator ) populateProviders () {
247- g .Providers = []string {
248- "anthropic" ,
249- "openai" ,
250- "gemini" ,
251- "openrouter" ,
252- "grok" ,
253- "groq" ,
254- "deepseek" ,
255- "mistral" ,
256- "bedrock" ,
257- "vertex" ,
258- "ollama" ,
66+ providers := registry .All ()
67+ g .Providers = make ([]string , 0 , len (providers ))
68+ for _ , provider := range providers {
69+ g .Providers = append (g .Providers , provider .ProviderID )
25970 }
26071}
26172
@@ -601,6 +412,10 @@ func (g *CompletionGenerator) GenerateFish() string {
601412
602413// GenerateJSON returns a machine-readable JSON completion spec for IDE integration.
603414func (g * CompletionGenerator ) GenerateJSON () string {
415+ v := strings .TrimSpace (version )
416+ if v == "" {
417+ v = "dev"
418+ }
604419 spec := struct {
605420 Name string `json:"name"`
606421 Version string `json:"version"`
@@ -611,7 +426,7 @@ func (g *CompletionGenerator) GenerateJSON() string {
611426 Models []string `json:"models"`
612427 }{
613428 Name : "hawk" ,
614- Version : "1.0.0" ,
429+ Version : v ,
615430 Commands : g .Commands ,
616431 GlobalFlags : g .Flags ,
617432 SlashCommands : g .SlashCommands ,
@@ -626,6 +441,104 @@ func (g *CompletionGenerator) GenerateJSON() string {
626441 return string (data )
627442}
628443
444+ func commandInfosFromCobra (cmds []* cobra.Command ) []CommandInfo {
445+ infos := make ([]CommandInfo , 0 , len (cmds ))
446+ for _ , cmd := range cmds {
447+ if cmd .Hidden {
448+ continue
449+ }
450+ info := CommandInfo {
451+ Name : cmd .Name (),
452+ Description : strings .TrimSpace (cmd .Short ),
453+ Flags : flagsFromFlagSet (cmd .NonInheritedFlags ()),
454+ }
455+ info .Subcommands = commandInfosFromCobra (cmd .Commands ())
456+ infos = append (infos , info )
457+ }
458+ return infos
459+ }
460+
461+ func augmentCommandInfos (commands []CommandInfo ) {
462+ for i := range commands {
463+ switch commands [i ].Name {
464+ case "completion" :
465+ commands [i ].Subcommands = []CommandInfo {
466+ {Name : "bash" , Description : "Generate bash completion" },
467+ {Name : "zsh" , Description : "Generate zsh completion" },
468+ {Name : "fish" , Description : "Generate fish completion" },
469+ {Name : "powershell" , Description : "Generate PowerShell completion" },
470+ {Name : "json" , Description : "Generate machine-readable completion metadata" },
471+ }
472+ case "config" :
473+ commands [i ].Subcommands = []CommandInfo {
474+ {Name : "get" , Description : "Get a setting value" },
475+ {Name : "set" , Description : "Set a setting value" },
476+ {Name : "provider" , Description : "Set the LLM provider" },
477+ {Name : "model" , Description : "Set the model" },
478+ {Name : "keys" , Description : "Show API key configuration" },
479+ {Name : "routing-preview" , Description : "Show routing JSON for a model" },
480+ {Name : "migrate-deployments" , Description : "Upgrade legacy provider config to deployments v2" },
481+ }
482+ }
483+ if len (commands [i ].Subcommands ) > 0 {
484+ augmentCommandInfos (commands [i ].Subcommands )
485+ }
486+ }
487+ }
488+
489+ func flagsFromFlagSet (fs * pflag.FlagSet ) []FlagInfo {
490+ if fs == nil {
491+ return nil
492+ }
493+ var flags []FlagInfo
494+ fs .VisitAll (func (f * pflag.Flag ) {
495+ if f == nil || f .Name == "help" {
496+ return
497+ }
498+ flags = append (flags , FlagInfo {
499+ Name : f .Name ,
500+ Short : f .Shorthand ,
501+ Description : f .Usage ,
502+ Type : normalizeFlagType (f .Value .Type ()),
503+ Choices : flagChoices (f .Name ),
504+ })
505+ })
506+ sort .SliceStable (flags , func (i , j int ) bool {
507+ return flags [i ].Name < flags [j ].Name
508+ })
509+ return flags
510+ }
511+
512+ func normalizeFlagType (flagType string ) string {
513+ switch flagType {
514+ case "bool" :
515+ return "bool"
516+ case "int" , "int8" , "int16" , "int32" , "int64" , "uint" , "uint8" , "uint16" , "uint32" , "uint64" :
517+ return "int"
518+ default :
519+ return "string"
520+ }
521+ }
522+
523+ func flagChoices (name string ) []string {
524+ switch name {
525+ case "permission-mode" :
526+ return []string {"default" , "acceptEdits" , "bypassPermissions" , "dontAsk" , "plan" }
527+ case "output-format" :
528+ return []string {"text" , "json" , "stream-json" }
529+ case "input-format" :
530+ return []string {"text" , "stream-json" }
531+ case "sandbox" :
532+ return []string {"strict" , "workspace" , "off" }
533+ case "direction" :
534+ return []string {"lower" , "higher" }
535+ case "auto" :
536+ return []string {"supervised" , "basic" , "semi" , "full" , "yolo" }
537+ default :
538+ return nil
539+ }
540+ }
541+
629542// InstallCompletion returns the filesystem path where the completion script
630543// should be installed for the given shell. It does not write the file; the
631544// caller should decide whether to proceed.
0 commit comments