Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions cmd/src/abc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"context"

"github.com/sourcegraph/src-cli/internal/clicompat"
"github.com/urfave/cli/v3"
)

var abcCommand = &cli.Command{
Name: "abc",
Usage: "manages agentic batch changes",
UsageText: `'src abc' is a tool that manages agentic batch changes.

Usage:

src abc command [command options]

The commands are:`,
OnUsageError: clicompat.OnUsageError,
Description: `Use "src abc [command] -h" for more information about a command.`,
HideHelpCommand: true,
HideVersion: true,
Commands: []*cli.Command{
abcVariablesCommand,
},
Action: func(_ context.Context, c *cli.Command) error {
return cli.ShowSubcommandHelp(c)
},
}
31 changes: 31 additions & 0 deletions cmd/src/abc_variables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"context"

"github.com/sourcegraph/src-cli/internal/clicompat"
"github.com/urfave/cli/v3"
)

var abcVariablesCommand = &cli.Command{
Name: "variables",
Usage: "manage workflow instance variables",
UsageText: `'src abc variables' is a tool that manages workflow instance variables on agentic batch changes.

Usage:

src abc variables command [command options]

The commands are:`,
OnUsageError: clicompat.OnUsageError,
Description: `Use "src abc variables [command] -h" for more information about a command.`,
HideHelpCommand: true,
HideVersion: true,
Commands: []*cli.Command{
abcVariablesSetCommand,
abcVariablesDeleteCommand,
},
Action: func(_ context.Context, c *cli.Command) error {
return cli.ShowSubcommandHelp(c)
},
}
83 changes: 83 additions & 0 deletions cmd/src/abc_variables_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package main

import (
"context"
"fmt"

"github.com/sourcegraph/src-cli/internal/clicompat"
"github.com/sourcegraph/src-cli/internal/cmderrors"
"github.com/urfave/cli/v3"
)

var abcVariablesDeleteCommand = clicompat.Wrap(&cli.Command{
Name: "delete",
Usage: "delete workflow instance variables",
Description: `Usage:

src abc variables delete [command options] <workflow-instance-id> [<name> ...]

Examples:

Delete a variable from a workflow instance:

$ src abc variables delete QWdlbnRpY1dvcmtmbG93SW5zdGFuY2U6MQ== approval

Delete multiple variables in one request:

$ src abc variables delete QWdlbnRpY1dvcmtmbG93SW5zdGFuY2U6MQ== --var approval --var checkpoints`,
DisableSliceFlagSeparator: true,
Flags: clicompat.WithAPIFlags(
&cli.StringSliceFlag{
Name: "var",
Usage: "Variable name to delete. Repeat for multiple names.",
},
),
Action: func(ctx context.Context, c *cli.Command) error {
if c.NArg() == 0 {
return cmderrors.Usage("must provide a workflow instance ID")
}

instanceID := c.Args().First()
variableNames, err := parseABCVariableNames(c.Args().Tail(), abcVariableArgs(c.StringSlice("var")))
if err != nil {
return err
}

variables := make([]map[string]string, 0, len(variableNames))
for _, key := range variableNames {
variables = append(variables, map[string]string{
"key": key,
"value": "null",
})
}

apiFlags := clicompat.APIFlagsFromCmd(c)
client := cfg.apiClient(apiFlags, c.Writer)
if err := updateABCWorkflowInstanceVariables(ctx, client, instanceID, variables); err != nil {
return err
}

if apiFlags.GetCurl() {
return nil
}

fmt.Fprintf(c.Writer, "Removed variables %q from workflow instance %q.\n", variableNames, instanceID)
return nil
},
})

func parseABCVariableNames(positional []string, flagged abcVariableArgs) ([]string, error) {
variableNames := append([]string{}, positional...)
variableNames = append(variableNames, flagged...)
if len(variableNames) == 0 {
return nil, cmderrors.Usage("must provide at least one variable name")
}

for _, name := range variableNames {
if name == "" {
return nil, cmderrors.Usage("variable names must not be empty")
}
}

return variableNames, nil
}
27 changes: 27 additions & 0 deletions cmd/src/abc_variables_delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import (
"testing"

"github.com/google/go-cmp/cmp"
)

func TestParseABCVariableNames(t *testing.T) {
t.Parallel()

variableNames, err := parseABCVariableNames(
[]string{"approval"},
abcVariableArgs{"checkpoints", "prompt"},
)
if err != nil {
t.Fatalf("parseABCVariableNames returned error: %v", err)
}

if len(variableNames) != 3 {
t.Fatalf("len(variableNames) = %d, want 3", len(variableNames))
}
want := []string{"approval", "checkpoints", "prompt"}
if diff := cmp.Diff(want, variableNames); diff != "" {
t.Fatalf("variableNames mismatch (-want +got):\n%s", diff)
}
}
177 changes: 177 additions & 0 deletions cmd/src/abc_variables_set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"

"github.com/sourcegraph/src-cli/internal/api"
"github.com/sourcegraph/src-cli/internal/clicompat"
"github.com/sourcegraph/src-cli/internal/cmderrors"
"github.com/urfave/cli/v3"
)

const updateABCWorkflowInstanceVariablesMutation = `mutation UpdateAgenticWorkflowInstanceVariables(
$instanceID: ID!,
$variables: [AgenticWorkflowInstanceVariableInput!]!,
) {
updateAgenticWorkflowInstanceVariables(instanceID: $instanceID, variables: $variables) {
id
}
}`

var abcVariablesSetCommand = clicompat.Wrap(&cli.Command{
Name: "set",
Usage: "set workflow instance variables",
Description: `Usage:

src abc variables set [command options] <workflow-instance-id> [<name>=<value> ...]

Examples:

Set a string variable on a workflow instance:

$ src abc variables set QWdlbnRpY1dvcmtmbG93SW5zdGFuY2U6MQ== prompt="tighten the review criteria"

Set multiple variables in one request:

$ src abc variables set QWdlbnRpY1dvcmtmbG93SW5zdGFuY2U6MQ== --var prompt="tighten the review criteria" --var checkpoints='[1,2,3]'

Set a structured JSON value:

$ src abc variables set QWdlbnRpY1dvcmtmbG93SW5zdGFuY2U6MQ== checkpoints='[1,2,3]'

Values are interpreted as JSON literals when valid. Otherwise they are sent as plain strings.`,
DisableSliceFlagSeparator: true,
Flags: clicompat.WithAPIFlags(
&cli.StringSliceFlag{
Name: "var",
Usage: "Variable assignment in <name>=<value> form. Repeat to set multiple variables.",
},
),
Action: func(ctx context.Context, c *cli.Command) error {
if c.NArg() == 0 {
return cmderrors.Usage("must provide a workflow instance ID")
}

instanceID := c.Args().First()
variables, err := parseABCVariables(c.Args().Tail(), abcVariableArgs(c.StringSlice("var")))
if err != nil {
return err
}

graphqlVariables := make([]map[string]string, 0, len(variables))
for _, variable := range variables {
graphqlVariables = append(graphqlVariables, map[string]string{
"key": variable.Key,
"value": variable.Value,
})
}

apiFlags := clicompat.APIFlagsFromCmd(c)
client := cfg.apiClient(apiFlags, c.Writer)
if err := updateABCWorkflowInstanceVariables(ctx, client, instanceID, graphqlVariables); err != nil {
return err
}

if apiFlags.GetCurl() {
return nil
}

if len(variables) == 1 {
fmt.Fprintf(c.Writer, "Set variable %q on workflow instance %q.\n", variables[0].Key, instanceID)
return nil
}

fmt.Fprintf(c.Writer, "Updated %d variables on workflow instance %q.\n", len(variables), instanceID)
return nil
},
})

type abcVariableArgs []string

func (a *abcVariableArgs) String() string {
return strings.Join(*a, ",")
}

func (a *abcVariableArgs) Set(value string) error {
*a = append(*a, value)
return nil
}

type abcVariable struct {
Key string
Value string
}

func parseABCVariables(positional []string, flagged abcVariableArgs) ([]abcVariable, error) {
rawVariables := append([]string{}, positional...)
rawVariables = append(rawVariables, flagged...)
if len(rawVariables) == 0 {
return nil, cmderrors.Usage("must provide at least one variable assignment")
}

variables := make([]abcVariable, 0, len(rawVariables))
for _, rawVariable := range rawVariables {
variable, err := parseABCVariable(rawVariable)
if err != nil {
return nil, err
}
variables = append(variables, variable)
}

return variables, nil
}

func parseABCVariable(raw string) (abcVariable, error) {
name, rawValue, ok := strings.Cut(raw, "=")
if !ok || name == "" {
return abcVariable{}, cmderrors.Usagef("invalid variable assignment %q: must be in <name>=<value> form", raw)
}

value, remove, err := marshalABCVariableValue(rawValue)
if err != nil {
return abcVariable{}, err
}
if remove {
return abcVariable{}, cmderrors.Usagef("invalid variable assignment %q: use 'src abc variables delete <workflow-instance-id> %s' to remove a variable", raw, name)
}

return abcVariable{Key: name, Value: value}, nil
}

func updateABCWorkflowInstanceVariables(ctx context.Context, client api.Client, instanceID string, variables []map[string]string) error {
var result struct {
UpdateAgenticWorkflowInstanceVariables struct {
ID string `json:"id"`
} `json:"updateAgenticWorkflowInstanceVariables"`
}
if ok, err := client.NewRequest(updateABCWorkflowInstanceVariablesMutation, map[string]any{
"instanceID": instanceID,
"variables": variables,
}).Do(ctx, &result); err != nil || !ok {
return err
}

return nil
}

func marshalABCVariableValue(raw string) (value string, remove bool, err error) {
// Try to compact valid JSON literals first so numbers, arrays, and objects are sent unchanged.
// A bare null is detected separately so the CLI can require the explicit delete command.
// If compacting doesn't work for the given value, fall back to string encoding.
var compact bytes.Buffer
if err := json.Compact(&compact, []byte(raw)); err == nil {
value := compact.String()
return value, value == "null", nil
}

encoded, err := json.Marshal(raw)
if err != nil {
return "", false, err
}

return string(encoded), false, nil
}
Loading
Loading