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
3 changes: 2 additions & 1 deletion assets/plugin/skills/deploy/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ sem-ai promote-and-wait <id> --target "Staging Deploy" --confirm
# Override conditions (deploy despite failures)
sem-ai pipeline promote <id> --target "Staging" --confirm --override

# With parameters
# With parameters (each --param becomes a promotion env var)
sem-ai pipeline promote <id> --target "Production" --confirm --param version=1.2.3
sem-ai pipeline promote <id> --target "Production Deploy" --confirm --param SERVICE=web
```

## Deployment targets — read
Expand Down
6 changes: 6 additions & 0 deletions assets/plugin/skills/manage-infra/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ sem-ai task list --project <p>
sem-ai task show <id>
sem-ai task run <id> # trigger now
sem-ai task delete <id>

# Parameterized tasks: pass parameters, branch, and pipeline file.
# Parameters go to the task's run_now as a map; branch/pipeline-file pick
# the ref + YAML the task pipeline runs.
sem-ai task run <id> --param KEY=VALUE [--param ...] \
[--branch <ref>] [--pipeline-file <path>]
```

## Artifacts
Expand Down
25 changes: 9 additions & 16 deletions cmd/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/url"
"strings"

"github.com/semaphoreio/sem-ai/pkg/client"
"github.com/semaphoreio/sem-ai/pkg/config"
Expand Down Expand Up @@ -320,22 +321,14 @@ Safety:
"override": promoteOverrideFlag,
}

// Parse --param key=val pairs
if len(promoteParamsFlag) > 0 {
envVars := make([]map[string]string, 0, len(promoteParamsFlag))
for _, p := range promoteParamsFlag {
for i := range p {
if p[i] == '=' {
envVars = append(envVars, map[string]string{
"name": p[:i],
"value": p[i+1:],
})
break
}
}
}
if len(envVars) > 0 {
reqBody["parameters"] = envVars
// Promotion parameters go as top-level body keys: the v1alpha
// /promotions endpoint maps every key except
// pipeline_id/name/override/request_token/switch_id/user_id to a
// promotion env var. A nested "parameters" array makes the server
// encode a list as an env-var string value and 500.
for _, p := range promoteParamsFlag {
if k, v, ok := strings.Cut(p, "="); ok {
reqBody[k] = v
}
}

Expand Down
58 changes: 48 additions & 10 deletions cmd/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ var taskCmd = &cobra.Command{
var taskProjectFlag string

var taskListCmd = &cobra.Command{
Use: "list",
Short: "List scheduled tasks for a project",
Use: "list",
Short: "List scheduled tasks for a project",
Example: ` sem-ai task list --project my-project`,
RunE: func(cmd *cobra.Command, args []string) error {
if !config.IsConfigured() {
Expand Down Expand Up @@ -77,17 +77,51 @@ var taskShowCmd = &cobra.Command{
},
}

var (
taskRunParamsFlag []string
taskRunBranchFlag string
taskRunFileFlag string
)

var taskRunCmd = &cobra.Command{
Use: "run <id>",
Short: "Trigger a scheduled task to run now",
Args: cobra.ExactArgs(1),
Example: ` sem-ai task run <task-id>`,
Use: "run <id>",
Short: "Trigger a scheduled task to run now",
Args: cobra.ExactArgs(1),
Example: ` sem-ai task run <task-id>
sem-ai task run <task-id> --param KEY=VALUE --param KEY2=VALUE2
sem-ai task run <task-id> --branch main --pipeline-file .semaphore/pipeline.yml --param KEY=VALUE`,
RunE: func(cmd *cobra.Command, args []string) error {
if !config.IsConfigured() {
return fmt.Errorf("not configured — run 'sem-ai connect' first")
}

// Build a run_now body only when overrides are supplied; otherwise
// send nil to preserve the parameter-less default behaviour.
var body []byte
if len(taskRunParamsFlag) > 0 || taskRunBranchFlag != "" || taskRunFileFlag != "" {
reqBody := map[string]any{}
if taskRunBranchFlag != "" {
reqBody["branch"] = taskRunBranchFlag
}
if taskRunFileFlag != "" {
reqBody["pipeline_file"] = taskRunFileFlag
}
params := map[string]string{}
for _, p := range taskRunParamsFlag {
i := strings.IndexByte(p, '=')
if i <= 0 {
return fmt.Errorf("invalid --param %q: expected KEY=VALUE", p)
}
params[p[:i]] = p[i+1:]
}
if len(params) > 0 {
reqBody["parameters"] = params
}
body, _ = json.Marshal(reqBody)
}

c := client.New()
resp, err := c.PostAction("tasks", args[0], "run_now", nil)
resp, err := c.PostAction("tasks", args[0], "run_now", body)
if err != nil {
output.Error("api_error", err.Error(), 1)
return err
Expand Down Expand Up @@ -138,9 +172,9 @@ var (
)

var taskCreateCmd = &cobra.Command{
Use: "create <name>",
Short: "Create a scheduled task (periodic job)",
Args: cobra.ExactArgs(1),
Use: "create <name>",
Short: "Create a scheduled task (periodic job)",
Args: cobra.ExactArgs(1),
Example: ` sem-ai task create nightly-tests --project my-app --branch main --file .semaphore/nightly.yml --cron "0 2 * * *"`,
RunE: func(cmd *cobra.Command, args []string) error {
if !config.IsConfigured() {
Expand Down Expand Up @@ -240,6 +274,10 @@ func init() {
taskCreateCmd.Flags().StringVar(&taskCreateFileFlag, "file", ".semaphore/semaphore.yml", "pipeline YAML file")
taskCreateCmd.Flags().StringVar(&taskCreateCronFlag, "cron", "", "cron expression for recurring tasks")

taskRunCmd.Flags().StringArrayVar(&taskRunParamsFlag, "param", nil, "task parameter as KEY=VALUE (repeatable)")
taskRunCmd.Flags().StringVar(&taskRunBranchFlag, "branch", "", "git ref the task pipeline runs on (e.g. master); defaults to the task's configured branch")
taskRunCmd.Flags().StringVar(&taskRunFileFlag, "pipeline-file", "", "pipeline YAML file the task runs; defaults to the task's configured file")

taskCmd.AddCommand(taskListCmd)
taskCmd.AddCommand(taskShowCmd)
taskCmd.AddCommand(taskRunCmd)
Expand Down
Loading