Add file input support to agent-task create command
The agent-task create command now accepts a task description from a file using the -F/--from-file flag, with mutual exclusivity enforced between inline and file input. Tests were updated to cover new input scenarios and error cases, and usage examples were added to the command help.
This commit is contained in:
parent
6a50ecb880
commit
b94ffe90c4
2 changed files with 122 additions and 13 deletions
|
|
@ -5,10 +5,13 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/gh"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/agent-task/capi"
|
||||
|
|
@ -31,18 +34,35 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
opts := &CreateOptions{
|
||||
IO: f.IOStreams,
|
||||
}
|
||||
|
||||
var fromFileName string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create \"<task description>\"",
|
||||
Use: "create [<task description>] [flags]",
|
||||
Short: "Create an agent task (preview)",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// TODO: We'll support prompting for the problem statement if not provided
|
||||
// and from file flags, later.
|
||||
if len(args) == 0 {
|
||||
return cmdutil.FlagErrorf("a task description is required")
|
||||
if err := cmdutil.MutuallyExclusive("only one of -F or arg can be provided", len(args) > 0, fromFileName != ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts.ProblemStatement = args[0]
|
||||
// Populate ProblemStatement from either arg or file
|
||||
if len(args) > 0 {
|
||||
opts.ProblemStatement = args[0]
|
||||
} else if fromFileName != "" {
|
||||
fileContent, err := os.ReadFile(fromFileName)
|
||||
if err != nil {
|
||||
return cmdutil.FlagErrorf("could not read task description file: %v", err)
|
||||
}
|
||||
trimmed := strings.TrimSpace(string(fileContent))
|
||||
if trimmed == "" {
|
||||
return cmdutil.FlagErrorf("task description file is empty")
|
||||
}
|
||||
opts.ProblemStatement = trimmed
|
||||
}
|
||||
if opts.ProblemStatement == "" {
|
||||
return cmdutil.FlagErrorf("a task description is required")
|
||||
}
|
||||
// Support -R/--repo override
|
||||
if f != nil {
|
||||
opts.BaseRepo = f.BaseRepo
|
||||
|
|
@ -52,11 +72,20 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
}
|
||||
return createRun(opts)
|
||||
},
|
||||
Example: heredoc.Doc(`
|
||||
# Create a task from an inline description
|
||||
$ gh agent-task create "build me a new app"
|
||||
|
||||
# Create a task from a file
|
||||
$ gh agent-task create -F task-desc.md
|
||||
`),
|
||||
}
|
||||
if f != nil {
|
||||
cmdutil.EnableRepoOverride(cmd, f)
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&fromFileName, "from-file", "F", "", "Read task description from file")
|
||||
|
||||
opts.CapiClient = func() (capi.CapiClient, error) {
|
||||
cfg, err := f.Config()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package create
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
|
|
@ -17,13 +18,92 @@ import (
|
|||
|
||||
// Test basic option parsing & repository requirement
|
||||
func TestNewCmdCreate_Args(t *testing.T) {
|
||||
f := &cmdutil.Factory{}
|
||||
cmd := NewCmdCreate(f, func(o *CreateOptions) error { return nil })
|
||||
// no args should error via cobra MinimumNArgs before our runF
|
||||
// TODO once we support more sources of problem statement input,
|
||||
// this will change.
|
||||
_, err := cmd.ExecuteC()
|
||||
require.Error(t, err)
|
||||
type tc struct {
|
||||
name string
|
||||
args []string
|
||||
fileContent string // if non-empty, create temp file and substitute {{FILE}} token in args
|
||||
wantOpts *CreateOptions // nil when expecting error
|
||||
expectedErr string
|
||||
}
|
||||
|
||||
tests := []tc{
|
||||
{
|
||||
name: "no args nor file",
|
||||
args: []string{},
|
||||
expectedErr: "a task description is required",
|
||||
},
|
||||
{
|
||||
name: "arg only success",
|
||||
args: []string{"task description from args"},
|
||||
wantOpts: &CreateOptions{
|
||||
ProblemStatement: "task description from args",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "from-file success",
|
||||
args: []string{"-F", "{{FILE}}"},
|
||||
fileContent: "task description from file",
|
||||
wantOpts: &CreateOptions{
|
||||
ProblemStatement: "task description from file",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mutually exclusive arg and file",
|
||||
args: []string{"Some task inline", "-F", "{{FILE}}"},
|
||||
fileContent: "Some task",
|
||||
expectedErr: "only one of -F or arg can be provided",
|
||||
},
|
||||
{
|
||||
name: "missing file path",
|
||||
args: []string{"-F", "does-not-exist.md"},
|
||||
expectedErr: "could not read task description file: open does-not-exist.md: no such file or directory",
|
||||
},
|
||||
{
|
||||
name: "empty file",
|
||||
args: []string{"-F", "{{FILE}}"},
|
||||
fileContent: " \n\n",
|
||||
expectedErr: "task description file is empty",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Test file creation
|
||||
var filePath string
|
||||
if tt.fileContent != "" {
|
||||
dir := t.TempDir()
|
||||
filePath = dir + "/task.md"
|
||||
if err := os.WriteFile(filePath, []byte(tt.fileContent), 0o600); err != nil {
|
||||
t.Fatalf("failed to write temp file: %v", err)
|
||||
}
|
||||
// substitute placeholder
|
||||
for i, a := range tt.args {
|
||||
if a == "{{FILE}}" {
|
||||
tt.args[i] = filePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f := &cmdutil.Factory{}
|
||||
var gotOpts *CreateOptions
|
||||
cmd := NewCmdCreate(f, func(o *CreateOptions) error {
|
||||
gotOpts = o
|
||||
return nil
|
||||
})
|
||||
cmd.SetArgs(tt.args)
|
||||
_, err := cmd.ExecuteC()
|
||||
|
||||
if tt.expectedErr != "" {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tt.expectedErr, err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
if tt.wantOpts != nil {
|
||||
require.Equal(t, tt.wantOpts.ProblemStatement, gotOpts.ProblemStatement)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_createRun(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue