hub-launch 1.2.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env bash
2
+ # hula-execute-manage.sh — management surface for the /hula-execute skill
3
+ # Usage (exactly one action per call):
4
+ # bash .github/scripts/hula-execute-manage.sh --list
5
+ # bash .github/scripts/hula-execute-manage.sh --list-schedules
6
+ # bash .github/scripts/hula-execute-manage.sh --show <runId>
7
+ # bash .github/scripts/hula-execute-manage.sh --run-now <scheduleId>
8
+ # bash .github/scripts/hula-execute-manage.sh --cancel-schedule <scheduleId>
9
+ # bash .github/scripts/hula-execute-manage.sh --publish-skill <repo-relative-path>
10
+ # bash .github/scripts/hula-execute-manage.sh --delete-skill <repo-relative-path>
11
+ #
12
+ # Each invocation maps to the corresponding `hula execute …` call, captures its
13
+ # output, and emits a single structured JSON object to stdout; all other output
14
+ # goes to stderr.
15
+ #
16
+ # JSON contract (stdout):
17
+ # read verbs : {"status":"success","kind":"list|list-schedules|show","cliOutput":"…"}
18
+ # run-now : {"status":"success","kind":"run-now","runId":"…","cliOutput":"…"}
19
+ # cancel : {"status":"success","kind":"cancel-schedule","scheduleId":"…","cliOutput":"…"}
20
+ # publish-skill : {"status":"success","kind":"publish-skill","path":"…","cliOutput":"…"}
21
+ # delete-skill : {"status":"success","kind":"delete-skill","path":"…","cliOutput":"…"}
22
+ # failure : {"status":"error","code":<n>,"message":"…"}
23
+ #
24
+ # Exit codes: 0=success, 1=user error, 2=tool error
25
+ #
26
+ # Security: never prints secrets. Credentials are resolved inside the CLI from
27
+ # flags/config/env — they are NOT passed by this script. --publish-skill and
28
+ # --delete-skill reject path traversal and require a .hublaunch/skills/ path.
29
+
30
+ set -euo pipefail
31
+
32
+ # ── helpers ────────────────────────────────────────────────────────────────────
33
+
34
+ json_escape() {
35
+ if command -v jq &>/dev/null; then
36
+ # jq -Rs . reads stdin as a raw string and outputs a JSON string (with surrounding quotes).
37
+ # Strip the surrounding quotes; the escaped content is safe to embed in JSON.
38
+ printf '%s' "$1" | jq -Rs . | sed 's/^"//;s/"$//'
39
+ else
40
+ # Fallback: escape backslashes and double quotes, collapse control chars to spaces
41
+ printf '%s' "$1" \
42
+ | sed 's/\\/\\\\/g; s/"/\\"/g' \
43
+ | tr '\n\r\t' ' '
44
+ fi
45
+ }
46
+
47
+ die() {
48
+ local code="$1"; shift
49
+ local safe_msg
50
+ safe_msg=$(json_escape "$*")
51
+ printf '{"status":"error","code":%d,"message":"%s"}\n' "$code" "$safe_msg" >&1
52
+ exit "$code"
53
+ }
54
+
55
+ # Validate a skill path: must be repo-relative, under .hublaunch/skills/, no traversal.
56
+ validate_skill_path() {
57
+ local p="$1"
58
+ case "$p" in
59
+ /*) die 1 "Skill path must be repo-relative, not absolute: ${p}" ;;
60
+ esac
61
+ if printf '%s' "$p" | grep -q '\.\.'; then
62
+ die 1 "Invalid skill path (path traversal not allowed): ${p}"
63
+ fi
64
+ case "$p" in
65
+ .hublaunch/skills/*) : ;;
66
+ *) die 1 "Skill path must be under .hublaunch/skills/ — got ${p}" ;;
67
+ esac
68
+ }
69
+
70
+ # ── argument parsing ────────────────────────────────────────────────────────────
71
+ # Exactly one action flag is allowed per call.
72
+
73
+ ACTION=''
74
+ ARG=''
75
+
76
+ set_action() {
77
+ if [[ -n "$ACTION" ]]; then
78
+ die 1 "Provide exactly one action (got --$ACTION and $1)."
79
+ fi
80
+ ACTION="$1"
81
+ }
82
+
83
+ while [[ $# -gt 0 ]]; do
84
+ case "$1" in
85
+ --list) set_action 'list'; shift ;;
86
+ --list-schedules) set_action 'list-schedules'; shift ;;
87
+ --show) set_action 'show'; ARG="$2"; shift 2 ;;
88
+ --show=*) set_action 'show'; ARG="${1#--show=}"; shift ;;
89
+ --run-now) set_action 'run-now'; ARG="$2"; shift 2 ;;
90
+ --run-now=*) set_action 'run-now'; ARG="${1#--run-now=}"; shift ;;
91
+ --cancel-schedule) set_action 'cancel-schedule'; ARG="$2"; shift 2 ;;
92
+ --cancel-schedule=*) set_action 'cancel-schedule'; ARG="${1#--cancel-schedule=}"; shift ;;
93
+ --publish-skill) set_action 'publish-skill'; ARG="$2"; shift 2 ;;
94
+ --publish-skill=*) set_action 'publish-skill'; ARG="${1#--publish-skill=}"; shift ;;
95
+ --delete-skill) set_action 'delete-skill'; ARG="$2"; shift 2 ;;
96
+ --delete-skill=*) set_action 'delete-skill'; ARG="${1#--delete-skill=}"; shift ;;
97
+ *) die 1 "Unexpected argument: $1" ;;
98
+ esac
99
+ done
100
+
101
+ if [[ -z "$ACTION" ]]; then
102
+ die 1 "Usage: bash .github/scripts/hula-execute-manage.sh (--list | --list-schedules | --show <id> | --run-now <id> | --cancel-schedule <id> | --publish-skill <path> | --delete-skill <path>)"
103
+ fi
104
+
105
+ # Actions that require an argument.
106
+ case "$ACTION" in
107
+ show|run-now|cancel-schedule|publish-skill|delete-skill)
108
+ [[ -n "$ARG" ]] || die 1 "--$ACTION requires an argument." ;;
109
+ esac
110
+
111
+ # Path validation for skill file actions.
112
+ case "$ACTION" in
113
+ publish-skill|delete-skill) validate_skill_path "$ARG" ;;
114
+ esac
115
+
116
+ # ── Build and run the hula execute invocation ────────────────────────────────
117
+
118
+ CLI_ARGS=()
119
+ case "$ACTION" in
120
+ list) CLI_ARGS=(--list) ;;
121
+ list-schedules) CLI_ARGS=(--list-schedules) ;;
122
+ show) CLI_ARGS=(--show "$ARG") ;;
123
+ run-now) CLI_ARGS=(--run-now "$ARG") ;;
124
+ cancel-schedule) CLI_ARGS=(--cancel-schedule "$ARG") ;;
125
+ publish-skill) CLI_ARGS=(--publish-skill "$ARG") ;;
126
+ delete-skill) CLI_ARGS=(--delete-skill "$ARG") ;;
127
+ esac
128
+
129
+ printf '⚙️ hula execute --%s...\n' "$ACTION" >&2
130
+
131
+ OUTPUT=$(hula execute "${CLI_ARGS[@]}" 2>&1) || die 2 "Management action failed: $OUTPUT"
132
+
133
+ SAFE_OUTPUT=$(json_escape "$OUTPUT")
134
+
135
+ # ── Emit the structured JSON result ──────────────────────────────────────────
136
+
137
+ case "$ACTION" in
138
+ list|list-schedules|show)
139
+ printf '{"status":"success","kind":"%s","cliOutput":"%s"}\n' \
140
+ "$ACTION" "$SAFE_OUTPUT"
141
+ ;;
142
+ run-now)
143
+ # The CLI prints " ID: <id>" for the new run.
144
+ RUN_ID=$(printf '%s' "$OUTPUT" | grep -E '^\s*ID:' | head -1 | sed -E 's/^\s*ID:\s*//') || true
145
+ SAFE_ID=$(json_escape "$RUN_ID")
146
+ printf '{"status":"success","kind":"run-now","runId":"%s","cliOutput":"%s"}\n' \
147
+ "$SAFE_ID" "$SAFE_OUTPUT"
148
+ ;;
149
+ cancel-schedule)
150
+ SAFE_ID=$(json_escape "$ARG")
151
+ printf '{"status":"success","kind":"cancel-schedule","scheduleId":"%s","cliOutput":"%s"}\n' \
152
+ "$SAFE_ID" "$SAFE_OUTPUT"
153
+ ;;
154
+ publish-skill|delete-skill)
155
+ SAFE_PATH=$(json_escape "$ARG")
156
+ printf '{"status":"success","kind":"%s","path":"%s","cliOutput":"%s"}\n' \
157
+ "$ACTION" "$SAFE_PATH" "$SAFE_OUTPUT"
158
+ ;;
159
+ esac
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env bash
2
+ # hula-execute-run.sh — runs the hula.execute workflow for the /hula-execute skill
3
+ # Usage:
4
+ # bash .github/scripts/hula-execute-run.sh \
5
+ # (--built-in <name> | --action-path <path>) \
6
+ # [--entry-point <path>] [--outcome-type <pr|plan|feedback>] [--schedule "<cron>"]
7
+ #
8
+ # Performs: hula execute (one-off run, or a recurring schedule when --schedule is given).
9
+ # Outputs a single structured JSON object to stdout; all other output goes to stderr.
10
+ #
11
+ # JSON contract (stdout):
12
+ # one-off run : {"status":"success","kind":"run","runId":"…","prUrl":"…","cliOutput":"…"}
13
+ # schedule : {"status":"success","kind":"schedule","scheduleId":"…","cronExpr":"…","cliOutput":"…"}
14
+ # failure : {"status":"error","code":<n>,"message":"…"}
15
+ #
16
+ # Exit codes: 0=success, 1=user error, 2=tool error
17
+ #
18
+ # Security: never prints secrets. Credentials are resolved inside the CLI from
19
+ # flags/config/env — they are NOT passed by this script.
20
+
21
+ set -euo pipefail
22
+
23
+ # ── helpers ────────────────────────────────────────────────────────────────────
24
+
25
+ json_escape() {
26
+ if command -v jq &>/dev/null; then
27
+ # jq -Rs . reads stdin as a raw string and outputs a JSON string (with surrounding quotes).
28
+ # Strip the surrounding quotes; the escaped content is safe to embed in JSON.
29
+ printf '%s' "$1" | jq -Rs . | sed 's/^"//;s/"$//'
30
+ else
31
+ # Fallback: escape backslashes and double quotes, collapse control chars to spaces
32
+ printf '%s' "$1" \
33
+ | sed 's/\\/\\\\/g; s/"/\\"/g' \
34
+ | tr '\n\r\t' ' '
35
+ fi
36
+ }
37
+
38
+ die() {
39
+ local code="$1"; shift
40
+ local safe_msg
41
+ safe_msg=$(json_escape "$*")
42
+ printf '{"status":"error","code":%d,"message":"%s"}\n' "$code" "$safe_msg" >&1
43
+ exit "$code"
44
+ }
45
+
46
+ # ── argument parsing ────────────────────────────────────────────────────────────
47
+
48
+ BUILT_IN=''
49
+ ACTION_PATH=''
50
+ ENTRY_POINT=''
51
+ OUTCOME_TYPE=''
52
+ SCHEDULE=''
53
+
54
+ while [[ $# -gt 0 ]]; do
55
+ case "$1" in
56
+ --built-in) BUILT_IN="$2"; shift 2 ;;
57
+ --built-in=*) BUILT_IN="${1#--built-in=}"; shift ;;
58
+ --action-path) ACTION_PATH="$2"; shift 2 ;;
59
+ --action-path=*) ACTION_PATH="${1#--action-path=}"; shift ;;
60
+ --entry-point) ENTRY_POINT="$2"; shift 2 ;;
61
+ --entry-point=*) ENTRY_POINT="${1#--entry-point=}"; shift ;;
62
+ --outcome-type) OUTCOME_TYPE="$2"; shift 2 ;;
63
+ --outcome-type=*) OUTCOME_TYPE="${1#--outcome-type=}"; shift ;;
64
+ --schedule) SCHEDULE="$2"; shift 2 ;;
65
+ --schedule=*) SCHEDULE="${1#--schedule=}"; shift ;;
66
+ *) die 1 "Unexpected argument: $1" ;;
67
+ esac
68
+ done
69
+
70
+ # Exactly one of --built-in / --action-path is required.
71
+ if [[ -n "$BUILT_IN" && -n "$ACTION_PATH" ]]; then
72
+ die 1 "Provide either --built-in or --action-path, not both."
73
+ fi
74
+ if [[ -z "$BUILT_IN" && -z "$ACTION_PATH" ]]; then
75
+ die 1 "Usage: bash .github/scripts/hula-execute-run.sh (--built-in <name> | --action-path <path>) [--entry-point <path>] [--outcome-type <pr|plan|feedback>] [--schedule \"<cron>\"]"
76
+ fi
77
+
78
+ # Reject directory-traversal in --action-path, except for https:// URLs.
79
+ if [[ -n "$ACTION_PATH" ]] && [[ "$ACTION_PATH" != https://* ]]; then
80
+ if printf '%s' "$ACTION_PATH" | grep -q '\.\.'; then
81
+ die 1 "Invalid action path (path traversal not allowed): ${ACTION_PATH}"
82
+ fi
83
+ fi
84
+
85
+ # ── Run hula execute ─────────────────────────────────────────────────────────
86
+
87
+ ARGS=()
88
+ if [[ -n "$BUILT_IN" ]]; then
89
+ ARGS+=(--built-in "$BUILT_IN")
90
+ else
91
+ ARGS+=(--action-path "$ACTION_PATH")
92
+ fi
93
+ [[ -n "$ENTRY_POINT" ]] && ARGS+=(--entry-point "$ENTRY_POINT")
94
+ [[ -n "$OUTCOME_TYPE" ]] && ARGS+=(--outcome-type "$OUTCOME_TYPE")
95
+ [[ -n "$SCHEDULE" ]] && ARGS+=(--schedule "$SCHEDULE")
96
+
97
+ if [[ -n "$SCHEDULE" ]]; then
98
+ printf '⏰ Scheduling execute-action (%s)...\n' "$SCHEDULE" >&2
99
+ else
100
+ printf '🚀 Running execute-action...\n' >&2
101
+ fi
102
+
103
+ OUTPUT=$(hula execute "${ARGS[@]}" 2>&1) || die 2 "Execute failed: $OUTPUT"
104
+
105
+ # ── Parse identifiers from the human-readable CLI output ─────────────────────
106
+ # Parsing is intentionally tolerant: a missing identifier becomes an empty
107
+ # string and we still report success, falling back to the raw cliOutput.
108
+
109
+ # Distinguish a schedule from a one-off run by the CLI's success banner.
110
+ if printf '%s' "$OUTPUT" | grep -q 'Schedule created successfully'; then
111
+ KIND='schedule'
112
+ elif printf '%s' "$OUTPUT" | grep -q 'Execute action queued successfully'; then
113
+ KIND='run'
114
+ elif [[ -n "$SCHEDULE" ]]; then
115
+ KIND='schedule'
116
+ else
117
+ KIND='run'
118
+ fi
119
+
120
+ # The CLI prints " ID: <id>" for the run/schedule identifier.
121
+ PARSED_ID=$(printf '%s' "$OUTPUT" | grep -E '^\s*ID:' | head -1 | sed -E 's/^\s*ID:\s*//') || true
122
+ SAFE_OUTPUT=$(json_escape "$OUTPUT")
123
+
124
+ if [[ "$KIND" == 'schedule' ]]; then
125
+ # " Cron: <expr>" — fall back to the schedule we sent if the label is absent.
126
+ CRON_EXPR=$(printf '%s' "$OUTPUT" | grep -E '^\s*Cron:' | head -1 | sed -E 's/^\s*Cron:\s*//') || true
127
+ [[ -z "$CRON_EXPR" ]] && CRON_EXPR="$SCHEDULE"
128
+ SAFE_ID=$(json_escape "$PARSED_ID")
129
+ SAFE_CRON=$(json_escape "$CRON_EXPR")
130
+ printf '{"status":"success","kind":"schedule","scheduleId":"%s","cronExpr":"%s","cliOutput":"%s"}\n' \
131
+ "$SAFE_ID" "$SAFE_CRON" "$SAFE_OUTPUT"
132
+ else
133
+ # " PR: <url>"
134
+ PR_URL=$(printf '%s' "$OUTPUT" | grep -E '^\s*PR:' | head -1 | sed -E 's/^\s*PR:\s*//') || true
135
+ SAFE_ID=$(json_escape "$PARSED_ID")
136
+ SAFE_PR=$(json_escape "$PR_URL")
137
+ printf '{"status":"success","kind":"run","runId":"%s","prUrl":"%s","cliOutput":"%s"}\n' \
138
+ "$SAFE_ID" "$SAFE_PR" "$SAFE_OUTPUT"
139
+ fi
@@ -0,0 +1,142 @@
1
+ # Execute Skill Creation Instructions
2
+
3
+ These instructions drive the **create-from-description** mode of the
4
+ `/hula-execute` skill: turning a plain-language description into a published
5
+ action file that `hula execute` can run or schedule.
6
+
7
+ The action file is plain instruction markdown that the hula-project server reads
8
+ from `origin/<uploadBranch>` (default `main`) at run time. It is **not** an Agent
9
+ Skill and is never registered as a slash command — it lives under
10
+ `.hublaunch/skills/`.
11
+
12
+ Follow these steps in order. Do **not** skip the question step, and do **not**
13
+ run `hula execute` before the file is successfully published.
14
+
15
+ ## Step 1: Ask clarifying questions first (then STOP)
16
+
17
+ Mirror the `/hula-plan` question-first behavior. Before writing anything, ask the
18
+ user the questions you cannot confidently answer from their description:
19
+
20
+ 1. **What should the action do?** The concrete task to perform on each run
21
+ (e.g. "remove unreachable code", "upgrade dependencies and fix breakages").
22
+ 2. **Target entry point** (optional): the file, directory, or URL the action
23
+ focuses on (e.g. `src/`). Maps to `--entry-point`.
24
+ 3. **Outcome type**: `pr` (open a pull request), `plan` (produce a plan), or
25
+ `feedback` (review/report only). Maps to `--outcome-type`. Default `pr`.
26
+ 4. **One-off or scheduled?** If recurring, get the schedule phrase (e.g. "every
27
+ night", "every Monday 9am").
28
+ 5. **Any constraints / scope limits** the run must respect (optional).
29
+
30
+ **STOP and wait for the answers.** Do not proceed to authoring until the user
31
+ responds. If anything is still ambiguous after the answers, ask a focused
32
+ follow-up round.
33
+
34
+ ## Step 2: Confirm the action name
35
+
36
+ Derive a default slug from the description: lowercase, hyphen-separated, no
37
+ spaces or punctuation (e.g. "remove unreachable code in src" →
38
+ `remove-unreachable-code`). Propose it to the user and let them override. Confirm
39
+ the final name before writing the file.
40
+
41
+ ## Step 3: Resolve the schedule (if recurring)
42
+
43
+ If the action is scheduled, translate the phrase to a 5-field cron expression
44
+ using the preset table in `SKILL.md`, then **echo the cron with a plain-English
45
+ readback and confirm it** before publishing/running. There is no server-side
46
+ cron validation, so the readback + confirmation is mandatory.
47
+
48
+ ## Step 4: Compute the filename and path
49
+
50
+ - Filename: `YYYY-MM-DD-HH:MM-<name-slug>.md`
51
+ - Use today's date and the current time on a 24-hour clock.
52
+ - `<name-slug>` is the confirmed, lowercase-hyphenated action name.
53
+ - Repo-relative path: `.hublaunch/skills/<filename>`
54
+ - Create the `.hublaunch/skills/` directory if it does not exist.
55
+
56
+ ## Step 5: Write the action file (free-form instruction markdown, NO frontmatter)
57
+
58
+ Use the `Write` tool to create the file at the computed path. The content MUST be
59
+ free-form instruction markdown with **no YAML frontmatter** (frontmatter would
60
+ make it look like an Agent Skill). Follow this template:
61
+
62
+ ```markdown
63
+ # <Action Title>
64
+
65
+ ## Goal
66
+ <1–3 sentences describing what this action should accomplish.>
67
+
68
+ ## Steps
69
+ 1. <step>
70
+ 2. <step>
71
+ 3. <step>
72
+
73
+ ## Constraints
74
+ - <optional: what must not change / scope limits>
75
+
76
+ ## Outcome
77
+ <What the run should produce, consistent with the chosen --outcome-type:
78
+ pr | plan | feedback.>
79
+ ```
80
+
81
+ Fill the template from the user's answers. Omit the `## Constraints` section if
82
+ the user gave no constraints.
83
+
84
+ ## Step 6: Publish to origin/main BEFORE running (mandatory ordering)
85
+
86
+ The server clones the repository and reads the action file from the default
87
+ branch at run time (and re-reads it on every scheduled fire). The file MUST be on
88
+ the branch before the run, so publish it first:
89
+
90
+ ```bash
91
+ bash .github/scripts/hula-execute-manage.sh --publish-skill .hublaunch/skills/<filename>
92
+ ```
93
+
94
+ Parse the single JSON object it prints:
95
+
96
+ - If `status` is `"error"`: display `❌ <message>` and **STOP**. Do **not** run
97
+ `hula execute` — the file is not on the branch, so the run would fail.
98
+ - If `status` is `"success"`: continue to Step 7.
99
+
100
+ ## Step 7: Run or schedule the action
101
+
102
+ Only after a successful publish, invoke the run wrapper with `--action-path`
103
+ pointing at the committed file:
104
+
105
+ ```bash
106
+ bash .github/scripts/hula-execute-run.sh --action-path .hublaunch/skills/<filename> [--entry-point <path>] [--outcome-type <type>] [--schedule "<cron>"]
107
+ ```
108
+
109
+ Pass only the flags you resolved. Quote the cron expression.
110
+
111
+ ## Step 8: Report the result
112
+
113
+ Parse the run wrapper's JSON (same contract as the normal run flow) and report:
114
+
115
+ - The **created file path** (`.hublaunch/skills/<filename>`) and that it was
116
+ published to `origin/<uploadBranch>`.
117
+ - The **run or schedule result**:
118
+ - One-off run → the Run ID and PR link (if any), plus
119
+ `hula execute --show <runId>` to check status.
120
+ - Schedule → the Schedule ID and cron expression, plus a note that you can
121
+ manage it with `/hula-execute list`, `/hula-execute run now <id>`,
122
+ `/hula-execute cancel <id>`, or `/hula-execute update <id> …`.
123
+
124
+ Example success report:
125
+
126
+ ```
127
+ ✅ Action created and published
128
+
129
+ 📄 **File**: .hublaunch/skills/2026-06-19-16:35-remove-unreachable-code.md (on origin/main)
130
+ 🔖 **Schedule ID**: sch_abc123
131
+ ⏰ **Cron**: 0 3 * * * (every day at 3:00 AM)
132
+
133
+ Manage it with: /hula-execute list · /hula-execute run now sch_abc123 · /hula-execute cancel sch_abc123
134
+ ```
135
+
136
+ ## Notes
137
+
138
+ - Never echo secrets. Credentials resolve inside the CLI.
139
+ - The action file is committed to `origin/<uploadBranch>` via a temporary
140
+ worktree — the user's current branch and working tree are never touched.
141
+ - If the publish succeeds but the run fails, the file remains on the branch; the
142
+ user can re-run it later with `/hula-execute .hublaunch/skills/<filename>`.