[wip] cli capability parity
Some checks failed
CI / Rustfmt (push) Successful in 23s
CI / Cargo Audit & Deny (push) Successful in 30s
CI / Web Blocking Checks (push) Successful in 48s
CI / Security Blocking Checks (push) Successful in 8s
CI / Clippy (push) Failing after 1m55s
CI / Web Advisory Checks (push) Successful in 35s
CI / Security Advisory Checks (push) Successful in 37s
CI / Tests (push) Successful in 8m5s

This commit is contained in:
2026-03-06 16:58:50 -06:00
parent 48b6ca6bd7
commit 87d830f952
94 changed files with 3694 additions and 734 deletions

View File

@@ -5,7 +5,7 @@
## Core Principles
1. **Use POSIX shell** (`#!/bin/sh`), not bash
2. **Read parameters in DOTENV format** from stdin
2. **Read parameters in DOTENV format** from stdin until EOF
3. **No external JSON parsers** (jq, yq, etc.)
4. **Minimal dependencies** (only POSIX utilities + curl)
@@ -17,7 +17,7 @@
# Brief description of what this action does
#
# This script uses pure POSIX shell without external dependencies like jq.
# It reads parameters in DOTENV format from stdin until the delimiter.
# It reads parameters in DOTENV format from stdin until EOF.
set -e
@@ -27,14 +27,8 @@ param2="default_value"
bool_param="false"
numeric_param="0"
# Read DOTENV-formatted parameters from stdin until delimiter
# Read DOTENV-formatted parameters from stdin until EOF
while IFS= read -r line; do
# Check for parameter delimiter
case "$line" in
*"---ATTUNE_PARAMS_END---"*)
break
;;
esac
[ -z "$line" ] && continue
key="${line%%=*}"
@@ -135,12 +129,11 @@ parameters:
### 1. Parameter Parsing
**Read until delimiter:**
**Read until EOF:**
```sh
while IFS= read -r line; do
case "$line" in
*"---ATTUNE_PARAMS_END---"*) break ;;
esac
[ -z "$line" ] && continue
# ... process line
done
```
@@ -303,11 +296,10 @@ runner_type: python # NO! Use shell for core pack
param1="string value"
param2=42
bool_param=true
---ATTUNE_PARAMS_END---
```
**Key Rules:**
- Parameters end with `---ATTUNE_PARAMS_END---` delimiter
- Parameters are delivered via stdin; the script reads until EOF (stdin is closed after delivery)
- Values may be quoted (single or double quotes)
- Empty lines are skipped
- No multiline values (use base64 if needed)

View File

@@ -107,17 +107,15 @@ parameter_format: json
**Reading stdin parameters:**
The worker writes parameters to stdin with a delimiter:
The worker writes a single document to stdin containing all parameters (including secrets merged in), followed by a newline, then closes stdin:
```
<formatted_parameters>
---ATTUNE_PARAMS_END---
<secrets_json>
<formatted_parameters>\n
```
- Parameters come first in your chosen format
- Delimiter `---ATTUNE_PARAMS_END---` separates parameters from secrets
- Secrets follow as JSON (if any)
- Parameters and secrets are merged into a single document
- Secrets are included as top-level keys in the parameters object
- The action reads until EOF (stdin is closed after delivery)
#### 2. **File Delivery**
@@ -174,10 +172,9 @@ COUNT=$(echo "$PARAMS_JSON" | jq -r '.count // 1')
import json
import sys
# Read until delimiter
content = sys.stdin.read()
parts = content.split('---ATTUNE_PARAMS_END---')
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
# Read all parameters from stdin (secrets are merged in)
content = sys.stdin.read().strip()
params = json.loads(content) if content else {}
message = params.get('message', '')
count = params.get('count', 1)
@@ -206,9 +203,9 @@ nested:
import sys
import yaml
content = sys.stdin.read()
parts = content.split('---ATTUNE_PARAMS_END---')
params = yaml.safe_load(parts[0].strip()) if parts[0].strip() else {}
# Read all parameters from stdin (secrets are merged in)
content = sys.stdin.read().strip()
params = yaml.safe_load(content) if content else {}
message = params.get('message', '')
```
@@ -240,7 +237,6 @@ count=""
# Read until delimiter
while IFS= read -r line; do
case "$line" in
*"---ATTUNE_PARAMS_END---"*) break ;;
message=*)
message="${line#message=}"
# Remove quotes
@@ -564,10 +560,9 @@ import json
import sys
def main():
# Read parameters
content = sys.stdin.read()
parts = content.split('---ATTUNE_PARAMS_END---')
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
# Read parameters (secrets are merged into the same document)
content = sys.stdin.read().strip()
params = json.loads(content) if content else {}
# Process
message = params.get('message', '')
@@ -614,7 +609,6 @@ async function main() {
let input = '';
for await (const line of rl) {
if (line.includes('---ATTUNE_PARAMS_END---')) break;
input += line;
}
@@ -815,15 +809,9 @@ import smtplib
from email.mime.text import MIMEText
def read_stdin_params():
"""Read parameters and secrets from stdin."""
content = sys.stdin.read()
parts = content.split('---ATTUNE_PARAMS_END---')
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
secrets = json.loads(parts[1].strip()) if len(parts) > 1 and parts[1].strip() else {}
# Merge secrets into params
return {**params, **secrets}
"""Read parameters from stdin. Secrets are already merged into the parameters."""
content = sys.stdin.read().strip()
return json.loads(content) if content else {}
def main():
try:
@@ -903,10 +891,9 @@ import sys
import time
def main():
# Read parameters
content = sys.stdin.read()
parts = content.split('---ATTUNE_PARAMS_END---')
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
# Read parameters (secrets are merged into the same document)
content = sys.stdin.read().strip()
params = json.loads(content) if content else {}
items = params.get('items', [])
@@ -975,7 +962,6 @@ compress="true"
# Read dotenv format from stdin
while IFS= read -r line; do
case "$line" in
*"---ATTUNE_PARAMS_END---"*) break ;;
source=*)
source="${line#source=}"
source="${source#[\"\']}"
@@ -1060,7 +1046,7 @@ exit 0
**Check:**
- Is `parameter_delivery` set correctly?
- Are you reading from stdin or checking `$ATTUNE_PARAMETER_FILE`?
- Are you reading until the delimiter `---ATTUNE_PARAMS_END---`?
- Are you reading stdin until EOF?
**Debug:**
```bash
@@ -1100,15 +1086,13 @@ sys.stdout.flush() # Ensure output is written immediately
**Check:**
- Are secrets configured for the action?
- Are you reading past the delimiter in stdin?
- Secrets come as JSON after `---ATTUNE_PARAMS_END---`
- Secrets are merged into the parameters document — access them by key name just like regular parameters
**Example:**
```python
content = sys.stdin.read()
parts = content.split('---ATTUNE_PARAMS_END---')
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
secrets = json.loads(parts[1].strip()) if len(parts) > 1 else {}
content = sys.stdin.read().strip()
params = json.loads(content) if content else {}
api_key = params.get('api_key', '') # Secrets are regular keys
```
### Environment variables are missing

View File

@@ -50,11 +50,10 @@ parameter_format: yaml
```
```python
# Read from stdin
# Read from stdin (secrets are merged into parameters)
import sys, json
content = sys.stdin.read()
params_str = content.split('---ATTUNE_PARAMS_END---')[0]
params = json.loads(params_str)
content = sys.stdin.read().strip()
params = json.loads(content) if content else {}
api_key = params['api_key'] # Secure!
```
@@ -119,11 +118,9 @@ import sys
import json
def read_params():
content = sys.stdin.read()
parts = content.split('---ATTUNE_PARAMS_END---')
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
secrets = json.loads(parts[1].strip()) if len(parts) > 1 and parts[1].strip() else {}
return {**params, **secrets}
"""Read parameters from stdin. Secrets are already merged in."""
content = sys.stdin.read().strip()
return json.loads(content) if content else {}
params = read_params()
api_key = params['api_key']
@@ -279,10 +276,10 @@ ps aux | grep attune-worker
- Read from stdin or parameter file
```python
# Read parameters from stdin
# Read parameters from stdin (secrets are merged in)
import sys, json
content = sys.stdin.read()
params = json.loads(content.split('---ATTUNE_PARAMS_END---')[0])
content = sys.stdin.read().strip()
params = json.loads(content) if content else {}
api_key = params['api_key'] # Secure!
```

View File

@@ -57,9 +57,9 @@ parameters:
# my_action.py
import sys, json
# Read from stdin (the default)
content = sys.stdin.read()
params = json.loads(content.split('---ATTUNE_PARAMS_END---')[0])
# Read from stdin (the default) — secrets are merged into parameters
content = sys.stdin.read().strip()
params = json.loads(content) if content else {}
api_key = params['api_key'] # Secure - not in process list!
```

View File

@@ -42,7 +42,7 @@ Environment variables provide execution context and configuration:
**Security**: ✅ **High** - Not visible in process listings
**Use Case**: Sensitive data, structured parameters, credentials
Parameters are serialized in the specified format and passed via stdin. A delimiter `---ATTUNE_PARAMS_END---` separates parameters from secrets.
Parameters are serialized in the specified format and written to stdin as a single document (secrets are merged into the parameters), followed by a newline, then stdin is closed.
**Example** (this is the default):
```yaml
@@ -56,9 +56,7 @@ parameter_format: json
**Stdin content (JSON format)**:
```
{"message":"Hello","count":42,"enabled":true}
---ATTUNE_PARAMS_END---
{"api_key":"secret123","db_password":"pass456"}
{"message":"Hello","count":42,"enabled":true,"api_key":"secret123","db_password":"pass456"}
```
**Python script example**:
@@ -68,23 +66,13 @@ import sys
import json
def read_stdin_params():
"""Read parameters and secrets from stdin."""
content = sys.stdin.read()
parts = content.split('---ATTUNE_PARAMS_END---')
# Parse parameters
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
# Parse secrets (if present)
secrets = {}
if len(parts) > 1 and parts[1].strip():
secrets = json.loads(parts[1].strip())
return params, secrets
"""Read parameters from stdin. Secrets are already merged into parameters."""
content = sys.stdin.read().strip()
return json.loads(content) if content else {}
params, secrets = read_stdin_params()
params = read_stdin_params()
message = params.get('message', 'default')
api_key = secrets.get('api_key')
api_key = params.get('api_key')
print(f"Message: {message}")
```
@@ -373,10 +361,10 @@ If you were previously passing data as environment variables, you now have two o
**Option 1: Move to Parameters** (for action data):
```python
# Read from stdin
# Read from stdin (secrets are merged into parameters)
import sys, json
content = sys.stdin.read()
params = json.loads(content.split('---ATTUNE_PARAMS_END---')[0])
content = sys.stdin.read().strip()
params = json.loads(content) if content else {}
value = params.get('key')
```
@@ -434,16 +422,9 @@ import json
import requests
def read_stdin_params():
"""Read parameters and secrets from stdin."""
content = sys.stdin.read()
parts = content.split('---ATTUNE_PARAMS_END---')
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
secrets = {}
if len(parts) > 1 and parts[1].strip():
secrets = json.loads(parts[1].strip())
return {**params, **secrets}
"""Read parameters from stdin. Secrets are already merged into parameters."""
content = sys.stdin.read().strip()
return json.loads(content) if content else {}
def main():
params = read_stdin_params()

View File

@@ -241,12 +241,9 @@ import json
import sys
def read_stdin_params():
"""Read parameters from stdin."""
content = sys.stdin.read()
parts = content.split('---ATTUNE_PARAMS_END---')
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
secrets = json.loads(parts[1].strip()) if len(parts) > 1 and parts[1].strip() else {}
return {**params, **secrets}
"""Read parameters from stdin. Secrets are already merged into parameters."""
content = sys.stdin.read().strip()
return json.loads(content) if content else {}
def main():
params = read_stdin_params()

View File

@@ -102,11 +102,8 @@ message='It'\''s working!'
```bash
#!/bin/sh
# Read DOTENV-formatted parameters from stdin
# Read DOTENV-formatted parameters from stdin until EOF
while IFS= read -r line; do
case "$line" in
*"---ATTUNE_PARAMS_END---"*) break ;;
esac
[ -z "$line" ] && continue
key="${line%%=*}"
@@ -137,9 +134,6 @@ headers_file=$(mktemp)
query_params_file=$(mktemp)
while IFS= read -r line; do
case "$line" in
*"---ATTUNE_PARAMS_END---"*) break ;;
esac
[ -z "$line" ] && continue
key="${line%%=*}"
@@ -212,14 +206,13 @@ This combination provides several security benefits:
### Secret Handling
Secrets are passed separately via stdin after parameters. They are never included in environment variables or parameter files.
Secrets are merged into the parameters document before delivery. They appear as regular key-value pairs in the DOTENV output. Secrets are never included in environment variables or parameter files.
```bash
# Parameters are sent first
# All parameters (including secrets) delivered as a single document
url='https://api.example.com'
---ATTUNE_PARAMS_END---
# Then secrets (as JSON)
{"api_key":"secret123","password":"hunter2"}
api_key='secret123'
password='hunter2'
```
## Examples
@@ -257,7 +250,6 @@ method='POST'
query_params.limit='10'
query_params.page='1'
url='https://api.example.com/users'
---ATTUNE_PARAMS_END---
```
### Example 2: Simple Shell Action
@@ -281,7 +273,6 @@ parameter_format: dotenv
```bash
greeting='Hello'
name='Alice'
---ATTUNE_PARAMS_END---
```
## Troubleshooting
@@ -290,13 +281,11 @@ name='Alice'
**Symptom:** Action receives empty or incorrect parameter values.
**Solution:** Ensure you're reading until the `---ATTUNE_PARAMS_END---` delimiter:
**Solution:** Ensure you're reading stdin until EOF:
```bash
while IFS= read -r line; do
case "$line" in
*"---ATTUNE_PARAMS_END---"*) break ;; # Important!
esac
[ -z "$line" ] && continue
# ... parse line
done
```