You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Sub-issue of #246. Hardened spec — do not re-derive decisions. Mirrors internal/cmd/pr/view/view.go and uses Go text templates via internal/template.Template (the same engine used by azdo pr view).
Command Description
Display the details of a single Azure DevOps agent. The target is given as a single positional argument of the form [ORGANIZATION/]POOL/AGENT where each of the POOL and AGENT segments is either a positive integer (used as the ID directly) or a name resolved against the Agents REST 7.1 endpoint. The matching TaskAgent is fetched and rendered as a Go text template showing Status, Version, and capabilities (when --include-capabilities is set) with their respective child values.
GET https://dev.azure.com/{organization}/_apis/distributedtask/pools/{poolId}/agents/{agentId}?includeCapabilities={bool}&includeAssignedRequest={bool}&includeLastCompletedRequest={bool}&api-version=7.1
Locked Decisions (do not re-derive)
#
Decision
Rationale
1
Use the vendored SDK taskagent.Client.GetAgent for the final fetch, plus taskagent.Client.GetAgentPools and taskagent.Client.GetAgents for the two-stage name resolution. All three mocks are already generated.
Reuse the vendored taskagent client.
2
Use: "show [ORGANIZATION/]POOL/AGENT". The pool and agent are positional segments. Each segment is either a positive integer (used as the ID directly) or a name resolved by calling the appropriate list API and picking the first case-insensitive match. Errors on zero matches ("not found") or more than one match ("multiple named y found; specify the numeric ID").
Mirrors the variablegroup/delete target-resolution pattern (#243, #245) and the Python get_definition_id_from_name helper. Agents are org-scoped, not project-scoped.
3
Args: util.ExactArgs(1, "agent target is required"). The single positional argument is the full [ORG/]POOL/AGENT string, parsed by util.ParsePoolAgentTargetWithDefaultOrganization introduced by this story. Empty or whitespace-only input, missing pool, or missing agent all error from the parser.
Cheap pre-flight validation; REST 4xx is unhelpful.
4
Optional organization prefix (3-segment form [ORG/]POOL/AGENT) falls back to the configured default organization when omitted. The helper's 2-segment form (POOL/AGENT) requires a default organization; the 3-segment form (ORG/POOL/AGENT) does not.
Matches the implicit-org pattern of the other Parse*WithDefaultOrganization wrappers.
5
Use the Go text template engine from internal/template/template.go with an //go:embed show.tpl file. Mirror internal/cmd/pr/view/view.go and view.tpl structure: bold, hyperlink, s, timeago, timefmt, markdown, pluck, join, truncate, stripprefix, tablerow, tablerender.
The user explicitly requested template rendering like pr/view.
6
Aliases: view, status. Primary name is show. cmd.Use: "show [ORGANIZATION/]POOL/AGENT", cmd.Aliases: []string{"view", "status"}.
Mirrors the pr/view aliasing pattern but with show as the primary.
7
JSON output passes the raw SDK *taskagent.TaskAgent to opts.exporter.Write. No view struct.
Symmetric with the other show siblings.
8
No confirmation prompt. Show is read-only.
Show is non-destructive.
9
--raw flag dumps the full SDK agent with spew.Dump to stderr for debugging.
Mirrors pr/view --raw.
10
New public helper util.ParsePoolAgentTargetWithDefaultOrganization is added to internal/cmd/util/scope.go as part of this story (thin wrapper over the unified util.Parse introduced by #259). New resolver shared.ResolvePoolAgent is added at internal/cmd/pipelines/agent/show/shared/resolve.go. No new SDK client, no new package beyond internal/cmd/pipelines/agent/show and internal/cmd/pipelines/agent/show/shared.
Mirrors the variablegroup/delete + variablegroup/shared/resolver.go split. Keeps #259 a pure refactor (no new public API).
11
Mocks for GetAgent, GetAgentPools, and GetAgents are already generated at internal/mocks/taskagent_client_mock.go:336-345, :411-426, and :530-542 respectively. Do not regenerate.
Verified.
12
Optional flag --include-capabilities (bool, default false) maps to GetAgentArgs.IncludeCapabilities. When true, the template renders system capabilities and user capabilities blocks.
Mirrors Azure CLI flag.
13
Strict predecessor: #259 must be merged before this story can be implemented.#259 introduces the unified util.Parse(ctx, raw, opts) (*Path, error) infrastructure that this story's new util.ParsePoolAgentTargetWithDefaultOrganization wrapper delegates to. The wrapper itself is added as part of this story's implementation, not by #259.
Keeps #259 a pure refactor (no new public API) while still gating #247 on the unified parser landing first.
Command Signature
azdo pipelines agent show [ORGANIZATION/]POOL/AGENT
[--include-capabilities] (bool)
[--raw] [--json ...]
Aliases: view, status
Args: util.ExactArgs(1, "agent target is required") — args[0] is the full target string, parsed by util.ParsePoolAgentTargetWithDefaultOrganization.
The target string is split on / into 2 or 3 segments: 2 segments → pool + agent with implicit default organization; 3 segments → organization + pool + agent.
Flags
Flag
Maps to
Notes
--include-capabilities (bool)
GetAgentArgs.IncludeCapabilities
When true, render system/user capabilities in template
Pass the raw *taskagent.TaskAgent returned by the SDK (Decision 7).
Template Output Contract (show.tpl)
url: (if present)
id:
name:
pool:
status: (online | offline)
enabled: (true | false)
version:
os description:
access point:
max parallelism:
created on:
created by: (if present)
last status change:
system capabilities: (when --include-capabilities)
... list of key/value pairs
user capabilities: (when --include-capabilities)
... list of key/value pairs
Command Wiring
Package path: internal/cmd/pipelines/agent/show (command) and internal/cmd/pipelines/agent/show/shared (resolver).
show_test.go — table-driven gomock tests for runShow
shared/resolve.go — ResolvePoolAgent(cmdCtx util.CmdContext, client taskagent.Client, org, poolTarget, agentTarget string) (*taskagent.TaskAgent, error). Two-stage resolution: (1) resolve pool — if poolTarget parses as a positive integer, use it directly; else call GetAgentPools(args.PoolName=poolTarget) and pick the first case-insensitive Name match; (2) resolve agent — if agentTarget parses as a positive integer, use it directly; else call GetAgents(args.PoolId=resolved_pool_id, args.AgentName=agentTarget) and pick the first case-insensitive Name match; (3) call GetAgent(args.PoolId=resolved_pool_id, args.AgentId=resolved_agent_id). Errors on zero or ambiguous matches for either segment.
shared/resolve_test.go — table-driven tests for ResolvePoolAgent covering: numeric pool + numeric agent; name pool + name agent; mixed numeric/name; missing pool; ambiguous pool; missing agent; ambiguous agent.
Update internal/cmd/pipelines/agent/agent.go to add showcmd "github.com/tmeckel/azdo-cli/internal/cmd/pipelines/agent/show" and cmd.AddCommand(showcmd.NewCmd(ctx)). Update the Example block.
Mocks for GetAgent, GetAgentPools, and GetAgents are all already generated. No mock regeneration needed.
Reference Existing Patterns
internal/cmd/pipelines/variablegroup/delete/delete.go:34,58,85 — primary target-resolution precedent. The Use: "delete [ORGANIZATION/]PROJECT/GROUP", Args: util.ExactArgs(1, "..."), util.ParseProjectTargetWithDefaultOrganization, shared.ResolveVariableGroup pattern translates directly: replace the parser with util.ParsePoolAgentTargetWithDefaultOrganization (this story's new helper) and the resolver with shared.ResolvePoolAgent; remove the project from the segment count; add a second-stage resolution for the agent segment that depends on the resolved pool ID.
internal/cmd/pipelines/variablegroup/shared/resolver.go:14-20 — ResolveVariableGroup signature template: (ctx CmdContext, client taskagent.Client, project, target string) (*VariableGroup, error). The analogous signature for ResolvePoolAgent is (ctx CmdContext, client taskagent.Client, org, poolTarget, agentTarget string) (*TaskAgent, error).
internal/cmd/pr/view/view.go — primary template-engine reference.
internal/cmd/pr/view/view.tpl — primary template-file reference.
internal/mocks/taskagent_client_mock.go:336-345, :411-426, :530-542 — mocks for GetAgent, GetAgentPools, GetAgents (already generated, do not regenerate).
Sub-issue of #246. Hardened spec — do not re-derive decisions. Mirrors
internal/cmd/pr/view/view.goand uses Go text templates viainternal/template.Template(the same engine used byazdo pr view).Command Description
Display the details of a single Azure DevOps agent. The target is given as a single positional argument of the form
[ORGANIZATION/]POOL/AGENTwhere each of the POOL and AGENT segments is either a positive integer (used as the ID directly) or a name resolved against the Agents REST 7.1 endpoint. The matchingTaskAgentis fetched and rendered as a Go text template showingStatus,Version, and capabilities (when--include-capabilitiesis set) with their respective child values.Locked Decisions (do not re-derive)
taskagent.Client.GetAgentfor the final fetch, plustaskagent.Client.GetAgentPoolsandtaskagent.Client.GetAgentsfor the two-stage name resolution. All three mocks are already generated.taskagentclient.Use: "show [ORGANIZATION/]POOL/AGENT". The pool and agent are positional segments. Each segment is either a positive integer (used as the ID directly) or a name resolved by calling the appropriate list API and picking the first case-insensitive match. Errors on zero matches ("not found") or more than one match ("multiplenamedyfound; specify the numeric ID").variablegroup/deletetarget-resolution pattern (#243, #245) and the Pythonget_definition_id_from_namehelper. Agents are org-scoped, not project-scoped.Args: util.ExactArgs(1, "agent target is required"). The single positional argument is the full[ORG/]POOL/AGENTstring, parsed byutil.ParsePoolAgentTargetWithDefaultOrganizationintroduced by this story. Empty or whitespace-only input, missing pool, or missing agent all error from the parser.[ORG/]POOL/AGENT) falls back to the configured default organization when omitted. The helper's 2-segment form (POOL/AGENT) requires a default organization; the 3-segment form (ORG/POOL/AGENT) does not.Parse*WithDefaultOrganizationwrappers.internal/template/template.gowith an//go:embed show.tplfile. Mirrorinternal/cmd/pr/view/view.goandview.tplstructure:bold,hyperlink,s,timeago,timefmt,markdown,pluck,join,truncate,stripprefix,tablerow,tablerender.pr/view.view,status. Primary name isshow.cmd.Use: "show [ORGANIZATION/]POOL/AGENT",cmd.Aliases: []string{"view", "status"}.pr/viewaliasing pattern but withshowas the primary.*taskagent.TaskAgenttoopts.exporter.Write. No view struct.--rawflag dumps the full SDK agent withspew.Dumpto stderr for debugging.pr/view --raw.util.ParsePoolAgentTargetWithDefaultOrganizationis added tointernal/cmd/util/scope.goas part of this story (thin wrapper over the unifiedutil.Parseintroduced by #259). New resolvershared.ResolvePoolAgentis added atinternal/cmd/pipelines/agent/show/shared/resolve.go. No new SDK client, no new package beyondinternal/cmd/pipelines/agent/showandinternal/cmd/pipelines/agent/show/shared.variablegroup/delete+variablegroup/shared/resolver.gosplit. Keeps #259 a pure refactor (no new public API).GetAgent,GetAgentPools, andGetAgentsare already generated atinternal/mocks/taskagent_client_mock.go:336-345,:411-426, and:530-542respectively. Do not regenerate.--include-capabilities(bool, default false) maps toGetAgentArgs.IncludeCapabilities. When true, the template renderssystem capabilitiesanduser capabilitiesblocks.util.Parse(ctx, raw, opts) (*Path, error)infrastructure that this story's newutil.ParsePoolAgentTargetWithDefaultOrganizationwrapper delegates to. The wrapper itself is added as part of this story's implementation, not by #259.Command Signature
view,statusArgs: util.ExactArgs(1, "agent target is required")—args[0]is the full target string, parsed byutil.ParsePoolAgentTargetWithDefaultOrganization./into 2 or 3 segments: 2 segments → pool + agent with implicit default organization; 3 segments → organization + pool + agent.Flags
--include-capabilities(bool)GetAgentArgs.IncludeCapabilities--raw(bool)spew.Dumpof full SDK agent to stderr--json/--jq/--templateutil.AddJSONFlagsJSON Output Contract
Pass the raw
*taskagent.TaskAgentreturned by the SDK (Decision 7).Template Output Contract (
show.tpl)Command Wiring
internal/cmd/pipelines/agent/show(command) andinternal/cmd/pipelines/agent/show/shared(resolver).show.go—NewCmd(ctx util.CmdContext) *cobra.Command+showOptions+runShowshow.tpl— Go text templateshow_test.go— table-driven gomock tests forrunShowshared/resolve.go—ResolvePoolAgent(cmdCtx util.CmdContext, client taskagent.Client, org, poolTarget, agentTarget string) (*taskagent.TaskAgent, error). Two-stage resolution: (1) resolve pool — ifpoolTargetparses as a positive integer, use it directly; else callGetAgentPools(args.PoolName=poolTarget)and pick the first case-insensitiveNamematch; (2) resolve agent — ifagentTargetparses as a positive integer, use it directly; else callGetAgents(args.PoolId=resolved_pool_id, args.AgentName=agentTarget)and pick the first case-insensitiveNamematch; (3) callGetAgent(args.PoolId=resolved_pool_id, args.AgentId=resolved_agent_id). Errors on zero or ambiguous matches for either segment.shared/resolve_test.go— table-driven tests forResolvePoolAgentcovering: numeric pool + numeric agent; name pool + name agent; mixed numeric/name; missing pool; ambiguous pool; missing agent; ambiguous agent.internal/cmd/pipelines/agent/agent.goto addshowcmd "github.com/tmeckel/azdo-cli/internal/cmd/pipelines/agent/show"andcmd.AddCommand(showcmd.NewCmd(ctx)). Update theExampleblock.pipelines→agent→show.internal/cmd/util/scope.goto add the new public wrapperParsePoolAgentTargetWithDefaultOrganization(ctx, raw) (*Path, error). It is a 5-line thin wrapper over the unifiedParseintroduced by refactor: Unifyinternal/cmd/util/scope.goparsing into a singlePathtype with options-drivenParse#259, configured asParseOptions{AllowImplicitOrg: true, RequireProject: false, MinTargets: 2, MaxTargets: 2}.API Surface
Reuse the already-vendored client. No new SDK clients required.
taskagent.Client.GetAgent→ Agents - Get (REST 7.1)taskagent.Client.GetAgentPools→ AgentPools - Get Agent Pools (REST 7.1) —GetAgentPoolsArgs.PoolNamefilter for name resolution.taskagent.Client.GetAgents→ Agents - List (REST 7.1) —GetAgentsArgs.PoolId(required) +GetAgentsArgs.AgentNamefilter for name resolution.taskagent.GetAgentArgsstruct:{PoolId *int, AgentId *int, IncludeCapabilities *bool, IncludeAssignedRequest *bool, IncludeLastCompletedRequest *bool, PropertyFilters *[]string}.util.ParsePoolAgentTargetWithDefaultOrganization(ctx, raw) (*util.Path, error). Splits the input on/into 2 or 3 segments; returns a*PathwithTargets = [pool, agent]. Added tointernal/cmd/util/scope.goas part of this story (gated on refactor: Unifyinternal/cmd/util/scope.goparsing into a singlePathtype with options-drivenParse#259).internal/template.Templatewithbold,hyperlink,s,timeago,timefmt,markdown,pluck,join,truncate,stripprefix,tablerow,tablerender.Mocks for
GetAgent,GetAgentPools, andGetAgentsare all already generated. No mock regeneration needed.Reference Existing Patterns
internal/cmd/pipelines/variablegroup/delete/delete.go:34,58,85— primary target-resolution precedent. TheUse: "delete [ORGANIZATION/]PROJECT/GROUP",Args: util.ExactArgs(1, "..."),util.ParseProjectTargetWithDefaultOrganization,shared.ResolveVariableGrouppattern translates directly: replace the parser withutil.ParsePoolAgentTargetWithDefaultOrganization(this story's new helper) and the resolver withshared.ResolvePoolAgent; remove the project from the segment count; add a second-stage resolution for the agent segment that depends on the resolved pool ID.internal/cmd/pipelines/variablegroup/shared/resolver.go:14-20—ResolveVariableGroupsignature template:(ctx CmdContext, client taskagent.Client, project, target string) (*VariableGroup, error). The analogous signature forResolvePoolAgentis(ctx CmdContext, client taskagent.Client, org, poolTarget, agentTarget string) (*TaskAgent, error).internal/cmd/pr/view/view.go— primary template-engine reference.internal/cmd/pr/view/view.tpl— primary template-file reference.internal/cmd/boards/workitem/list/list_test.go:765-844—setupFakeDeps/stub*fixture.internal/mocks/taskagent_client_mock.go:336-345, :411-426, :530-542— mocks forGetAgent,GetAgentPools,GetAgents(already generated, do not regenerate).internal/azdo/factory.go:133-139—ClientFactory().TaskAgent(...)accessor (reuse).internal/template/template.go— template engine + funcs (reuse, do not reimplement).References
Blocks
refactor: Unify internal/cmd/util/scope.go parsing into a single Path type with options-driven Parse). This story's newutil.ParsePoolAgentTargetWithDefaultOrganizationwrapper is added as part of feat: Implementazdo pipelines agent showcommand #247's implementation, but it delegates to the unifiedutil.Parse(ctx, raw, opts) (*Path, error)introduced by refactor: Unifyinternal/cmd/util/scope.goparsing into a singlePathtype with options-drivenParse#259. This story cannot be implemented until refactor: Unifyinternal/cmd/util/scope.goparsing into a singlePathtype with options-drivenParse#259 is merged.