33import os
44import subprocess
55from pathlib import Path
6- from typing import Optional
6+ from typing import Dict , List , Optional
77
88import click
99
1010
11- def generate_powershell_completion () -> str :
12- """Generate PowerShell completion script for slcli."""
13- return """
14- # PowerShell completion for slcli
15- Register-ArgumentCompleter -Native -CommandName slcli -ScriptBlock {
11+ def get_cli_commands_dynamically () -> Dict [str , List [str ]]:
12+ """Extract commands and subcommands from the CLI structure dynamically.
13+
14+ Returns:
15+ Dict mapping command names to lists of their subcommands.
16+ """
17+ try :
18+ # Import the main CLI group to inspect its structure
19+ from slcli .main import cli
20+
21+ commands = {}
22+
23+ # Get top-level commands
24+ commands ["" ] = list (cli .commands .keys ())
25+
26+ # Get subcommands for each command
27+ for cmd_name , cmd_obj in cli .commands .items ():
28+ if isinstance (cmd_obj , click .Group ):
29+ commands [cmd_name ] = list (cmd_obj .commands .keys ())
30+ else :
31+ commands [cmd_name ] = []
32+
33+ return commands
34+ except Exception :
35+ # Fallback to hardcoded commands if introspection fails
36+ return {
37+ "" : [
38+ "completion" ,
39+ "login" ,
40+ "logout" ,
41+ "notebook" ,
42+ "template" ,
43+ "user" ,
44+ "workflow" ,
45+ "workspace" ,
46+ ],
47+ "notebook" : ["list" , "get" , "create" , "update" , "delete" , "run" ],
48+ "template" : ["list" , "get" , "create" , "update" , "delete" ],
49+ "user" : ["list" , "get" , "create" , "update" , "delete" ],
50+ "workflow" : ["list" , "get" , "create" , "update" , "delete" , "run" ],
51+ "workspace" : ["list" , "get" , "create" , "update" , "delete" ],
52+ "completion" : [],
53+ }
54+
55+
56+ def generate_powershell_completion_dynamic () -> str :
57+ """Generate PowerShell completion script using dynamic command discovery."""
58+ commands_dict = get_cli_commands_dynamically ()
59+
60+ # Build the command arrays as PowerShell code
61+ top_level_commands = commands_dict .get ("" , [])
62+
63+ powershell_script = f"""
64+ # PowerShell completion for slcli (dynamically generated)
65+ Register-ArgumentCompleter -Native -CommandName slcli -ScriptBlock {{
1666 param($wordToComplete, $commandAst, $cursorPosition)
1767
1868 # Get all arguments passed so far
19- $arguments = $commandAst.CommandElements | Select-Object -Skip 1 | ForEach-Object { $_.Value }
69+ $arguments = $commandAst.CommandElements | Select-Object -Skip 1 | ForEach-Object {{ $_.Value } }
2070
21- # Function to get available commands
22- function Get-SlcliCommands {
23- param($subCommand = $null)
24-
25- $commands = @()
26-
27- if (-not $subCommand) {
28- # Top-level commands
29- $commands += @('completion', 'login', 'logout', 'notebook', 'template', 'user', 'workflow', 'workspace')
30- } else {
31- switch ($subCommand) {
32- 'notebook' { $commands += @('list', 'get', 'create', 'update', 'delete', 'run') }
33- 'template' { $commands += @('list', 'get', 'create', 'update', 'delete') }
34- 'user' { $commands += @('list', 'get', 'create', 'update', 'delete') }
35- 'workflow' { $commands += @('list', 'get', 'create', 'update', 'delete', 'run') }
36- 'workspace' { $commands += @('list', 'get', 'create', 'update', 'delete') }
37- 'completion' { $commands += @() }
38- }
39- }
40-
41- return $commands
71+ # Dynamically defined command structure
72+ $topLevelCommands = @({ ', ' .join (f"'{ cmd } '" for cmd in top_level_commands )} )
73+
74+ # Subcommand mappings
75+ $subCommands = @{{"""
76+
77+ # Add subcommand mappings
78+ for cmd , subcmds in commands_dict .items ():
79+ if cmd and subcmds : # Skip empty command key and empty subcommand lists
80+ subcmd_list = ", " .join (f"'{ sub } '" for sub in subcmds )
81+ powershell_script += f"\n '{ cmd } ' = @({ subcmd_list } )"
82+
83+ powershell_script += """
4284 }
4385
44- # Function to get available options
86+ # Function to get available options dynamically by calling slcli --help
4587 function Get-SlcliOptions {
4688 param($command, $subCommand = $null)
4789
4890 $options = @()
4991
50- # Global options
51- $options += @('--help', '-h', '--version')
52-
53- # Command-specific options
54- if ($command -eq 'completion') {
55- $options += @('--shell', '--install')
56- } elseif ($command -eq 'login') {
57- $options += @('--url', '--api-key')
58- } elseif ($subCommand) {
59- switch ($subCommand) {
60- 'list' { $options += @('--format', '-f', '--filter', '--take', '--sortby', '--order') }
61- 'get' { $options += @('--id', '--name', '--email', '--format', '-f') }
62- 'create' {
63- switch ($command) {
64- 'user' { $options += @('--first-name', '--last-name', '--email', '--niua-id', '--accepted-tos', '--policies', '--keywords', '--properties') }
65- default { $options += @('--name', '--description') }
66- }
67- }
68- 'update' { $options += @('--id', '--name', '--description') }
69- 'delete' { $options += @('--id') }
92+ try {
93+ # Build command to get help for
94+ $helpCmd = @('slcli')
95+ if ($command) { $helpCmd += $command }
96+ if ($subCommand) { $helpCmd += $subCommand }
97+ $helpCmd += '--help'
98+
99+ # Get options from help output
100+ $helpOutput = & $helpCmd[0] $helpCmd[1..($helpCmd.Length-1)] 2>$null
101+ if ($LASTEXITCODE -eq 0) {
102+ $options = $helpOutput |
103+ Select-String -Pattern "^\\ s+(--?\\ w+(?:-\\ w+)*)" |
104+ ForEach-Object { $_.Matches[0].Groups[1].Value }
105+ return $options
70106 }
107+ } catch {
108+ # Fallback to basic options
71109 }
72110
73- return $options
74- }
75-
76- # Function to get option values
77- function Get-SlcliOptionValues {
78- param($option)
111+ # Fallback hardcoded options
112+ $options += @('--help', '-h')
79113
80- switch ($option) {
81- '--format' { return @('table', 'json') }
82- '-f' { return @('table', 'json') }
83- '--shell' { return @('bash', 'zsh', 'fish', 'powershell') }
84- '--order' { return @('ascending', 'descending') }
85- '--sortby' {
86- # This would depend on the command context
87- return @('name', 'created', 'updated', 'firstName', 'lastName', 'email', 'status')
88- }
89- default { return @() }
114+ if (-not $command) {
115+ $options += @('--version')
90116 }
117+
118+ return $options
91119 }
92120
93- # Parse current command context
94- $currentCommand = $null
95- $currentSubCommand = $null
96- $lastOption = $null
121+ # Determine what to complete based on current position
122+ $completions = @()
97123
98- for ($i = 0; $i -lt $arguments.Count; $i++) {
99- $arg = $arguments[$i]
124+ if ($arguments.Count -eq 0) {
125+ # Complete top-level commands
126+ $completions = $topLevelCommands | Where-Object { $_ -like "$wordToComplete*" }
127+ } elseif ($arguments.Count -eq 1) {
128+ $firstArg = $arguments[0]
100129
101- if ($arg.StartsWith('--') -or $arg.StartsWith('-')) {
102- $lastOption = $arg
103- } elseif (-not $currentCommand) {
104- $currentCommand = $arg
105- } elseif (-not $currentSubCommand) {
106- $currentSubCommand = $arg
107- } else {
108- $lastOption = $null
109- }
110- }
111-
112- # If we're completing an option value
113- if ($lastOption) {
114- $values = Get-SlcliOptionValues -option $lastOption
115- $values | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
116- [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
130+ if ($firstArg.StartsWith('-')) {
131+ # Complete global options
132+ $completions = Get-SlcliOptions | Where-Object { $_ -like "$wordToComplete*" }
133+ } elseif ($subCommands.ContainsKey($firstArg)) {
134+ # Complete subcommands
135+ $completions = $subCommands[$firstArg] | Where-Object { $_ -like "$wordToComplete*" }
117136 }
118- return
119- }
120-
121- # If we're completing an option
122- if ($wordToComplete.StartsWith('-')) {
123- $options = Get-SlcliOptions -command $currentCommand -subCommand $currentSubCommand
124- $options | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
125- [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterName', $_)
137+ } elseif ($arguments.Count -eq 2) {
138+ $command = $arguments[0]
139+ $subCommand = $arguments[1]
140+
141+ if ($subCommand.StartsWith('-')) {
142+ # Complete command options
143+ $completions = Get-SlcliOptions $command | Where-Object { $_ -like "$wordToComplete*" }
144+ } else {
145+ # Complete subcommand options
146+ $completions = Get-SlcliOptions $command $subCommand | Where-Object { $_ -like "$wordToComplete*" }
126147 }
127- return
148+ } else {
149+ # Complete options for the current command/subcommand context
150+ $command = $arguments[0]
151+ $subCommand = if ($arguments.Count -gt 1 -and -not $arguments[1].StartsWith('-')) { $arguments[1] } else { $null }
152+ $completions = Get-SlcliOptions $command $subCommand | Where-Object { $_ -like "$wordToComplete*" }
128153 }
129154
130- # If we're completing a command or subcommand
131- if (-not $currentCommand) {
132- # Top-level commands
133- $commands = Get-SlcliCommands
134- $commands | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
135- [System.Management.Automation.CompletionResult]::new($_, $_, 'Command', $_)
136- }
137- } elseif (-not $currentSubCommand) {
138- # Subcommands
139- $commands = Get-SlcliCommands -subCommand $currentCommand
140- $commands | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
141- [System.Management.Automation.CompletionResult]::new($_, $_, 'Command', $_)
142- }
143- }
144- }
155+ $completions | ForEach-Object {{
156+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
157+ }}
158+ }}
145159"""
146160
161+ return powershell_script
162+
147163
148164def detect_shell () -> Optional [str ]:
149165 """Auto-detect the current shell."""
@@ -162,7 +178,8 @@ def detect_shell() -> Optional[str]:
162178def generate_completion_script (shell : str ) -> Optional [str ]:
163179 """Generate completion script for the specified shell."""
164180 if shell .lower () == "powershell" :
165- return generate_powershell_completion ()
181+ # Use dynamic completion for PowerShell
182+ return generate_powershell_completion_dynamic ()
166183
167184 # For bash, zsh, fish - use Click's built-in completion
168185 env_vars = {
@@ -287,6 +304,7 @@ def install_powershell_completion(completion_script: str) -> bool:
287304def install_completion_for_shell (shell : str ) -> bool :
288305 """Install completion script for the specified shell."""
289306 completion_script = generate_completion_script (shell )
307+
290308 if not completion_script :
291309 click .echo ("✗ Failed to generate completion script" , err = True )
292310 return False
@@ -346,6 +364,7 @@ def completion(shell, install):
346364 else :
347365 # Just output the completion script
348366 completion_script = generate_completion_script (shell )
367+
349368 if completion_script :
350369 click .echo (completion_script )
351370 else :
0 commit comments