more internal polish, resilient workers

This commit is contained in:
2026-02-09 18:32:34 -06:00
parent 588b319fec
commit e31ecb781b
62 changed files with 9872 additions and 584 deletions

View File

@@ -2,19 +2,31 @@
## Overview
All actions in the core pack follow Attune's secure-by-design architecture:
- **Parameter delivery:** stdin (JSON format) - never environment variables
- **Output format:** Explicitly declared (text, json, or yaml)
- **Output schema:** Describes structured data shape (json/yaml only)
- **Execution metadata:** Automatically captured (stdout/stderr/exit_code)
All actions in the core pack are implemented as **pure POSIX shell scripts** with **zero external dependencies** (except `curl` for HTTP actions). This design ensures maximum portability and minimal runtime requirements.
**Key Principles:**
- **POSIX shell only** - No bash-specific features, works everywhere
- **DOTENV parameter format** - Simple key=value format, no JSON parsing needed
- **No jq/yq/Python/Node.js** - Core pack depends only on standard POSIX utilities
- **Stdin parameter delivery** - Secure, never exposed in process list
- **Explicit output formats** - text, json, or yaml
## Parameter Delivery Method
**All actions:**
- Read parameters from **stdin** as JSON
- Use `parameter_delivery: stdin` and `parameter_format: json` in their YAML definitions
**All actions use stdin with DOTENV format:**
- Parameters read from **stdin** in `key=value` format
- Use `parameter_delivery: stdin` and `parameter_format: dotenv` in YAML
- Terminated with `---ATTUNE_PARAMS_END---` delimiter
- **DO NOT** use environment variables for parameters
**Example DOTENV input:**
```
message="Hello World"
seconds=5
enabled=true
---ATTUNE_PARAMS_END---
```
## Output Format
**All actions must specify an `output_format`:**
@@ -48,170 +60,160 @@ The worker automatically provides these environment variables to all action exec
- Creating child executions
- Accessing secrets via API
**Example:**
```bash
#!/bin/bash
# Log with context
echo "[$ATTUNE_ACTION] [Exec: $ATTUNE_EXEC_ID] Processing..." >&2
# Call Attune API
curl -s -H "Authorization: Bearer $ATTUNE_API_TOKEN" \
"$ATTUNE_API_URL/api/v1/executions/$ATTUNE_EXEC_ID"
# Conditional behavior
if [ -n "$ATTUNE_RULE" ]; then
echo "Triggered by rule: $ATTUNE_RULE" >&2
fi
```
See [Execution Environment Variables](../../../docs/QUICKREF-execution-environment.md) for complete documentation.
### Custom Environment Variables (Optional)
Custom environment variables can be set via `execution.env_vars` field for:
- **Debug/logging controls** (e.g., `DEBUG=1`, `LOG_LEVEL=debug`)
- **Runtime configuration** (e.g., custom paths, feature flags)
- **Action-specific context** (non-sensitive execution context)
Environment variables should **NEVER** be used for:
- Action parameters (use stdin instead)
- Action parameters (use stdin DOTENV instead)
- Secrets or credentials (use `ATTUNE_API_TOKEN` to fetch from key vault)
- User-provided data (use stdin parameters)
## Implementation Patterns
## Implementation Pattern
### Bash/Shell Actions
### POSIX Shell Actions (Standard Pattern)
Shell actions read JSON from stdin using `jq`:
All core pack actions follow this pattern:
```sh
#!/bin/sh
# Action Name - Core Pack
# Brief description
#
# This script uses pure POSIX shell without external dependencies like jq.
# It reads parameters in DOTENV format from stdin until the delimiter.
```bash
#!/bin/bash
set -e
set -o pipefail
# Read JSON parameters from stdin
INPUT=$(cat)
# Initialize variables with defaults
param1=""
param2="default_value"
# Parse parameters using jq
PARAM1=$(echo "$INPUT" | jq -r '.param1 // "default_value"')
PARAM2=$(echo "$INPUT" | jq -r '.param2 // ""')
# Read DOTENV-formatted parameters from stdin
while IFS= read -r line; do
case "$line" in
*"---ATTUNE_PARAMS_END---"*) break ;;
esac
[ -z "$line" ] && continue
# Check for null values (optional parameters)
if [ -n "$PARAM2" ] && [ "$PARAM2" != "null" ]; then
echo "Param2 provided: $PARAM2"
fi
key="${line%%=*}"
value="${line#*=}"
# Use the parameters
echo "Param1: $PARAM1"
```
# Remove quotes if present
case "$value" in
\"*\") value="${value#\"}"; value="${value%\"}" ;;
\'*\') value="${value#\'}"; value="${value%\'}" ;;
esac
### Advanced Bash Actions
For more complex bash actions (like http_request.sh), use `curl` or other standard utilities:
```bash
#!/bin/bash
set -e
set -o pipefail
# Read JSON parameters from stdin
INPUT=$(cat)
# Parse parameters
URL=$(echo "$INPUT" | jq -r '.url // ""')
METHOD=$(echo "$INPUT" | jq -r '.method // "GET"')
# Process parameters
case "$key" in
param1) param1="$value" ;;
param2) param2="$value" ;;
esac
done
# Validate required parameters
if [ -z "$URL" ]; then
echo "ERROR: url parameter is required" >&2
if [ -z "$param1" ]; then
echo "ERROR: param1 is required" >&2
exit 1
fi
# Make HTTP request with curl
RESPONSE=$(curl -s -X "$METHOD" "$URL")
# Action logic
echo "Processing: $param1"
# Output result as JSON
jq -n \
--arg body "$RESPONSE" \
--argjson success true \
'{body: $body, success: $success}'
exit 0
```
### Boolean Normalization
```sh
case "$bool_param" in
true|True|TRUE|yes|Yes|YES|1) bool_param="true" ;;
*) bool_param="false" ;;
esac
```
### Numeric Validation
```sh
case "$number" in
''|*[!0-9]*)
echo "ERROR: must be a number" >&2
exit 1
;;
esac
```
## Core Pack Actions
### Simple Actions
1. **echo.sh** - Outputs a message
1. **echo.sh** - Outputs a message (reference implementation)
2. **sleep.sh** - Pauses execution for a specified duration
3. **noop.sh** - Does nothing (useful for testing)
3. **noop.sh** - Does nothing (useful for testing and placeholder workflows)
### HTTP Action
4. **http_request.sh** - Makes HTTP requests with authentication support (curl-based)
4. **http_request.sh** - Makes HTTP requests with full feature support:
- Multiple HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.)
- Custom headers and query parameters
- Authentication (basic, bearer token)
- SSL verification control
- Redirect following
- JSON output with parsed response
### Pack Management Actions (API Wrappers)
These actions wrap API endpoints and pass parameters to the Attune API:
These actions wrap Attune API endpoints for pack management:
5. **download_packs.sh** - Downloads packs from git/HTTP/registry
6. **build_pack_envs.sh** - Builds runtime environments for packs
7. **register_packs.sh** - Registers packs in the database
8. **get_pack_dependencies.sh** - Analyzes pack dependencies
All API wrappers:
- Accept parameters via DOTENV format
- Build JSON request bodies manually (no jq)
- Make authenticated API calls with curl
- Extract response data using simple sed patterns
- Return structured JSON output
## Testing Actions Locally
You can test actions locally by piping JSON to stdin:
Test actions by echoing DOTENV format to stdin:
```bash
# Test echo action
echo '{"message": "Hello from stdin!"}' | ./echo.sh
printf 'message="Hello World"\n---ATTUNE_PARAMS_END---\n' | ./echo.sh
# Test echo with no message (outputs empty line)
echo '{}' | ./echo.sh
# Test with empty parameters
printf '---ATTUNE_PARAMS_END---\n' | ./echo.sh
# Test sleep action
echo '{"seconds": 2, "message": "Sleeping..."}' | ./sleep.sh
printf 'seconds=2\nmessage="Sleeping..."\n---ATTUNE_PARAMS_END---\n' | ./sleep.sh
# Test http_request action
echo '{"url": "https://api.github.com", "method": "GET"}' | ./http_request.sh
printf 'url="https://api.github.com"\nmethod="GET"\n---ATTUNE_PARAMS_END---\n' | ./http_request.sh
# Test with file input
cat params.json | ./echo.sh
cat params.dotenv | ./echo.sh
```
## Migration Summary
**Before (using environment variables):**
```bash
MESSAGE="${ATTUNE_ACTION_MESSAGE:-}"
```
**After (using stdin JSON):**
```bash
INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // ""')
```
## Security Benefits
1. **No process exposure** - Parameters never appear in `ps`, `/proc/<pid>/environ`
2. **Secure by default** - All actions use stdin, no special configuration needed
3. **Clear separation** - Action parameters vs. environment configuration
4. **Audit friendly** - All sensitive data flows through stdin, not environment
## YAML Configuration
All action YAML files explicitly declare parameter delivery and output format:
## YAML Configuration Example
```yaml
name: example_action
ref: core.example_action
label: "Example Action"
description: "Example action demonstrating DOTENV format"
enabled: true
runner_type: shell
entry_point: example.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
# IMPORTANT: Use DOTENV format for POSIX shell compatibility
parameter_delivery: stdin
parameter_format: json
parameter_format: dotenv
# Output format: text, json, or yaml
output_format: text
@@ -221,51 +223,75 @@ parameters:
properties:
message:
type: string
description: "Message to output (empty string if not provided)"
required: []
# Output schema: not applicable for text output format
# For json/yaml formats, describe the structure of data your action outputs
# Do NOT include stdout/stderr/exit_code - those are captured automatically
# Do NOT include generic "status" or "result" wrappers - output your data directly
description: "Message to output"
default: ""
count:
type: integer
description: "Number of times to repeat"
default: 1
required:
- message
```
## Dependencies
**Core pack has ZERO runtime dependencies:**
**Required (universally available):**
- POSIX-compliant shell (`/bin/sh`)
- `curl` (for HTTP actions only)
- Standard POSIX utilities: `sed`, `mktemp`, `cat`, `printf`, `sleep`
**NOT Required:**
- `jq` - Eliminated (was used for JSON parsing)
- `yq` - Never used
- Python - Not used in core pack actions
- Node.js - Not used in core pack actions
- bash - Scripts are POSIX-compliant
- Any other external tools or libraries
This makes the core pack **maximally portable** and suitable for minimal containers (Alpine, distroless, etc.).
## Security Benefits
1. **No process exposure** - Parameters never appear in `ps`, `/proc/<pid>/environ`
2. **Secure by default** - All actions use stdin, no special configuration needed
3. **Clear separation** - Action parameters vs. environment configuration
4. **Audit friendly** - All sensitive data flows through stdin, not environment
5. **Minimal attack surface** - No external dependencies to exploit
## Best Practices
### Parameters
1. **Always use stdin** for action parameters
2. **Use jq for bash** scripts to parse JSON
3. **Handle null values** - Use jq's `// "default"` operator to provide defaults
4. **Provide sensible defaults** - Use empty string, 0, false, or empty array/object as appropriate
5. **Validate required params** - Exit with error if required parameters are missing (when truly required)
6. **Mark secrets** - Use `secret: true` in YAML for sensitive parameters
7. **Never use env vars for parameters** - Parameters come from stdin, not environment
1. **Always use stdin with DOTENV format** for action parameters
2. **Handle quoted values** - Remove both single and double quotes
3. **Provide sensible defaults** - Use empty string, 0, false as appropriate
4. **Validate required params** - Exit with error if truly required parameters missing
5. **Mark secrets** - Use `secret: true` in YAML for sensitive parameters
6. **Never use env vars for parameters** - Parameters come from stdin only
### Environment Variables
1. **Use standard ATTUNE_* variables** - Worker provides execution context
2. **Access API with ATTUNE_API_TOKEN** - Execution-scoped authentication
3. **Log with context** - Include `ATTUNE_ACTION` and `ATTUNE_EXEC_ID` in logs
4. **Custom env vars via execution.env_vars** - For debug flags and configuration only
5. **Never log ATTUNE_API_TOKEN** - Security sensitive
6. **Check ATTUNE_RULE/ATTUNE_TRIGGER** - Conditional behavior for automated vs manual
7. **Use env vars for runtime context** - Not for user data or parameters
4. **Never log ATTUNE_API_TOKEN** - Security sensitive
5. **Use env vars for runtime config only** - Not for user data or parameters
### Output Format
1. **Specify output_format** - Always set to "text", "json", or "yaml"
2. **Use text for simple output** - Messages, logs, unstructured data
3. **Use json for structured data** - API responses, complex results
4. **Use yaml for readable config** - Human-readable structured output
5. **Define schema for structured output** - Only for json/yaml formats
6. **Don't include execution metadata** - No stdout/stderr/exit_code in schema
7. **Use stderr for errors** - Diagnostic messages go to stderr, not stdout
8. **Return proper exit codes** - 0 for success, non-zero for failure
4. **Define schema for structured output** - Only for json/yaml formats
5. **Use stderr for diagnostics** - Error messages go to stderr, not stdout
6. **Return proper exit codes** - 0 for success, non-zero for failure
## Dependencies
All core pack actions have **zero runtime dependencies**:
- **Bash actions**: Require `jq` (for JSON parsing) and `curl` (for HTTP requests)
- Both `jq` and `curl` are standard utilities available in all Attune worker containers
- **No Python, Node.js, or other runtime dependencies required**
### Shell Script Best Practices
1. **Use `#!/bin/sh`** - POSIX shell, not bash
2. **Use `set -e`** - Exit on error
3. **Quote all variables** - `"$var"` not `$var`
4. **Use `case` not `if`** - More portable for pattern matching
5. **Clean up temp files** - Use trap handlers
6. **Avoid bash-isms** - No `[[`, `${var^^}`, `=~`, arrays, etc.
## Execution Metadata (Automatic)
@@ -278,44 +304,66 @@ The following are **automatically captured** by the worker and should **NOT** be
These are execution system concerns, not action output concerns.
## Example: Using Environment Variables and Parameters
## Example: Complete Action
```sh
#!/bin/sh
# Example Action - Core Pack
# Demonstrates DOTENV parameter parsing and environment variable usage
#
# This script uses pure POSIX shell without external dependencies like jq.
```bash
#!/bin/bash
set -e
set -o pipefail
# Standard environment variables (provided by worker)
echo "[$ATTUNE_ACTION] [Exec: $ATTUNE_EXEC_ID] Starting execution" >&2
# Log execution start
echo "[$ATTUNE_ACTION] [Exec: $ATTUNE_EXEC_ID] Starting" >&2
# Read action parameters from stdin
INPUT=$(cat)
URL=$(echo "$INPUT" | jq -r '.url // ""')
# Initialize variables
url=""
timeout="30"
if [ -z "$URL" ]; then
echo "ERROR: url parameter is required" >&2
# Read DOTENV parameters
while IFS= read -r line; do
case "$line" in
*"---ATTUNE_PARAMS_END---"*) break ;;
esac
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
case "$value" in
\"*\") value="${value#\"}"; value="${value%\"}" ;;
esac
case "$key" in
url) url="$value" ;;
timeout) timeout="$value" ;;
esac
done
# Validate
if [ -z "$url" ]; then
echo "ERROR: url is required" >&2
exit 1
fi
# Log execution context
if [ -n "$ATTUNE_RULE" ]; then
echo "Triggered by rule: $ATTUNE_RULE" >&2
fi
# Execute
echo "Fetching: $url" >&2
result=$(curl -s --max-time "$timeout" "$url")
# Make request
RESPONSE=$(curl -s "$URL")
# Output
echo "$result"
# Output result
echo "$RESPONSE"
echo "[$ATTUNE_ACTION] [Exec: $ATTUNE_EXEC_ID] Completed successfully" >&2
echo "[$ATTUNE_ACTION] [Exec: $ATTUNE_EXEC_ID] Completed" >&2
exit 0
```
## Future Considerations
## Further Documentation
- Consider adding a bash library for common parameter parsing patterns
- Add parameter validation helpers
- Create templates for new actions in different languages
- Add output schema validation tooling
- Add helper functions for API interaction using ATTUNE_API_TOKEN
- **Pattern Reference:** `docs/QUICKREF-dotenv-shell-actions.md`
- **Pack Structure:** `docs/pack-structure.md`
- **Example Actions:**
- `echo.sh` - Simplest reference implementation
- `http_request.sh` - Complex action with full HTTP client
- `register_packs.sh` - API wrapper with JSON construction

245
packs/core/actions/build_pack_envs.sh Normal file → Executable file
View File

@@ -1,83 +1,202 @@
#!/bin/bash
# Build Pack Environments Action - API Wrapper
# Thin wrapper around POST /api/v1/packs/build-envs
#!/bin/sh
# Build Pack Environments Action - Core Pack
# API Wrapper for POST /api/v1/packs/build-envs
#
# 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
pack_paths=""
packs_base_dir="/opt/attune/packs"
python_version="3.11"
nodejs_version="20"
skip_python="false"
skip_nodejs="false"
force_rebuild="false"
timeout="600"
api_url="http://localhost:8080"
api_token=""
# Parse parameters using jq
PACK_PATHS=$(echo "$INPUT" | jq -c '.pack_paths // []')
PACKS_BASE_DIR=$(echo "$INPUT" | jq -r '.packs_base_dir // "/opt/attune/packs"')
PYTHON_VERSION=$(echo "$INPUT" | jq -r '.python_version // "3.11"')
NODEJS_VERSION=$(echo "$INPUT" | jq -r '.nodejs_version // "20"')
SKIP_PYTHON=$(echo "$INPUT" | jq -r '.skip_python // false')
SKIP_NODEJS=$(echo "$INPUT" | jq -r '.skip_nodejs // false')
FORCE_REBUILD=$(echo "$INPUT" | jq -r '.force_rebuild // false')
TIMEOUT=$(echo "$INPUT" | jq -r '.timeout // 600')
API_URL=$(echo "$INPUT" | jq -r '.api_url // "http://localhost:8080"')
API_TOKEN=$(echo "$INPUT" | jq -r '.api_token // ""')
# Read DOTENV-formatted parameters from stdin until delimiter
while IFS= read -r line; do
# Check for parameter delimiter
case "$line" in
*"---ATTUNE_PARAMS_END---"*)
break
;;
esac
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
# Remove quotes if present (both single and double)
case "$value" in
\"*\")
value="${value#\"}"
value="${value%\"}"
;;
\'*\')
value="${value#\'}"
value="${value%\'}"
;;
esac
# Process parameters
case "$key" in
pack_paths)
pack_paths="$value"
;;
packs_base_dir)
packs_base_dir="$value"
;;
python_version)
python_version="$value"
;;
nodejs_version)
nodejs_version="$value"
;;
skip_python)
skip_python="$value"
;;
skip_nodejs)
skip_nodejs="$value"
;;
force_rebuild)
force_rebuild="$value"
;;
timeout)
timeout="$value"
;;
api_url)
api_url="$value"
;;
api_token)
api_token="$value"
;;
esac
done
# Validate required parameters
PACK_COUNT=$(echo "$PACK_PATHS" | jq -r 'length' 2>/dev/null || echo "0")
if [[ "$PACK_COUNT" -eq 0 ]]; then
echo '{"built_environments":[],"failed_environments":[],"summary":{"total_packs":0,"success_count":0,"failure_count":0,"python_envs_built":0,"nodejs_envs_built":0,"total_duration_ms":0}}' >&1
if [ -z "$pack_paths" ]; then
printf '{"built_environments":[],"failed_environments":[],"summary":{"total_packs":0,"success_count":0,"failure_count":0,"python_envs_built":0,"nodejs_envs_built":0,"total_duration_ms":0}}\n'
exit 1
fi
# Build request body
REQUEST_BODY=$(jq -n \
--argjson pack_paths "$PACK_PATHS" \
--arg packs_base_dir "$PACKS_BASE_DIR" \
--arg python_version "$PYTHON_VERSION" \
--arg nodejs_version "$NODEJS_VERSION" \
--argjson skip_python "$([[ "$SKIP_PYTHON" == "true" ]] && echo true || echo false)" \
--argjson skip_nodejs "$([[ "$SKIP_NODEJS" == "true" ]] && echo true || echo false)" \
--argjson force_rebuild "$([[ "$FORCE_REBUILD" == "true" ]] && echo true || echo false)" \
--argjson timeout "$TIMEOUT" \
'{
pack_paths: $pack_paths,
packs_base_dir: $packs_base_dir,
python_version: $python_version,
nodejs_version: $nodejs_version,
skip_python: $skip_python,
skip_nodejs: $skip_nodejs,
force_rebuild: $force_rebuild,
timeout: $timeout
}')
# Normalize booleans
case "$skip_python" in
true|True|TRUE|yes|Yes|YES|1) skip_python="true" ;;
*) skip_python="false" ;;
esac
# Make API call
CURL_ARGS=(
-X POST
-H "Content-Type: application/json"
-H "Accept: application/json"
-d "$REQUEST_BODY"
-s
-w "\n%{http_code}"
--max-time $((TIMEOUT + 30))
--connect-timeout 10
case "$skip_nodejs" in
true|True|TRUE|yes|Yes|YES|1) skip_nodejs="true" ;;
*) skip_nodejs="false" ;;
esac
case "$force_rebuild" in
true|True|TRUE|yes|Yes|YES|1) force_rebuild="true" ;;
*) force_rebuild="false" ;;
esac
# Validate timeout is numeric
case "$timeout" in
''|*[!0-9]*)
timeout="600"
;;
esac
# Escape values for JSON
pack_paths_escaped=$(printf '%s' "$pack_paths" | sed 's/\\/\\\\/g; s/"/\\"/g')
packs_base_dir_escaped=$(printf '%s' "$packs_base_dir" | sed 's/\\/\\\\/g; s/"/\\"/g')
python_version_escaped=$(printf '%s' "$python_version" | sed 's/\\/\\\\/g; s/"/\\"/g')
nodejs_version_escaped=$(printf '%s' "$nodejs_version" | sed 's/\\/\\\\/g; s/"/\\"/g')
# Build JSON request body
request_body=$(cat <<EOF
{
"pack_paths": $pack_paths_escaped,
"packs_base_dir": "$packs_base_dir_escaped",
"python_version": "$python_version_escaped",
"nodejs_version": "$nodejs_version_escaped",
"skip_python": $skip_python,
"skip_nodejs": $skip_nodejs,
"force_rebuild": $force_rebuild,
"timeout": $timeout
}
EOF
)
if [[ -n "$API_TOKEN" ]] && [[ "$API_TOKEN" != "null" ]]; then
CURL_ARGS+=(-H "Authorization: Bearer ${API_TOKEN}")
fi
# Create temp files for curl
temp_response=$(mktemp)
temp_headers=$(mktemp)
RESPONSE=$(curl "${CURL_ARGS[@]}" "${API_URL}/api/v1/packs/build-envs" 2>/dev/null || echo -e "\n000")
cleanup() {
rm -f "$temp_response" "$temp_headers"
}
trap cleanup EXIT
# Extract status code (last line)
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | head -n -1)
# Calculate curl timeout (request timeout + buffer)
curl_timeout=$((timeout + 30))
# Make API call
http_code=$(curl -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
${api_token:+-H "Authorization: Bearer ${api_token}"} \
-d "$request_body" \
-s \
-w "%{http_code}" \
-o "$temp_response" \
--max-time "$curl_timeout" \
--connect-timeout 10 \
"${api_url}/api/v1/packs/build-envs" 2>/dev/null || echo "000")
# Check HTTP status
if [[ "$HTTP_CODE" -ge 200 ]] && [[ "$HTTP_CODE" -lt 300 ]]; then
# Extract data field from API response
echo "$BODY" | jq -r '.data // .'
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
# Success - extract data field from API response
response_body=$(cat "$temp_response")
# Try to extract .data field using simple text processing
# If response contains "data" field, extract it; otherwise use whole response
case "$response_body" in
*'"data":'*)
# Extract content after "data": up to the closing brace
# This is a simple extraction - assumes well-formed JSON
data_content=$(printf '%s' "$response_body" | sed -n 's/.*"data":\s*\(.*\)}/\1/p')
if [ -n "$data_content" ]; then
printf '%s\n' "$data_content"
else
cat "$temp_response"
fi
;;
*)
cat "$temp_response"
;;
esac
exit 0
else
# Error response
ERROR_MSG=$(echo "$BODY" | jq -r '.error // .message // "API request failed"' 2>/dev/null || echo "API request failed")
# Error response - try to extract error message
error_msg="API request failed"
if [ -s "$temp_response" ]; then
# Try to extract error or message field
response_content=$(cat "$temp_response")
case "$response_content" in
*'"error":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"error":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
*'"message":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"message":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
esac
fi
# Escape error message for JSON
error_msg_escaped=$(printf '%s' "$error_msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
cat <<EOF
{
@@ -86,7 +205,7 @@ else
"pack_ref": "api",
"pack_path": "",
"runtime": "unknown",
"error": "API call failed (HTTP $HTTP_CODE): $ERROR_MSG"
"error": "API call failed (HTTP $http_code): $error_msg_escaped"
}],
"summary": {
"total_packs": 0,

View File

@@ -10,7 +10,7 @@ entry_point: build_pack_envs.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
parameter_delivery: stdin
parameter_format: json
parameter_format: dotenv
# Output format: json (structured data parsing enabled)
output_format: json

229
packs/core/actions/download_packs.sh Normal file → Executable file
View File

@@ -1,81 +1,202 @@
#!/bin/bash
# Download Packs Action - API Wrapper
# Thin wrapper around POST /api/v1/packs/download
#!/bin/sh
# Download Packs Action - Core Pack
# API Wrapper for POST /api/v1/packs/download
#
# 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
packs=""
destination_dir=""
registry_url="https://registry.attune.io/index.json"
ref_spec=""
timeout="300"
verify_ssl="true"
api_url="http://localhost:8080"
api_token=""
# Parse parameters using jq
PACKS=$(echo "$INPUT" | jq -c '.packs // []')
DESTINATION_DIR=$(echo "$INPUT" | jq -r '.destination_dir // ""')
REGISTRY_URL=$(echo "$INPUT" | jq -r '.registry_url // "https://registry.attune.io/index.json"')
REF_SPEC=$(echo "$INPUT" | jq -r '.ref_spec // ""')
TIMEOUT=$(echo "$INPUT" | jq -r '.timeout // 300')
VERIFY_SSL=$(echo "$INPUT" | jq -r '.verify_ssl // true')
API_URL=$(echo "$INPUT" | jq -r '.api_url // "http://localhost:8080"')
API_TOKEN=$(echo "$INPUT" | jq -r '.api_token // ""')
# Read DOTENV-formatted parameters from stdin until delimiter
while IFS= read -r line; do
# Check for parameter delimiter
case "$line" in
*"---ATTUNE_PARAMS_END---"*)
break
;;
esac
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
# Remove quotes if present (both single and double)
case "$value" in
\"*\")
value="${value#\"}"
value="${value%\"}"
;;
\'*\')
value="${value#\'}"
value="${value%\'}"
;;
esac
# Process parameters
case "$key" in
packs)
packs="$value"
;;
destination_dir)
destination_dir="$value"
;;
registry_url)
registry_url="$value"
;;
ref_spec)
ref_spec="$value"
;;
timeout)
timeout="$value"
;;
verify_ssl)
verify_ssl="$value"
;;
api_url)
api_url="$value"
;;
api_token)
api_token="$value"
;;
esac
done
# Validate required parameters
if [[ -z "$DESTINATION_DIR" ]] || [[ "$DESTINATION_DIR" == "null" ]]; then
echo '{"downloaded_packs":[],"failed_packs":[{"source":"input","error":"destination_dir is required"}],"total_count":0,"success_count":0,"failure_count":1}' >&1
if [ -z "$destination_dir" ]; then
printf '{"downloaded_packs":[],"failed_packs":[{"source":"input","error":"destination_dir is required"}],"total_count":0,"success_count":0,"failure_count":1}\n'
exit 1
fi
# Build request body
REQUEST_BODY=$(jq -n \
--argjson packs "$PACKS" \
--arg destination_dir "$DESTINATION_DIR" \
--arg registry_url "$REGISTRY_URL" \
--argjson timeout "$TIMEOUT" \
--argjson verify_ssl "$([[ "$VERIFY_SSL" == "true" ]] && echo true || echo false)" \
'{
packs: $packs,
destination_dir: $destination_dir,
registry_url: $registry_url,
timeout: $timeout,
verify_ssl: $verify_ssl
}' | jq --arg ref_spec "$REF_SPEC" 'if $ref_spec != "" and $ref_spec != "null" then .ref_spec = $ref_spec else . end')
# Normalize boolean
case "$verify_ssl" in
true|True|TRUE|yes|Yes|YES|1) verify_ssl="true" ;;
*) verify_ssl="false" ;;
esac
# Make API call
CURL_ARGS=(
-X POST
-H "Content-Type: application/json"
-H "Accept: application/json"
-d "$REQUEST_BODY"
-s
-w "\n%{http_code}"
--max-time $((TIMEOUT + 30))
--connect-timeout 10
# Validate timeout is numeric
case "$timeout" in
''|*[!0-9]*)
timeout="300"
;;
esac
# Escape values for JSON
packs_escaped=$(printf '%s' "$packs" | sed 's/\\/\\\\/g; s/"/\\"/g')
destination_dir_escaped=$(printf '%s' "$destination_dir" | sed 's/\\/\\\\/g; s/"/\\"/g')
registry_url_escaped=$(printf '%s' "$registry_url" | sed 's/\\/\\\\/g; s/"/\\"/g')
# Build JSON request body
if [ -n "$ref_spec" ]; then
ref_spec_escaped=$(printf '%s' "$ref_spec" | sed 's/\\/\\\\/g; s/"/\\"/g')
request_body=$(cat <<EOF
{
"packs": $packs_escaped,
"destination_dir": "$destination_dir_escaped",
"registry_url": "$registry_url_escaped",
"ref_spec": "$ref_spec_escaped",
"timeout": $timeout,
"verify_ssl": $verify_ssl
}
EOF
)
else
request_body=$(cat <<EOF
{
"packs": $packs_escaped,
"destination_dir": "$destination_dir_escaped",
"registry_url": "$registry_url_escaped",
"timeout": $timeout,
"verify_ssl": $verify_ssl
}
EOF
)
if [[ -n "$API_TOKEN" ]] && [[ "$API_TOKEN" != "null" ]]; then
CURL_ARGS+=(-H "Authorization: Bearer ${API_TOKEN}")
fi
RESPONSE=$(curl "${CURL_ARGS[@]}" "${API_URL}/api/v1/packs/download" 2>/dev/null || echo -e "\n000")
# Create temp files for curl
temp_response=$(mktemp)
temp_headers=$(mktemp)
# Extract status code (last line)
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | head -n -1)
cleanup() {
rm -f "$temp_response" "$temp_headers"
}
trap cleanup EXIT
# Calculate curl timeout (request timeout + buffer)
curl_timeout=$((timeout + 30))
# Make API call
http_code=$(curl -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
${api_token:+-H "Authorization: Bearer ${api_token}"} \
-d "$request_body" \
-s \
-w "%{http_code}" \
-o "$temp_response" \
--max-time "$curl_timeout" \
--connect-timeout 10 \
"${api_url}/api/v1/packs/download" 2>/dev/null || echo "000")
# Check HTTP status
if [[ "$HTTP_CODE" -ge 200 ]] && [[ "$HTTP_CODE" -lt 300 ]]; then
# Extract data field from API response
echo "$BODY" | jq -r '.data // .'
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
# Success - extract data field from API response
response_body=$(cat "$temp_response")
# Try to extract .data field using simple text processing
# If response contains "data" field, extract it; otherwise use whole response
case "$response_body" in
*'"data":'*)
# Extract content after "data": up to the closing brace
# This is a simple extraction - assumes well-formed JSON
data_content=$(printf '%s' "$response_body" | sed -n 's/.*"data":\s*\(.*\)}/\1/p')
if [ -n "$data_content" ]; then
printf '%s\n' "$data_content"
else
cat "$temp_response"
fi
;;
*)
cat "$temp_response"
;;
esac
exit 0
else
# Error response
ERROR_MSG=$(echo "$BODY" | jq -r '.error // .message // "API request failed"' 2>/dev/null || echo "API request failed")
# Error response - try to extract error message
error_msg="API request failed"
if [ -s "$temp_response" ]; then
# Try to extract error or message field
response_content=$(cat "$temp_response")
case "$response_content" in
*'"error":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"error":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
*'"message":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"message":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
esac
fi
# Escape error message for JSON
error_msg_escaped=$(printf '%s' "$error_msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
cat <<EOF
{
"downloaded_packs": [],
"failed_packs": [{
"source": "api",
"error": "API call failed (HTTP $HTTP_CODE): $ERROR_MSG"
"error": "API call failed (HTTP $http_code): $error_msg_escaped"
}],
"total_count": 0,
"success_count": 0,

View File

@@ -10,7 +10,7 @@ entry_point: download_packs.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
parameter_delivery: stdin
parameter_format: json
parameter_format: dotenv
# Output format: json (structured data parsing enabled)
output_format: json

View File

@@ -36,7 +36,7 @@ while IFS= read -r line; do
done
# Echo the message (even if empty)
echo "$message"
echo -n "$message"
# Exit successfully
exit 0

173
packs/core/actions/get_pack_dependencies.sh Normal file → Executable file
View File

@@ -1,65 +1,148 @@
#!/bin/bash
# Get Pack Dependencies Action - API Wrapper
# Thin wrapper around POST /api/v1/packs/dependencies
#!/bin/sh
# Get Pack Dependencies Action - Core Pack
# API Wrapper for POST /api/v1/packs/dependencies
#
# 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
pack_paths=""
skip_validation="false"
api_url="http://localhost:8080"
api_token=""
# Parse parameters using jq
PACK_PATHS=$(echo "$INPUT" | jq -c '.pack_paths // []')
SKIP_VALIDATION=$(echo "$INPUT" | jq -r '.skip_validation // false')
API_URL=$(echo "$INPUT" | jq -r '.api_url // "http://localhost:8080"')
API_TOKEN=$(echo "$INPUT" | jq -r '.api_token // ""')
# Read DOTENV-formatted parameters from stdin until delimiter
while IFS= read -r line; do
# Check for parameter delimiter
case "$line" in
*"---ATTUNE_PARAMS_END---"*)
break
;;
esac
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
# Remove quotes if present (both single and double)
case "$value" in
\"*\")
value="${value#\"}"
value="${value%\"}"
;;
\'*\')
value="${value#\'}"
value="${value%\'}"
;;
esac
# Process parameters
case "$key" in
pack_paths)
pack_paths="$value"
;;
skip_validation)
skip_validation="$value"
;;
api_url)
api_url="$value"
;;
api_token)
api_token="$value"
;;
esac
done
# Validate required parameters
PACK_COUNT=$(echo "$PACK_PATHS" | jq -r 'length' 2>/dev/null || echo "0")
if [[ "$PACK_COUNT" -eq 0 ]]; then
echo '{"dependencies":[],"runtime_requirements":{},"missing_dependencies":[],"analyzed_packs":[],"errors":[{"pack_path":"input","error":"No pack paths provided"}]}' >&1
if [ -z "$pack_paths" ]; then
printf '{"dependencies":[],"runtime_requirements":{},"missing_dependencies":[],"analyzed_packs":[],"errors":[{"pack_path":"input","error":"No pack paths provided"}]}\n'
exit 1
fi
# Build request body
REQUEST_BODY=$(jq -n \
--argjson pack_paths "$PACK_PATHS" \
--argjson skip_validation "$([[ "$SKIP_VALIDATION" == "true" ]] && echo true || echo false)" \
'{
pack_paths: $pack_paths,
skip_validation: $skip_validation
}')
# Normalize boolean
case "$skip_validation" in
true|True|TRUE|yes|Yes|YES|1) skip_validation="true" ;;
*) skip_validation="false" ;;
esac
# Make API call
CURL_ARGS=(
-X POST
-H "Content-Type: application/json"
-H "Accept: application/json"
-d "$REQUEST_BODY"
-s
-w "\n%{http_code}"
--max-time 60
--connect-timeout 10
# Build JSON request body (escape pack_paths value for JSON)
pack_paths_escaped=$(printf '%s' "$pack_paths" | sed 's/\\/\\\\/g; s/"/\\"/g')
request_body=$(cat <<EOF
{
"pack_paths": $pack_paths_escaped,
"skip_validation": $skip_validation
}
EOF
)
if [[ -n "$API_TOKEN" ]] && [[ "$API_TOKEN" != "null" ]]; then
CURL_ARGS+=(-H "Authorization: Bearer ${API_TOKEN}")
fi
# Create temp files for curl
temp_response=$(mktemp)
temp_headers=$(mktemp)
RESPONSE=$(curl "${CURL_ARGS[@]}" "${API_URL}/api/v1/packs/dependencies" 2>/dev/null || echo -e "\n000")
cleanup() {
rm -f "$temp_response" "$temp_headers"
}
trap cleanup EXIT
# Extract status code (last line)
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | head -n -1)
# Make API call
http_code=$(curl -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
${api_token:+-H "Authorization: Bearer ${api_token}"} \
-d "$request_body" \
-s \
-w "%{http_code}" \
-o "$temp_response" \
--max-time 60 \
--connect-timeout 10 \
"${api_url}/api/v1/packs/dependencies" 2>/dev/null || echo "000")
# Check HTTP status
if [[ "$HTTP_CODE" -ge 200 ]] && [[ "$HTTP_CODE" -lt 300 ]]; then
# Extract data field from API response
echo "$BODY" | jq -r '.data // .'
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
# Success - extract data field from API response
response_body=$(cat "$temp_response")
# Try to extract .data field using simple text processing
# If response contains "data" field, extract it; otherwise use whole response
case "$response_body" in
*'"data":'*)
# Extract content after "data": up to the closing brace
# This is a simple extraction - assumes well-formed JSON
data_content=$(printf '%s' "$response_body" | sed -n 's/.*"data":\s*\(.*\)}/\1/p')
if [ -n "$data_content" ]; then
printf '%s\n' "$data_content"
else
cat "$temp_response"
fi
;;
*)
cat "$temp_response"
;;
esac
exit 0
else
# Error response
ERROR_MSG=$(echo "$BODY" | jq -r '.error // .message // "API request failed"' 2>/dev/null || echo "API request failed")
# Error response - try to extract error message
error_msg="API request failed"
if [ -s "$temp_response" ]; then
# Try to extract error or message field
response_content=$(cat "$temp_response")
case "$response_content" in
*'"error":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"error":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
*'"message":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"message":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
esac
fi
# Escape error message for JSON
error_msg_escaped=$(printf '%s' "$error_msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
cat <<EOF
{
@@ -69,7 +152,7 @@ else
"analyzed_packs": [],
"errors": [{
"pack_path": "api",
"error": "API call failed (HTTP $HTTP_CODE): $ERROR_MSG"
"error": "API call failed (HTTP $http_code): $error_msg_escaped"
}]
}
EOF

View File

@@ -10,7 +10,7 @@ entry_point: get_pack_dependencies.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
parameter_delivery: stdin
parameter_format: json
parameter_format: dotenv
# Output format: json (structured data parsing enabled)
output_format: json

209
packs/core/actions/register_packs.sh Normal file → Executable file
View File

@@ -1,74 +1,175 @@
#!/bin/bash
# Register Packs Action - API Wrapper
# Thin wrapper around POST /api/v1/packs/register-batch
#!/bin/sh
# Register Packs Action - Core Pack
# API Wrapper for POST /api/v1/packs/register-batch
#
# 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
pack_paths=""
packs_base_dir="/opt/attune/packs"
skip_validation="false"
skip_tests="false"
force="false"
api_url="http://localhost:8080"
api_token=""
# Parse parameters using jq
PACK_PATHS=$(echo "$INPUT" | jq -c '.pack_paths // []')
PACKS_BASE_DIR=$(echo "$INPUT" | jq -r '.packs_base_dir // "/opt/attune/packs"')
SKIP_VALIDATION=$(echo "$INPUT" | jq -r '.skip_validation // false')
SKIP_TESTS=$(echo "$INPUT" | jq -r '.skip_tests // false')
FORCE=$(echo "$INPUT" | jq -r '.force // false')
API_URL=$(echo "$INPUT" | jq -r '.api_url // "http://localhost:8080"')
API_TOKEN=$(echo "$INPUT" | jq -r '.api_token // ""')
# Read DOTENV-formatted parameters from stdin until delimiter
while IFS= read -r line; do
# Check for parameter delimiter
case "$line" in
*"---ATTUNE_PARAMS_END---"*)
break
;;
esac
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
# Remove quotes if present (both single and double)
case "$value" in
\"*\")
value="${value#\"}"
value="${value%\"}"
;;
\'*\')
value="${value#\'}"
value="${value%\'}"
;;
esac
# Process parameters
case "$key" in
pack_paths)
pack_paths="$value"
;;
packs_base_dir)
packs_base_dir="$value"
;;
skip_validation)
skip_validation="$value"
;;
skip_tests)
skip_tests="$value"
;;
force)
force="$value"
;;
api_url)
api_url="$value"
;;
api_token)
api_token="$value"
;;
esac
done
# Validate required parameters
PACK_COUNT=$(echo "$PACK_PATHS" | jq -r 'length' 2>/dev/null || echo "0")
if [[ "$PACK_COUNT" -eq 0 ]]; then
echo '{"registered_packs":[],"failed_packs":[{"pack_ref":"input","pack_path":"","error":"No pack paths provided","error_stage":"input_validation"}],"summary":{"total_packs":0,"success_count":0,"failure_count":1,"total_components":0,"duration_ms":0}}' >&1
if [ -z "$pack_paths" ]; then
printf '{"registered_packs":[],"failed_packs":[{"pack_ref":"input","pack_path":"","error":"No pack paths provided","error_stage":"input_validation"}],"summary":{"total_packs":0,"success_count":0,"failure_count":1,"total_components":0,"duration_ms":0}}\n'
exit 1
fi
# Build request body
REQUEST_BODY=$(jq -n \
--argjson pack_paths "$PACK_PATHS" \
--arg packs_base_dir "$PACKS_BASE_DIR" \
--argjson skip_validation "$([[ "$SKIP_VALIDATION" == "true" ]] && echo true || echo false)" \
--argjson skip_tests "$([[ "$SKIP_TESTS" == "true" ]] && echo true || echo false)" \
--argjson force "$([[ "$FORCE" == "true" ]] && echo true || echo false)" \
'{
pack_paths: $pack_paths,
packs_base_dir: $packs_base_dir,
skip_validation: $skip_validation,
skip_tests: $skip_tests,
force: $force
}')
# Normalize booleans
case "$skip_validation" in
true|True|TRUE|yes|Yes|YES|1) skip_validation="true" ;;
*) skip_validation="false" ;;
esac
# Make API call
CURL_ARGS=(
-X POST
-H "Content-Type: application/json"
-H "Accept: application/json"
-d "$REQUEST_BODY"
-s
-w "\n%{http_code}"
--max-time 300
--connect-timeout 10
case "$skip_tests" in
true|True|TRUE|yes|Yes|YES|1) skip_tests="true" ;;
*) skip_tests="false" ;;
esac
case "$force" in
true|True|TRUE|yes|Yes|YES|1) force="true" ;;
*) force="false" ;;
esac
# Escape values for JSON
pack_paths_escaped=$(printf '%s' "$pack_paths" | sed 's/\\/\\\\/g; s/"/\\"/g')
packs_base_dir_escaped=$(printf '%s' "$packs_base_dir" | sed 's/\\/\\\\/g; s/"/\\"/g')
# Build JSON request body
request_body=$(cat <<EOF
{
"pack_paths": $pack_paths_escaped,
"packs_base_dir": "$packs_base_dir_escaped",
"skip_validation": $skip_validation,
"skip_tests": $skip_tests,
"force": $force
}
EOF
)
if [[ -n "$API_TOKEN" ]] && [[ "$API_TOKEN" != "null" ]]; then
CURL_ARGS+=(-H "Authorization: Bearer ${API_TOKEN}")
fi
# Create temp files for curl
temp_response=$(mktemp)
temp_headers=$(mktemp)
RESPONSE=$(curl "${CURL_ARGS[@]}" "${API_URL}/api/v1/packs/register-batch" 2>/dev/null || echo -e "\n000")
cleanup() {
rm -f "$temp_response" "$temp_headers"
}
trap cleanup EXIT
# Extract status code (last line)
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | head -n -1)
# Make API call
http_code=$(curl -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
${api_token:+-H "Authorization: Bearer ${api_token}"} \
-d "$request_body" \
-s \
-w "%{http_code}" \
-o "$temp_response" \
--max-time 300 \
--connect-timeout 10 \
"${api_url}/api/v1/packs/register-batch" 2>/dev/null || echo "000")
# Check HTTP status
if [[ "$HTTP_CODE" -ge 200 ]] && [[ "$HTTP_CODE" -lt 300 ]]; then
# Extract data field from API response
echo "$BODY" | jq -r '.data // .'
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
# Success - extract data field from API response
response_body=$(cat "$temp_response")
# Try to extract .data field using simple text processing
# If response contains "data" field, extract it; otherwise use whole response
case "$response_body" in
*'"data":'*)
# Extract content after "data": up to the closing brace
# This is a simple extraction - assumes well-formed JSON
data_content=$(printf '%s' "$response_body" | sed -n 's/.*"data":\s*\(.*\)}/\1/p')
if [ -n "$data_content" ]; then
printf '%s\n' "$data_content"
else
cat "$temp_response"
fi
;;
*)
cat "$temp_response"
;;
esac
exit 0
else
# Error response
ERROR_MSG=$(echo "$BODY" | jq -r '.error // .message // "API request failed"' 2>/dev/null || echo "API request failed")
# Error response - try to extract error message
error_msg="API request failed"
if [ -s "$temp_response" ]; then
# Try to extract error or message field
response_content=$(cat "$temp_response")
case "$response_content" in
*'"error":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"error":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
*'"message":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"message":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
esac
fi
# Escape error message for JSON
error_msg_escaped=$(printf '%s' "$error_msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
cat <<EOF
{
@@ -76,7 +177,7 @@ else
"failed_packs": [{
"pack_ref": "api",
"pack_path": "",
"error": "API call failed (HTTP $HTTP_CODE): $ERROR_MSG",
"error": "API call failed (HTTP $http_code): $error_msg_escaped",
"error_stage": "api_call"
}],
"summary": {

View File

@@ -10,7 +10,7 @@ entry_point: register_packs.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
parameter_delivery: stdin
parameter_format: json
parameter_format: dotenv
# Output format: json (structured data parsing enabled)
output_format: json