documenting action spec

This commit is contained in:
2026-02-09 00:30:48 -06:00
parent a74e13fa0b
commit 588b319fec
38 changed files with 3050 additions and 286 deletions

View File

@@ -1,8 +1,8 @@
# Build Pack Environments Action
# Creates runtime environments and installs dependencies for packs
name: build_pack_envs
ref: core.build_pack_envs
label: "Build Pack Environments"
description: "Build runtime environments for packs and install declared dependencies (Python requirements.txt, Node.js package.json)"
enabled: true
runner_type: shell

View File

@@ -1,8 +1,8 @@
# Download Packs Action
# Downloads packs from various sources (git repositories, HTTP archives, or pack registry)
name: download_packs
ref: core.download_packs
label: "Download Packs"
description: "Download packs from git repositories, HTTP archives, or pack registry to a temporary directory"
enabled: true
runner_type: shell

View File

@@ -1,8 +1,8 @@
# Echo Action
# Outputs a message to stdout
name: echo
ref: core.echo
label: "Echo"
description: "Echo a message to stdout"
enabled: true
@@ -26,7 +26,6 @@ parameters:
message:
type: string
description: "Message to echo (empty string if not provided)"
required: []
# Output schema: not applicable for text output format
# The action outputs plain text to stdout

View File

@@ -1,8 +1,8 @@
# Get Pack Dependencies Action
# Parses pack.yaml files to identify pack and runtime dependencies
name: get_pack_dependencies
ref: core.get_pack_dependencies
label: "Get Pack Dependencies"
description: "Parse pack.yaml files to extract pack dependencies and runtime requirements"
enabled: true
runner_type: shell

View File

@@ -1,209 +1,259 @@
#!/bin/bash
#!/bin/sh
# HTTP Request Action - Core Pack
# Make HTTP requests to external APIs using curl
#
# This script uses pure POSIX shell without external dependencies like jq.
# It reads parameters in DOTENV format from stdin until the delimiter.
set -e
set -o pipefail
# Read JSON parameters from stdin
INPUT=$(cat)
# Initialize variables
url=""
method="GET"
body=""
json_body=""
timeout="30"
verify_ssl="true"
auth_type="none"
auth_username=""
auth_password=""
auth_token=""
follow_redirects="true"
max_redirects="10"
# Parse required parameters
URL=$(echo "$INPUT" | jq -r '.url // ""')
# Temporary files
headers_file=$(mktemp)
query_params_file=$(mktemp)
body_file=""
temp_headers=$(mktemp)
curl_output=$(mktemp)
if [ -z "$URL" ] || [ "$URL" = "null" ]; then
echo "ERROR: 'url' parameter is required" >&2
cleanup() {
rm -f "$headers_file" "$query_params_file" "$temp_headers" "$curl_output"
[ -n "$body_file" ] && [ -f "$body_file" ] && rm -f "$body_file"
}
trap cleanup EXIT
# Read DOTENV-formatted parameters
while IFS= read -r line; do
case "$line" in
*"---ATTUNE_PARAMS_END---"*) break ;;
esac
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
# Remove quotes
case "$value" in
\"*\") value="${value#\"}"; value="${value%\"}" ;;
\'*\') value="${value#\'}"; value="${value%\'}" ;;
esac
# Process parameters
case "$key" in
url) url="$value" ;;
method) method="$value" ;;
body) body="$value" ;;
json_body) json_body="$value" ;;
timeout) timeout="$value" ;;
verify_ssl) verify_ssl="$value" ;;
auth_type) auth_type="$value" ;;
auth_username) auth_username="$value" ;;
auth_password) auth_password="$value" ;;
auth_token) auth_token="$value" ;;
follow_redirects) follow_redirects="$value" ;;
max_redirects) max_redirects="$value" ;;
headers.*)
printf '%s: %s\n' "${key#headers.}" "$value" >> "$headers_file"
;;
query_params.*)
printf '%s=%s\n' "${key#query_params.}" "$value" >> "$query_params_file"
;;
esac
done
# Validate required
if [ -z "$url" ]; then
printf '{"status_code":0,"headers":{},"body":"","json":null,"elapsed_ms":0,"url":"","success":false,"error":"url parameter is required"}\n'
exit 1
fi
# Parse optional parameters
METHOD=$(echo "$INPUT" | jq -r '.method // "GET"' | tr '[:lower:]' '[:upper:]')
HEADERS=$(echo "$INPUT" | jq -r '.headers // {}')
BODY=$(echo "$INPUT" | jq -r '.body // ""')
JSON_BODY=$(echo "$INPUT" | jq -c '.json_body // null')
QUERY_PARAMS=$(echo "$INPUT" | jq -r '.query_params // {}')
TIMEOUT=$(echo "$INPUT" | jq -r '.timeout // 30')
VERIFY_SSL=$(echo "$INPUT" | jq -r '.verify_ssl // true')
AUTH_TYPE=$(echo "$INPUT" | jq -r '.auth_type // "none"')
FOLLOW_REDIRECTS=$(echo "$INPUT" | jq -r '.follow_redirects // true')
MAX_REDIRECTS=$(echo "$INPUT" | jq -r '.max_redirects // 10')
# Normalize method
method=$(printf '%s' "$method" | tr '[:lower:]' '[:upper:]')
# Build URL with query parameters
FINAL_URL="$URL"
if [ "$QUERY_PARAMS" != "{}" ] && [ "$QUERY_PARAMS" != "null" ]; then
QUERY_STRING=$(echo "$QUERY_PARAMS" | jq -r 'to_entries | map("\(.key)=\(.value | @uri)") | join("&")')
if [[ "$FINAL_URL" == *"?"* ]]; then
FINAL_URL="${FINAL_URL}&${QUERY_STRING}"
else
FINAL_URL="${FINAL_URL}?${QUERY_STRING}"
# URL encode helper
url_encode() {
printf '%s' "$1" | sed 's/ /%20/g; s/!/%21/g; s/"/%22/g; s/#/%23/g; s/\$/%24/g; s/&/%26/g; s/'\''/%27/g'
}
# Build URL with query params
final_url="$url"
if [ -s "$query_params_file" ]; then
query_string=""
while IFS='=' read -r param_name param_value; do
[ -z "$param_name" ] && continue
encoded=$(url_encode "$param_value")
[ -z "$query_string" ] && query_string="${param_name}=${encoded}" || query_string="${query_string}&${param_name}=${encoded}"
done < "$query_params_file"
if [ -n "$query_string" ]; then
case "$final_url" in
*\?*) final_url="${final_url}&${query_string}" ;;
*) final_url="${final_url}?${query_string}" ;;
esac
fi
fi
# Build curl arguments array
CURL_ARGS=(
-X "$METHOD"
-s # Silent mode
-w "\n%{http_code}\n%{time_total}\n%{url_effective}\n" # Write out metadata
--max-time "$TIMEOUT"
--connect-timeout 10
)
# Handle SSL verification
if [ "$VERIFY_SSL" = "false" ]; then
CURL_ARGS+=(-k)
# Prepare body
if [ -n "$json_body" ]; then
body_file=$(mktemp)
printf '%s' "$json_body" > "$body_file"
elif [ -n "$body" ]; then
body_file=$(mktemp)
printf '%s' "$body" > "$body_file"
fi
# Handle redirects
if [ "$FOLLOW_REDIRECTS" = "true" ]; then
CURL_ARGS+=(-L --max-redirs "$MAX_REDIRECTS")
fi
# Build curl args file (avoid shell escaping issues)
curl_args=$(mktemp)
{
printf -- '-X\n%s\n' "$method"
printf -- '-s\n'
printf -- '-w\n\n%%{http_code}\n%%{url_effective}\n\n'
printf -- '--max-time\n%s\n' "$timeout"
printf -- '--connect-timeout\n10\n'
printf -- '--dump-header\n%s\n' "$temp_headers"
[ "$verify_ssl" = "false" ] && printf -- '-k\n'
if [ "$follow_redirects" = "true" ]; then
printf -- '-L\n'
printf -- '--max-redirs\n%s\n' "$max_redirects"
fi
# Add headers
if [ "$HEADERS" != "{}" ] && [ "$HEADERS" != "null" ]; then
while IFS= read -r header; do
if [ -n "$header" ]; then
CURL_ARGS+=(-H "$header")
fi
done < <(echo "$HEADERS" | jq -r 'to_entries | map("\(.key): \(.value)") | .[]')
fi
if [ -s "$headers_file" ]; then
while IFS= read -r h; do
[ -n "$h" ] && printf -- '-H\n%s\n' "$h"
done < "$headers_file"
fi
# Handle authentication
case "$AUTH_TYPE" in
basic)
AUTH_USERNAME=$(echo "$INPUT" | jq -r '.auth_username // ""')
AUTH_PASSWORD=$(echo "$INPUT" | jq -r '.auth_password // ""')
if [ -n "$AUTH_USERNAME" ] && [ "$AUTH_USERNAME" != "null" ]; then
CURL_ARGS+=(-u "${AUTH_USERNAME}:${AUTH_PASSWORD}")
fi
;;
bearer)
AUTH_TOKEN=$(echo "$INPUT" | jq -r '.auth_token // ""')
if [ -n "$AUTH_TOKEN" ] && [ "$AUTH_TOKEN" != "null" ]; then
CURL_ARGS+=(-H "Authorization: Bearer ${AUTH_TOKEN}")
fi
;;
esac
# Handle request body
if [ "$JSON_BODY" != "null" ] && [ "$JSON_BODY" != "" ]; then
CURL_ARGS+=(-H "Content-Type: application/json")
CURL_ARGS+=(-d "$JSON_BODY")
elif [ -n "$BODY" ] && [ "$BODY" != "null" ]; then
CURL_ARGS+=(-d "$BODY")
fi
# Capture start time
START_TIME=$(date +%s%3N)
# Make the request and capture response headers
TEMP_HEADERS=$(mktemp)
CURL_ARGS+=(--dump-header "$TEMP_HEADERS")
# Execute curl and capture output
set +e
RESPONSE=$(curl "${CURL_ARGS[@]}" "$FINAL_URL" 2>&1)
CURL_EXIT_CODE=$?
set -e
# Calculate elapsed time
END_TIME=$(date +%s%3N)
ELAPSED_MS=$((END_TIME - START_TIME))
# Parse curl output (last 3 lines are: http_code, time_total, url_effective)
BODY_OUTPUT=$(echo "$RESPONSE" | head -n -3)
HTTP_CODE=$(echo "$RESPONSE" | tail -n 3 | head -n 1 | tr -d '\r\n')
CURL_TIME=$(echo "$RESPONSE" | tail -n 2 | head -n 1 | tr -d '\r\n')
EFFECTIVE_URL=$(echo "$RESPONSE" | tail -n 1 | tr -d '\r\n')
# Ensure HTTP_CODE is numeric, default to 0 if not
if ! [[ "$HTTP_CODE" =~ ^[0-9]+$ ]]; then
HTTP_CODE=0
fi
# If curl failed, handle error
if [ "$CURL_EXIT_CODE" -ne 0 ]; then
ERROR_MSG="curl failed with exit code $CURL_EXIT_CODE"
# Determine specific error
case $CURL_EXIT_CODE in
6) ERROR_MSG="Could not resolve host" ;;
7) ERROR_MSG="Failed to connect to host" ;;
28) ERROR_MSG="Request timeout" ;;
35) ERROR_MSG="SSL/TLS connection error" ;;
52) ERROR_MSG="Empty reply from server" ;;
56) ERROR_MSG="Failure receiving network data" ;;
*) ERROR_MSG="curl error code $CURL_EXIT_CODE" ;;
case "$auth_type" in
basic)
[ -n "$auth_username" ] && printf -- '-u\n%s:%s\n' "$auth_username" "$auth_password"
;;
bearer)
[ -n "$auth_token" ] && printf -- '-H\nAuthorization: Bearer %s\n' "$auth_token"
;;
esac
# Output error result as JSON
jq -n \
--arg error "$ERROR_MSG" \
--argjson elapsed "$ELAPSED_MS" \
--arg url "$FINAL_URL" \
'{
status_code: 0,
headers: {},
body: "",
json: null,
elapsed_ms: $elapsed,
url: $url,
success: false,
error: $error
}'
if [ -n "$body_file" ] && [ -f "$body_file" ]; then
[ -n "$json_body" ] && printf -- '-H\nContent-Type: application/json\n'
printf -- '-d\n@%s\n' "$body_file"
fi
rm -f "$TEMP_HEADERS"
printf -- '%s\n' "$final_url"
} > "$curl_args"
# Execute curl
start_time=$(date +%s%3N 2>/dev/null || echo $(($(date +%s) * 1000)))
set +e
xargs -a "$curl_args" curl > "$curl_output" 2>&1
curl_exit_code=$?
set -e
rm -f "$curl_args"
end_time=$(date +%s%3N 2>/dev/null || echo $(($(date +%s) * 1000)))
elapsed_ms=$((end_time - start_time))
# Parse output
response=$(cat "$curl_output")
total_lines=$(printf '%s\n' "$response" | wc -l)
body_lines=$((total_lines - 2))
if [ "$body_lines" -gt 0 ]; then
body_output=$(printf '%s\n' "$response" | head -n "$body_lines")
else
body_output=""
fi
http_code=$(printf '%s\n' "$response" | tail -n 2 | head -n 1 | tr -d '\r\n ')
effective_url=$(printf '%s\n' "$response" | tail -n 1 | tr -d '\r\n')
case "$http_code" in
''|*[!0-9]*) http_code=0 ;;
esac
# Handle errors
if [ "$curl_exit_code" -ne 0 ]; then
error_msg="curl error code $curl_exit_code"
case $curl_exit_code in
6) error_msg="Could not resolve host" ;;
7) error_msg="Failed to connect to host" ;;
28) error_msg="Request timeout" ;;
35) error_msg="SSL/TLS connection error" ;;
52) error_msg="Empty reply from server" ;;
56) error_msg="Failure receiving network data" ;;
esac
error_msg=$(printf '%s' "$error_msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
printf '{"status_code":0,"headers":{},"body":"","json":null,"elapsed_ms":%d,"url":"%s","success":false,"error":"%s"}\n' \
"$elapsed_ms" "$final_url" "$error_msg"
exit 1
fi
# Parse response headers into JSON
HEADERS_JSON="{}"
if [ -f "$TEMP_HEADERS" ]; then
# Skip the status line and parse headers
HEADERS_JSON=$(grep -v "^HTTP/" "$TEMP_HEADERS" | grep ":" | sed 's/\r$//' | jq -R -s -c '
split("\n") |
map(select(length > 0)) |
map(split(": "; "") | select(length > 1) | {key: .[0], value: (.[1:] | join(": "))}) |
map({(.key): .value}) |
add // {}
' || echo '{}')
rm -f "$TEMP_HEADERS"
# Parse headers
headers_json="{"
first_header=true
if [ -f "$temp_headers" ]; then
while IFS= read -r line; do
case "$line" in HTTP/*|'') continue ;; esac
header_name="${line%%:*}"
header_value="${line#*:}"
[ "$header_name" = "$line" ] && continue
header_value=$(printf '%s' "$header_value" | sed 's/^ *//; s/ *$//; s/\r$//; s/\\/\\\\/g; s/"/\\"/g')
header_name=$(printf '%s' "$header_name" | sed 's/\\/\\\\/g; s/"/\\"/g')
if [ "$first_header" = true ]; then
headers_json="${headers_json}\"${header_name}\":\"${header_value}\""
first_header=false
else
headers_json="${headers_json},\"${header_name}\":\"${header_value}\""
fi
done < "$temp_headers"
fi
headers_json="${headers_json}}"
# Success check
success="false"
[ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ] && success="true"
# Escape body
body_escaped=$(printf '%s' "$body_output" | sed 's/\\/\\\\/g; s/"/\\"/g; s/ /\\t/g' | awk '{printf "%s\\n", $0}' | sed 's/\\n$//')
# Detect JSON
json_parsed="null"
if [ -n "$body_output" ]; then
first_char=$(printf '%s' "$body_output" | sed 's/^[[:space:]]*//' | head -c 1)
last_char=$(printf '%s' "$body_output" | sed 's/[[:space:]]*$//' | tail -c 1)
case "$first_char" in
'{'|'[')
case "$last_char" in
'}'|']') json_parsed="$body_output" ;;
esac
;;
esac
fi
# Ensure HEADERS_JSON is valid JSON
if ! echo "$HEADERS_JSON" | jq empty 2>/dev/null; then
HEADERS_JSON="{}"
# Output
if [ "$json_parsed" = "null" ]; then
printf '{"status_code":%d,"headers":%s,"body":"%s","json":null,"elapsed_ms":%d,"url":"%s","success":%s}\n' \
"$http_code" "$headers_json" "$body_escaped" "$elapsed_ms" "$effective_url" "$success"
else
printf '{"status_code":%d,"headers":%s,"body":"%s","json":%s,"elapsed_ms":%d,"url":"%s","success":%s}\n' \
"$http_code" "$headers_json" "$body_escaped" "$json_parsed" "$elapsed_ms" "$effective_url" "$success"
fi
# Determine if successful (2xx status code)
SUCCESS=false
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
SUCCESS=true
fi
# Try to parse body as JSON
JSON_PARSED="null"
if [ -n "$BODY_OUTPUT" ] && echo "$BODY_OUTPUT" | jq empty 2>/dev/null; then
JSON_PARSED=$(echo "$BODY_OUTPUT" | jq -c '.' || echo 'null')
fi
# Output result as JSON
jq -n \
--argjson status_code "$HTTP_CODE" \
--argjson headers "$HEADERS_JSON" \
--arg body "$BODY_OUTPUT" \
--argjson json "$JSON_PARSED" \
--argjson elapsed "$ELAPSED_MS" \
--arg url "$EFFECTIVE_URL" \
--argjson success "$SUCCESS" \
'{
status_code: $status_code,
headers: $headers,
body: $body,
json: $json,
elapsed_ms: $elapsed,
url: $url,
success: $success
}'
# Exit with success
exit 0

View File

@@ -1,8 +1,8 @@
# HTTP Request Action
# Make HTTP requests to external APIs
name: http_request
ref: core.http_request
label: "HTTP Request"
description: "Make HTTP requests to external APIs with support for various methods, headers, and authentication"
enabled: true
@@ -13,9 +13,9 @@ runner_type: shell
entry_point: http_request.sh
# Parameter delivery configuration (for security)
# Use stdin + JSON for secure parameter passing (credentials won't appear in process list)
# Use stdin + DOTENV for secure parameter passing (credentials won't appear in process list)
parameter_delivery: stdin
parameter_format: json
parameter_format: dotenv
# Output format: json (structured data parsing enabled)
output_format: json

View File

@@ -1,8 +1,8 @@
# No Operation Action
# Does nothing - useful for testing and placeholder workflows
name: noop
ref: core.noop
label: "No-Op"
description: "Does nothing - useful for testing and placeholder workflows"
enabled: true
@@ -32,7 +32,6 @@ parameters:
default: 0
minimum: 0
maximum: 255
required: []
# Output schema: not applicable for text output format
# The action outputs plain text to stdout

View File

@@ -1,8 +1,8 @@
# Register Packs Action
# Validates pack structure and loads components into database
name: register_packs
ref: core.register_packs
label: "Register Packs"
description: "Register packs by validating schemas, loading components into database, and copying to permanent storage"
enabled: true
runner_type: shell

View File

@@ -1,8 +1,8 @@
# Sleep Action
# Pauses execution for a specified duration
name: sleep
ref: core.sleep
label: "Sleep"
description: "Sleep for a specified number of seconds"
enabled: true

View File

@@ -25,7 +25,6 @@ conf_schema:
type: boolean
description: "Enable debug logging for core pack actions"
default: false
required: []
# Default pack configuration
config:

View File

@@ -1,8 +1,8 @@
# Timer Sensor
# Monitors time and fires all timer trigger types
name: interval_timer_sensor
ref: core.interval_timer_sensor
label: "Interval Timer Sensor"
description: "Built-in sensor that monitors time and fires timer triggers (interval, cron, and one-shot datetime)"
enabled: true
@@ -28,7 +28,6 @@ parameters:
default: 1
minimum: 1
maximum: 60
required: []
# Poll interval (how often the sensor checks for events)
poll_interval: 1

View File

@@ -1,8 +1,8 @@
# Cron Timer Trigger
# Fires based on cron schedule expressions
name: crontimer
ref: core.crontimer
label: "Cron Timer"
description: "Fires based on a cron schedule expression (e.g., '0 0 * * * *' for every hour)"
enabled: true

View File

@@ -1,8 +1,8 @@
# Datetime Timer Trigger
# Fires once at a specific date and time
name: datetimetimer
ref: core.datetimetimer
label: "DateTime Timer"
description: "Fires once at a specific date and time"
enabled: true

View File

@@ -1,8 +1,8 @@
# Interval Timer Trigger
# Fires at regular intervals based on time unit and interval
name: intervaltimer
ref: core.intervaltimer
label: "Interval Timer"
description: "Fires at regular intervals based on specified time unit and interval"
enabled: true