Add --json support to gh agent-task list

Add --json, --jq, and --template flags to `gh agent-task list`,
consistent with the pattern used by `gh pr list --json`,
`gh issue list --json`, etc.

This implements the ExportData interface on capi.Session and defines
SessionFields for the available JSON fields:
id, name, status, repository, createdAt, updatedAt,
pullRequestNumber, pullRequestUrl.

Closes https://github.com/cli/cli/issues/12805 (partial)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Max Beizer 2026-02-28 09:20:13 -06:00
parent cf862d65df
commit 0d9e6af3da
3 changed files with 93 additions and 0 deletions

View file

@ -102,6 +102,58 @@ type SessionError struct {
Message string
}
// SessionFields defines the available fields for JSON export of a Session.
var SessionFields = []string{
"id",
"name",
"status",
"repository",
"createdAt",
"updatedAt",
"pullRequestNumber",
"pullRequestUrl",
}
// ExportData implements the exportable interface for JSON output.
func (s *Session) ExportData(fields []string) map[string]interface{} {
data := make(map[string]interface{}, len(fields))
for _, f := range fields {
switch f {
case "id":
data[f] = s.ID
case "name":
data[f] = s.Name
case "status":
data[f] = s.State
case "repository":
if s.PullRequest != nil && s.PullRequest.Repository != nil {
data[f] = s.PullRequest.Repository.NameWithOwner
} else {
data[f] = nil
}
case "createdAt":
data[f] = s.CreatedAt
case "updatedAt":
data[f] = s.LastUpdatedAt
case "pullRequestNumber":
if s.PullRequest != nil {
data[f] = s.PullRequest.Number
} else {
data[f] = nil
}
case "pullRequestUrl":
if s.PullRequest != nil {
data[f] = s.PullRequest.URL
} else {
data[f] = nil
}
default:
data[f] = nil
}
}
return data
}
type resource struct {
ID string `json:"id"`
UserID uint64 `json:"user_id"`

View file

@ -25,6 +25,7 @@ type ListOptions struct {
CapiClient func() (capi.CapiClient, error)
Web bool
Browser browser.Browser
Exporter cmdutil.Exporter
}
// NewCmdList creates the list command
@ -54,6 +55,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman
cmd.Flags().IntVarP(&opts.Limit, "limit", "L", defaultLimit, "Maximum number of agent tasks to fetch")
cmd.Flags().BoolVarP(&opts.Web, "web", "w", false, "Open agent tasks in the browser")
cmdutil.AddJSONFlags(cmd, &opts.Exporter, capi.SessionFields)
return cmd
}
@ -91,6 +94,10 @@ func listRun(opts *ListOptions) error {
return cmdutil.NewNoResultsError("no agent tasks found")
}
if opts.Exporter != nil {
return opts.Exporter.Write(opts.IO, sessions)
}
if err := opts.IO.StartPager(); err == nil {
defer opts.IO.StopPager()
} else {

View file

@ -99,6 +99,7 @@ func Test_listRun(t *testing.T) {
capiStubs func(*testing.T, *capi.CapiClientMock)
limit int
web bool
jsonFields []string
wantOut string
wantErr error
wantStderr string
@ -286,6 +287,33 @@ func Test_listRun(t *testing.T) {
wantStderr: "Opening https://github.com/copilot/agents in your browser.\n",
wantBrowserURL: "https://github.com/copilot/agents",
},
{
name: "json output",
tty: false,
capiStubs: func(t *testing.T, m *capi.CapiClientMock) {
m.ListLatestSessionsForViewerFunc = func(ctx context.Context, limit int) ([]*capi.Session, error) {
return []*capi.Session{
{
ID: "abc-123",
Name: "s1",
State: "completed",
CreatedAt: sampleDate,
LastUpdatedAt: sampleDate,
ResourceType: "pull",
PullRequest: &api.PullRequest{
Number: 101,
URL: "https://github.com/OWNER/REPO/pull/101",
Repository: &api.PRRepository{
NameWithOwner: "OWNER/REPO",
},
},
},
}, nil
}
},
jsonFields: []string{"id", "name", "status", "repository", "pullRequestNumber", "pullRequestUrl"},
wantOut: "[{\"id\":\"abc-123\",\"name\":\"s1\",\"pullRequestNumber\":101,\"pullRequestUrl\":\"https://github.com/OWNER/REPO/pull/101\",\"repository\":\"OWNER/REPO\",\"status\":\"completed\"}]\n",
},
}
for _, tt := range tests {
@ -316,6 +344,12 @@ func Test_listRun(t *testing.T) {
},
}
if tt.jsonFields != nil {
exporter := cmdutil.NewJSONExporter()
exporter.SetFields(tt.jsonFields)
opts.Exporter = exporter
}
err := listRun(opts)
if tt.wantErr != nil {
assert.Error(t, err)