Skip to content
Merged
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
36 changes: 22 additions & 14 deletions internal/cli/automation.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
)

const automationHTTPPostOnlyCron = "0 0 * * *"
const automationUTCNote = "Convert local wall-clock requests to UTC before passing --at or --cron-expr."

func newAutomationCmd() *cobra.Command {
cmd := &cobra.Command{
Expand Down Expand Up @@ -63,11 +64,14 @@ By default the rule is enabled. Use --disabled only when the user explicitly
asks to create it disabled. team_id=0 means personal scope; --team-id >0 creates
the rule under that team. The scope is immutable after creation.

Schedule helpers build a 5-field cron expression. Use --cron-expr for exact
minute-level control. For HTTP POST-only rules, pass --http-post-trigger without
a schedule; the CLI sends a valid placeholder cron and disables the schedule trigger.`, "Automations", "RuleWriteCreate"),
Example: ` flashduty automation create --name "Daily SRE brief" --schedule daily --at 09:30 --prompt "Summarize yesterday's incidents"
flashduty automation create --name "Weekly noise review" --team-id 123 --schedule weekly --weekday mon --at 10:00 --prompt-file ./prompt.md
Schedule helpers build a 5-field UTC cron expression. --at and --cron-expr are
interpreted in UTC, not the caller's local timezone. Convert local wall-clock
requests to UTC before passing --at or --cron-expr.

For HTTP POST-only rules, pass --http-post-trigger without a schedule; the CLI
sends a valid placeholder cron and disables the schedule trigger.`, "Automations", "RuleWriteCreate"),
Example: ` flashduty automation create --name "Daily SRE brief" --schedule daily --at 01:30 --prompt "Summarize yesterday's incidents"
flashduty automation create --name "Weekly noise review" --team-id 123 --schedule weekly --weekday mon --at 02:00 --prompt-file ./prompt.md
flashduty automation create --name "Webhook triage" --http-post-trigger --prompt "Handle the posted payload"`,
RunE: func(cmd *cobra.Command, args []string) error {
return runCommand(cmd, args, func(ctx *RunContext) error {
Expand Down Expand Up @@ -111,10 +115,10 @@ a schedule; the CLI sends a valid placeholder cron and disables the schedule tri

cmd.Flags().StringVar(&name, "name", "", "Automation name")
cmd.Flags().Int64Var(&teamID, "team-id", 0, "Scope team ID; 0 means personal scope")
cmd.Flags().StringVar(&schedule, "schedule", "", "Schedule helper: hourly, daily, weekly, or cron")
cmd.Flags().StringVar(&at, "at", "", "Wall-clock time in HH:MM; for hourly schedules, only the minute is used")
cmd.Flags().StringVar(&schedule, "schedule", "", "UTC schedule helper: hourly, daily, weekly, or cron")
cmd.Flags().StringVar(&at, "at", "", "UTC time in HH:MM; for hourly schedules, only the minute is used. "+automationUTCNote)
cmd.Flags().StringVar(&weekday, "weekday", "", "Weekday for weekly schedules: sun, mon, tue, wed, thu, fri, sat, or 0-7")
cmd.Flags().StringVar(&cronExpr, "cron-expr", "", "Exact 5-field cron expression; overrides --schedule helpers")
cmd.Flags().StringVar(&cronExpr, "cron-expr", "", "Exact 5-field UTC cron expression; overrides --schedule helpers. "+automationUTCNote)
cmd.Flags().BoolVar(&disabled, "disabled", false, "Create the Automation disabled")
cmd.Flags().BoolVar(&scheduleEnabled, "schedule-enabled", true, "Whether the schedule trigger is enabled")
cmd.Flags().BoolVar(&httpPostTrigger, "http-post-trigger", false, "Create and enable an HTTP POST trigger")
Expand Down Expand Up @@ -218,9 +222,13 @@ func newAutomationUpdateCmd() *cobra.Command {
Short: "Update an Automation",
Long: curatedLong(`Update mutable fields on an Automation rule.

The personal/team scope is intentionally not exposed here. Scope is immutable
after creation; create a new Automation if the target person/team scope needs to change.`, "Automations", "RuleWriteUpdate"),
Example: ` flashduty automation update auto_123 --name "Daily brief v2" --cron-expr "15 9 * * *"
The personal/team scope is intentionally not exposed here. Scope is immutable
after creation; create a new Automation if the target person/team scope needs to change.

Schedule helpers build a 5-field UTC cron expression. --at and --cron-expr are
interpreted in UTC, not the caller's local timezone. Convert local wall-clock
requests to UTC before passing --at or --cron-expr.`, "Automations", "RuleWriteUpdate"),
Example: ` flashduty automation update auto_123 --name "Daily brief v2" --cron-expr "15 1 * * *"
flashduty automation update auto_123 --disable
flashduty automation update auto_123 --enable-http-post-trigger --rotate-http-post-token`,
Args: requireExactArg("rule_id"),
Expand Down Expand Up @@ -311,10 +319,10 @@ after creation; create a new Automation if the target person/team scope needs to
}

cmd.Flags().StringVar(&name, "name", "", "New Automation name")
cmd.Flags().StringVar(&schedule, "schedule", "", "Schedule helper: hourly, daily, weekly, or cron")
cmd.Flags().StringVar(&at, "at", "", "Wall-clock time in HH:MM; for hourly schedules, only the minute is used")
cmd.Flags().StringVar(&schedule, "schedule", "", "UTC schedule helper: hourly, daily, weekly, or cron")
cmd.Flags().StringVar(&at, "at", "", "UTC time in HH:MM; for hourly schedules, only the minute is used. "+automationUTCNote)
cmd.Flags().StringVar(&weekday, "weekday", "", "Weekday for weekly schedules: sun, mon, tue, wed, thu, fri, sat, or 0-7")
cmd.Flags().StringVar(&cronExpr, "cron-expr", "", "Exact 5-field cron expression; overrides --schedule helpers")
cmd.Flags().StringVar(&cronExpr, "cron-expr", "", "Exact 5-field UTC cron expression; overrides --schedule helpers. "+automationUTCNote)
cmd.Flags().BoolVar(&enableRule, "enable", false, "Enable the Automation")
cmd.Flags().BoolVar(&disableRule, "disable", false, "Disable the Automation")
cmd.Flags().BoolVar(&enableSchedule, "enable-schedule", false, "Enable the schedule trigger")
Expand Down
22 changes: 22 additions & 0 deletions internal/cli/automation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,28 @@ func TestAutomationCreateDailyDefaultsEnabled(t *testing.T) {
assertBody(t, stub.lastBody, "prompt", "Summarize yesterday's incidents")
}

func TestAutomationScheduleHelpDocumentsUTC(t *testing.T) {
saveAndResetGlobals(t)

for _, args := range [][]string{
{"automation", "create", "--help"},
{"automation", "update", "auto_123", "--help"},
} {
out, err := execCommand(args...)
if err != nil {
t.Fatalf("%v unexpected error: %v", args, err)
}
for _, want := range []string{
"UTC",
"Convert local wall-clock requests to UTC before passing --at or --cron-expr.",
} {
if !strings.Contains(out, want) {
t.Fatalf("%v help missing %q\n%s", args, want, out)
}
}
}
}

func TestAutomationCreateHTTPPostOnly(t *testing.T) {
saveAndResetGlobals(t)
stub := newGFStub(t)
Expand Down
18 changes: 10 additions & 8 deletions skills/flashduty/reference/automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ Prereq: `SKILL.md` read. Automations create AI SRE sessions on a schedule or thr
## Scheduling

- Default create behavior: enabled immediately. Use `--disabled` only if the user asks for a disabled Automation.
- No timezone flag is exposed by the current API. Build the requested wall-clock schedule in the account/customer timezone context.
- No timezone flag is exposed by the current API. Automation schedules are stored and sent as UTC cron.
- If the user asks for a local wall-clock schedule, first identify the intended timezone from the session context, runner `date`, or the user's wording. Convert that local time to UTC before calling the CLI. If the timezone is unclear, ask before creating or updating the schedule.
- Helper schedules:
- `--schedule hourly --at 00:15` -> minute 15 of every hour.
- `--schedule daily --at 09:30` -> every day at 09:30.
- `--schedule weekly --weekday mon --at 10:00` -> every Monday at 10:00.
- For exact minute-level control, use `--cron-expr '<minute> <hour> <day> <month> <weekday>'`.
- `--schedule hourly --at 00:15` -> minute 15 of every UTC hour.
- `--schedule daily --at 01:30` -> every day at 01:30 UTC.
- `--schedule weekly --weekday mon --at 02:00` -> every Monday at 02:00 UTC.
- For exact minute-level control, use `--cron-expr '<minute> <hour> <day> <month> <weekday>'` in UTC.
- Example: Asia/Shanghai 11:00 is UTC 03:00, so use `--schedule daily --at 03:00` or `--cron-expr "0 3 * * *"`.
- HTTP POST-only rule: pass `--http-post-trigger` without schedule flags. The CLI sends a placeholder cron and disables the schedule trigger.

## Hot flow - create from chat
Expand All @@ -44,7 +46,7 @@ fduty automation create \
--name "Daily SRE brief" \
--team-id <team-id> \
--schedule daily \
--at 09:30 \
--at 01:30 \
--prompt "Summarize yesterday's incidents, noisy alerts, and follow-up risks." \
--output-format toon
```
Expand Down Expand Up @@ -72,7 +74,7 @@ fduty automation update <rule-id> --rotate-http-post-token --output-format toon
```bash
fduty automation create \
--name "Weekday 08:05 review" \
--cron-expr "5 8 * * 1-5" \
--cron-expr "5 0 * * 1-5" \
--prompt "Review open incidents and alert noise before the workday." \
--output-format toon
```
Expand All @@ -85,7 +87,7 @@ fduty automation get <rule-id> --output-format toon
fduty automation runs <rule-id> --since 7d --output-format toon

fduty automation update <rule-id> --disable --output-format toon
fduty automation update <rule-id> --enable --cron-expr "30 9 * * *" --output-format toon
fduty automation update <rule-id> --enable --cron-expr "30 1 * * *" --output-format toon
fduty automation delete <rule-id> --force
```

Expand Down
Loading