-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Area
All command groups — cross-cutting UX improvement
Problem or use case
Cobra gives us basic command-name completion for free, but users still have to remember and type exact values for flags and positional arguments — project IDs, job IDs, agent names, pool names, status enums, etc. Major CLIs like gh and glab complete these dynamically, which dramatically reduces friction:
# Today: user has to know the exact job ID
teamcity run start Falcon_B<TAB> # nothing happens
# Goal: complete from the server
teamcity run start Falcon_B<TAB>
Falcon_Build Falcon_BuildDocker Falcon_BuildNative
# Same for flags
teamcity run list --status <TAB>
success failure running error unknown
teamcity agent list --pool <TAB>
Default Linux-Agents Windows-CloudWithout completions, users fall back to running teamcity project list or teamcity agent list in a separate terminal just to copy-paste an ID — that's the exact workflow completions eliminate.
Proposed solution
Implementation approach
- Create a shared completion helper (e.g.
internal/completion/) that wraps API calls with a short timeout and returns[]string+cobra.ShellCompDirective. - Register completions via
cobra.RegisterFlagCompletionFunc()for flags andValidArgsFunctionfor positional args in each command's setup. - Graceful degradation: if the server is unreachable or the token is missing, return
cobra.ShellCompDirectiveNoFileComp(no error, just no suggestions).
Tier 1 — Static enums (no API call, works offline)
| Command | Flag / Arg | Values |
|---|---|---|
run list |
--status |
success, failure, running, error, unknown |
job tree |
--only |
dependents, dependencies |
These are the quickest wins — just cobra.FixedCompletions(...) or a static slice.
Tier 2 — Single API call (most common, highest value)
| Command(s) | Flag / Arg | API source |
|---|---|---|
run start <job-id> |
positional | GetBuildTypes() → ID list |
run list, queue list |
--job / -j |
GetBuildTypes() → ID list |
run list |
--project / -p |
GetProjects() → ID list |
run watch, run log, run download, run artifacts, run pin/unpin/tag/untag |
<run-id> positional |
GetBuilds() → recent IDs |
queue remove, queue top, queue approve |
<run-id> positional |
GetBuildQueue() → queued IDs |
agent view, agent enable/disable/authorize/reboot, agent jobs, agent move |
<agent> positional |
GetAgents() → ID + name |
agent list |
--pool / -p |
GetAgentPools() → pool names |
agent move |
<pool-id> positional |
GetAgentPools() → pool IDs |
pool view, pool link/unlink |
<pool-id> positional |
GetAgentPools() → pool IDs |
pool link/unlink |
<project-id> positional |
GetProjects() → project IDs |
project view, project param * |
<project-id> positional |
GetProjects() → project IDs |
job view, job pause/resume, job tree, job param * |
<job-id> positional |
GetBuildTypes() → job IDs |
A small set of reusable completion functions covers all of these:
// Sketch — not prescriptive, just to illustrate scope
func CompleteJobIDs(f *cmdutil.Factory) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
client, err := f.Client()
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
ctx, cancel := context.WithTimeout(cmd.Context(), 2*time.Second)
defer cancel()
types, err := client.GetBuildTypes(ctx, api.BuildTypesOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
var completions []string
for _, bt := range types {
completions = append(completions, fmt.Sprintf("%s\t%s", bt.ID, bt.Name))
}
return completions, cobra.ShellCompDirectiveNoFileComp
}
}Tier 3 — Context-dependent (harder, lower priority)
| Command(s) | Flag / Arg | Depends on | API source |
|---|---|---|---|
project param get/set/delete |
<name> positional |
resolved <project-id> |
GetProjectParameters(projectID) |
job param get/set/delete |
<name> positional |
resolved <job-id> |
GetBuildTypeParameters(jobID) |
run download |
--path / -p |
resolved <run-id> |
GetArtifacts(buildID, "") |
These require reading a previously-resolved positional arg (args[0]) before querying the API. Cobra supports this — the args slice in ValidArgsFunction contains already-completed arguments.
Scope explicitly excluded
- Branch names: requires VCS API integration not currently exposed
- Parameter values: too context-dependent, no universal source
--since/--untildates: free-form input, shell completion not helpful
Concrete examples of the end result
# Complete project IDs (with description after tab)
$ teamcity project view <TAB>
MyProject My Project Name
Falcon Falcon CI
InternalTools Internal Tooling
# Complete agent by name or ID
$ teamcity agent enable <TAB>
1 Agent-Linux-01
2 Agent-Linux-02
3 Agent-Windows-Cloud
# Complete pool for agent list filter
$ teamcity agent list --pool <TAB>
Default Linux-Agents Windows-Cloud
# Complete queued build IDs
$ teamcity queue remove <TAB>
98701 98702 98715
# Context-dependent: parameter names after project ID is resolved
$ teamcity project param get MyProject <TAB>
env.JAVA_HOME env.BUILD_NUMBER system.teamcity.version
# Static enum — works offline
$ teamcity run list --status <TAB>
success failure running error unknownImplementation notes
- Timeout: API calls during completion must be fast (2s hard cap). Users will not tolerate a laggy TAB.
- Caching: Optional, but a short TTL cache (30-60s) for project/job lists would help for repeated completions in the same session.
\tdescriptions: Cobra supportsvalue\tdescriptionformat — use it to show both ID and human-readable name (e.g.Falcon_Build\tFalcon CI Build).- No file completion: Most flags don't accept file paths. Return
cobra.ShellCompDirectiveNoFileCompby default to avoid noise. - Testing:
cobra.GenBashCompletionV2/cobra.GenZshCompletionV2can be tested via__completehidden command — add unit tests that mock the API client and verify completion output.
Alternatives considered
- Static completion scripts shipped with the binary: Would handle enums but not dynamic values. Not viable for IDs that vary per server.
- Completion via alias expansion: Too fragile, no standard across shells.
Contribution
Happy to break this into smaller PRs per tier if that helps review. Tier 1 (static enums) could land independently as a quick win.