Skip to content

Commit e32f72c

Browse files
committed
migrate abc command to use urfave/cli
1 parent 68819d9 commit e32f72c

5 files changed

Lines changed: 301 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Command Guidelines
2+
3+
- When adding new commands, use `urfave/cli` instead of the legacy `commander` pattern.
4+
- Register new `urfave/cli` commands in the `migratedCommands` map in `cmd/src/run_migration_compat.go`.

cmd/src/abc.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package main
2+
3+
import (
4+
"context"
5+
6+
"github.com/sourcegraph/src-cli/internal/clicompat"
7+
"github.com/sourcegraph/src-cli/internal/cmderrors"
8+
"github.com/urfave/cli/v3"
9+
)
10+
11+
var abcCommand = clicompat.Wrap(&cli.Command{
12+
Name: "abc",
13+
Usage: "manages agentic batch changes",
14+
Commands: []*cli.Command{
15+
clicompat.Wrap(&cli.Command{
16+
Name: "variables",
17+
Usage: "manage workflow instance variables",
18+
Commands: []*cli.Command{
19+
abcVariablesSetCommand,
20+
abcVariablesDeleteCommand,
21+
},
22+
Action: func(ctx context.Context, cmd *cli.Command) error {
23+
return cli.ShowSubcommandHelp(cmd)
24+
},
25+
}),
26+
},
27+
Action: func(ctx context.Context, cmd *cli.Command) error {
28+
return cli.ShowSubcommandHelp(cmd)
29+
},
30+
})
31+
32+
var abcVariablesSetCommand = clicompat.Wrap(&cli.Command{
33+
Name: "set",
34+
UsageText: "src abc variables set [options] <workflow-instance-id> [<name>=<value> ...]",
35+
Usage: "Set variables on a workflow instance",
36+
Description: `
37+
Set workflow instance variables
38+
39+
Examples:
40+
41+
Set a string variable on a workflow instance:
42+
43+
$ src abc variables set QWdlbnRpY1dvcmtmbG93SW5zdGFuY2U6MQ== prompt="tighten the review criteria"
44+
45+
Set multiple variables in one request:
46+
47+
$ src abc variables set QWdlbnRpY1dvcmtmbG93SW5zdGFuY2U6MQ== --var prompt="tighten the review criteria" --var checkpoints='[1,2,3]'
48+
49+
Set a structured JSON value:
50+
51+
$ src abc variables set QWdlbnRpY1dvcmtmbG93SW5zdGFuY2U6MQ== checkpoints='[1,2,3]'
52+
53+
NOTE: Values are interpreted as JSON literals when valid. Otherwise they are sent as plain strings.
54+
`,
55+
Flags: clicompat.WithAPIFlags(
56+
&cli.StringSliceFlag{
57+
Name: "var",
58+
Usage: "Variable assignment in <name>=<value> form. Repeat to set multiple variables.",
59+
},
60+
),
61+
Action: func(ctx context.Context, cmd *cli.Command) error {
62+
if !cmd.Args().Present() {
63+
return cmderrors.Usage("must provide a workflow instance ID")
64+
}
65+
66+
instanceID := cmd.Args().First()
67+
client := cfg.apiClient(clicompat.APIFlagsFromCmd(cmd), cmd.Writer)
68+
return runABCVariablesSet(ctx, client, instanceID, cmd.Args().Tail(), abcVariableArgs(cmd.StringSlice("var")), cmd.Writer, cmd.Bool("get-curl"))
69+
},
70+
})
71+
72+
var abcVariablesDeleteCommand = clicompat.Wrap(&cli.Command{
73+
Name: "delete",
74+
Usage: "Delete variables on a workflow instance",
75+
UsageText: "src abc variables delete [options] <workflow-instance-id> [<name> ...]",
76+
Description: `
77+
Delete workflow instance variables
78+
79+
Examples:
80+
81+
Delete a variable from a workflow instance:
82+
83+
$ src abc variables delete QWdlbnRpY1dvcmtmbG93SW5zdGFuY2U6MQ== approval
84+
85+
Delete multiple variables in one request:
86+
87+
$ src abc variables delete QWdlbnRpY1dvcmtmbG93SW5zdGFuY2U6MQ== --var approval --var checkpoints
88+
`,
89+
Flags: clicompat.WithAPIFlags(
90+
&cli.StringSliceFlag{
91+
Name: "var",
92+
Usage: "Variable name to delete. Repeat for multiple names.",
93+
},
94+
),
95+
Action: func(ctx context.Context, cmd *cli.Command) error {
96+
if !cmd.Args().Present() {
97+
return cmderrors.Usage("must provide a workflow instance ID")
98+
}
99+
100+
instanceID := cmd.Args().First()
101+
client := cfg.apiClient(clicompat.APIFlagsFromCmd(cmd), cmd.Writer)
102+
varArgs := abcVariableArgs(cmd.StringSlice("var"))
103+
104+
if len(varArgs) == 0 {
105+
return cmderrors.Usage("must provide at least one variable name")
106+
}
107+
return runABCVariablesDelete(ctx, client, instanceID, cmd.Args().Tail(), varArgs, cmd.Writer, cmd.Bool("get-curl"))
108+
},
109+
})

cmd/src/abc_variables_delete.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"slices"
8+
9+
"github.com/sourcegraph/src-cli/internal/api"
10+
"github.com/sourcegraph/src-cli/internal/cmderrors"
11+
)
12+
13+
func parseABCVariableNames(positional []string, flagged abcVariableArgs) ([]string, error) {
14+
variableNames := append([]string{}, positional...)
15+
variableNames = append(variableNames, flagged...)
16+
17+
if slices.Contains(variableNames, "") {
18+
return nil, cmderrors.Usage("variable names must not be empty")
19+
}
20+
21+
return variableNames, nil
22+
}
23+
24+
func runABCVariablesDelete(ctx context.Context, client api.Client, instanceID string, positional []string, flagged abcVariableArgs, output io.Writer, getCurl bool) error {
25+
variableNames, err := parseABCVariableNames(positional, flagged)
26+
if err != nil {
27+
return err
28+
}
29+
30+
variables := make([]map[string]string, 0, len(variableNames))
31+
for _, key := range variableNames {
32+
variables = append(variables, map[string]string{
33+
"key": key,
34+
"value": "null",
35+
})
36+
}
37+
38+
if err := updateABCWorkflowInstanceVariables(ctx, client, instanceID, variables); err != nil {
39+
return err
40+
}
41+
42+
if getCurl {
43+
return nil
44+
}
45+
46+
_, err = fmt.Fprintf(output, "Removed variables %q from workflow instance %q.\n", variableNames, instanceID)
47+
return err
48+
}

cmd/src/abc_variables_set.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"io"
9+
"strings"
10+
11+
"github.com/sourcegraph/src-cli/internal/api"
12+
"github.com/sourcegraph/src-cli/internal/cmderrors"
13+
)
14+
15+
const updateABCWorkflowInstanceVariablesMutation = `mutation UpdateAgenticWorkflowInstanceVariables(
16+
$instanceID: ID!,
17+
$variables: [AgenticWorkflowInstanceVariableInput!]!,
18+
) {
19+
updateAgenticWorkflowInstanceVariables(instanceID: $instanceID, variables: $variables) {
20+
id
21+
}
22+
}`
23+
24+
type abcVariableArgs []string
25+
26+
func (a *abcVariableArgs) String() string {
27+
return strings.Join(*a, ",")
28+
}
29+
30+
func (a *abcVariableArgs) Set(value string) error {
31+
*a = append(*a, value)
32+
return nil
33+
}
34+
35+
type abcVariable struct {
36+
Key string
37+
Value string
38+
}
39+
40+
func parseABCVariables(positional []string, flagged abcVariableArgs) ([]abcVariable, error) {
41+
rawVariables := append([]string{}, positional...)
42+
rawVariables = append(rawVariables, flagged...)
43+
if len(rawVariables) == 0 {
44+
return nil, cmderrors.Usage("must provide at least one variable assignment")
45+
}
46+
47+
variables := make([]abcVariable, 0, len(rawVariables))
48+
for _, rawVariable := range rawVariables {
49+
variable, err := parseABCVariable(rawVariable)
50+
if err != nil {
51+
return nil, err
52+
}
53+
variables = append(variables, variable)
54+
}
55+
56+
return variables, nil
57+
}
58+
59+
func parseABCVariable(raw string) (abcVariable, error) {
60+
name, rawValue, ok := strings.Cut(raw, "=")
61+
if !ok || name == "" {
62+
return abcVariable{}, cmderrors.Usagef("invalid variable assignment %q: must be in <name>=<value> form", raw)
63+
}
64+
65+
value, remove, err := marshalABCVariableValue(rawValue)
66+
if err != nil {
67+
return abcVariable{}, err
68+
}
69+
if remove {
70+
return abcVariable{}, cmderrors.Usagef("invalid variable assignment %q: use 'src abc variables delete <workflow-instance-id> %s' to remove a variable", raw, name)
71+
}
72+
73+
return abcVariable{Key: name, Value: value}, nil
74+
}
75+
76+
func runABCVariablesSet(ctx context.Context, client api.Client, instanceID string, positional []string, flagged abcVariableArgs, output io.Writer, getCurl bool) error {
77+
variables, err := parseABCVariables(positional, flagged)
78+
if err != nil {
79+
return err
80+
}
81+
82+
graphqlVariables := make([]map[string]string, 0, len(variables))
83+
for _, variable := range variables {
84+
graphqlVariables = append(graphqlVariables, map[string]string{
85+
"key": variable.Key,
86+
"value": variable.Value,
87+
})
88+
}
89+
90+
if err := updateABCWorkflowInstanceVariables(ctx, client, instanceID, graphqlVariables); err != nil {
91+
return err
92+
}
93+
94+
if getCurl {
95+
return nil
96+
}
97+
98+
if len(variables) == 1 {
99+
_, err = fmt.Fprintf(output, "Set variable %q on workflow instance %q.\n", variables[0].Key, instanceID)
100+
return err
101+
}
102+
103+
_, err = fmt.Fprintf(output, "Updated %d variables on workflow instance %q.\n", len(variables), instanceID)
104+
return err
105+
}
106+
107+
func updateABCWorkflowInstanceVariables(ctx context.Context, client api.Client, instanceID string, variables []map[string]string) error {
108+
var result struct {
109+
UpdateAgenticWorkflowInstanceVariables struct {
110+
ID string `json:"id"`
111+
} `json:"updateAgenticWorkflowInstanceVariables"`
112+
}
113+
if ok, err := client.NewRequest(updateABCWorkflowInstanceVariablesMutation, map[string]any{
114+
"instanceID": instanceID,
115+
"variables": variables,
116+
}).Do(ctx, &result); err != nil || !ok {
117+
return err
118+
}
119+
120+
return nil
121+
}
122+
123+
func marshalABCVariableValue(raw string) (value string, remove bool, err error) {
124+
// Try to compact valid JSON literals first so numbers, arrays, and objects are sent unchanged.
125+
// A bare null is detected separately so the CLI can require the explicit delete command.
126+
// If compacting doesn't work for the given value, fall back to string encoding.
127+
var compact bytes.Buffer
128+
if err := json.Compact(&compact, []byte(raw)); err == nil {
129+
value := compact.String()
130+
return value, value == "null", nil
131+
}
132+
133+
encoded, err := json.Marshal(raw)
134+
if err != nil {
135+
return "", false, err
136+
}
137+
138+
return string(encoded), false, nil
139+
}

cmd/src/run_migration_compat.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
)
1717

1818
var migratedCommands = map[string]*cli.Command{
19+
"abc": abcCommand,
1920
"version": versionCommand,
2021
}
2122

0 commit comments

Comments
 (0)