Phase 1: file one Paperclip job from a script
Add scripts/paperclip-roundtrip.sh. It files one Paperclip issue and prints
the returned id and identifier. The key comes from the environment, then from
environment.d. The script never prints the key.
Fix the runbook: the create route is POST /api/companies/{companyId}/issues.
The old POST /api/issues route returns 404.
Record the first filed job: AUD-55 (d58891ce-9934-4710-9bba-d37974251ed9).
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
182
scripts/paperclip-roundtrip.sh
Executable file
182
scripts/paperclip-roundtrip.sh
Executable file
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# paperclip-roundtrip.sh - file one Paperclip job (issue) from the command line.
|
||||
#
|
||||
# This is the outbound half of the Central AI Hub Phase 1 round trip.
|
||||
# It proves that a script can file a job to Paperclip and get an identifier back.
|
||||
# See hub_brief.md (build order step 1) and docs/outline/04-agent-runbook.md.
|
||||
#
|
||||
# The script never prints or commits the API key. It reads the key from the
|
||||
# environment, then from the environment.d secrets file.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/paperclip-roundtrip.sh [options]
|
||||
#
|
||||
# Options:
|
||||
# -t, --title TITLE Job title.
|
||||
# -d, --description TEXT Job description text.
|
||||
# -f, --description-file F Read the description from file F ("-" is stdin).
|
||||
# -a, --assignee AGENT_ID Assign the job to this agent id.
|
||||
# -p, --priority LEVEL backlog|low|medium|high|critical (default: medium).
|
||||
# -P, --project PROJECT_ID Put the job in this project.
|
||||
# --parent ISSUE_ID Make the job a child of this issue id.
|
||||
# --idempotency-key KEY Idempotency key. A retry with the same key is safe.
|
||||
# --json Print the raw API response after the summary.
|
||||
# -h, --help Show this help.
|
||||
#
|
||||
# Environment:
|
||||
# PAPERCLIP_API_KEY Required. Read from the environment first, then from
|
||||
# ${PAPERCLIP_SECRETS_FILE:-~/.config/environment.d/10-secrets.conf}.
|
||||
# PAPERCLIP_BASE_URL Paperclip base URL. Default: http://192.168.20.13:3100
|
||||
# PAPERCLIP_COMPANY_ID Company id. Default: the Hub company.
|
||||
# PAPERCLIP_STATE_DIR Where the filed-issue record is written.
|
||||
# Default: <repo>/.state
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 job filed
|
||||
# 1 usage or configuration error
|
||||
# 2 Paperclip API error
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly PROJECT_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
readonly DEFAULT_BASE_URL="http://192.168.20.13:3100"
|
||||
readonly DEFAULT_COMPANY_ID="d4921013-e5da-4d88-8016-57d5746e2d03"
|
||||
readonly DEFAULT_TITLE="Hub round-trip job: confirm the return path"
|
||||
readonly DEFAULT_DESCRIPTION="Prove the outbound Paperclip path: a script files a job and reads back the identifier."
|
||||
|
||||
BASE_URL="${PAPERCLIP_BASE_URL:-${PAPERCLIP_BOARD_URL:-$DEFAULT_BASE_URL}}"
|
||||
COMPANY_ID="${PAPERCLIP_COMPANY_ID:-$DEFAULT_COMPANY_ID}"
|
||||
SECRETS_FILE="${PAPERCLIP_SECRETS_FILE:-$HOME/.config/environment.d/10-secrets.conf}"
|
||||
STATE_DIR="${PAPERCLIP_STATE_DIR:-$PROJECT_DIR/.state}"
|
||||
|
||||
TITLE="$DEFAULT_TITLE"
|
||||
DESCRIPTION="$DEFAULT_DESCRIPTION"
|
||||
DESCRIPTION_FILE=""
|
||||
ASSIGNEE=""
|
||||
PRIORITY="medium"
|
||||
PROJECT_ID=""
|
||||
PARENT=""
|
||||
IDEMPOTENCY_KEY=""
|
||||
PRINT_JSON=0
|
||||
|
||||
usage() {
|
||||
awk 'NR>2 && /^#/ { sub(/^# ?/, ""); print } NR>2 && !/^#/ { exit }' "${BASH_SOURCE[0]}"
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "error: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-t|--title) TITLE="${2:?missing value for $1}"; shift 2 ;;
|
||||
-d|--description) DESCRIPTION="${2:?missing value for $1}"; shift 2 ;;
|
||||
-f|--description-file) DESCRIPTION_FILE="${2:?missing value for $1}"; shift 2 ;;
|
||||
-a|--assignee) ASSIGNEE="${2:?missing value for $1}"; shift 2 ;;
|
||||
-p|--priority) PRIORITY="${2:?missing value for $1}"; shift 2 ;;
|
||||
-P|--project) PROJECT_ID="${2:?missing value for $1}"; shift 2 ;;
|
||||
--parent) PARENT="${2:?missing value for $1}"; shift 2 ;;
|
||||
--idempotency-key) IDEMPOTENCY_KEY="${2:?missing value for $1}"; shift 2 ;;
|
||||
--json) PRINT_JSON=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) die "unknown option: $1 (use --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$PRIORITY" in
|
||||
backlog|low|medium|high|critical) ;;
|
||||
*) die "invalid priority: $PRIORITY" ;;
|
||||
esac
|
||||
|
||||
# --- Read the description from a file when asked. -----------------------------
|
||||
if [[ -n "$DESCRIPTION_FILE" ]]; then
|
||||
if [[ "$DESCRIPTION_FILE" == "-" ]]; then
|
||||
DESCRIPTION="$(cat)"
|
||||
else
|
||||
[[ -r "$DESCRIPTION_FILE" ]] || die "cannot read description file: $DESCRIPTION_FILE"
|
||||
DESCRIPTION="$(cat -- "$DESCRIPTION_FILE")"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Resolve the API key. Never print it. -------------------------------------
|
||||
if [[ -z "${PAPERCLIP_API_KEY:-}" ]]; then
|
||||
if [[ -r "$SECRETS_FILE" ]]; then
|
||||
PAPERCLIP_API_KEY="$(awk -F= '/^PAPERCLIP_API_KEY=/{sub(/^PAPERCLIP_API_KEY=/,""); print; exit}' "$SECRETS_FILE")"
|
||||
fi
|
||||
fi
|
||||
[[ -n "${PAPERCLIP_API_KEY:-}" ]] || die "PAPERCLIP_API_KEY is not set and not in $SECRETS_FILE"
|
||||
|
||||
command -v curl >/dev/null 2>&1 || die "curl is required"
|
||||
command -v jq >/dev/null 2>&1 || die "jq is required"
|
||||
|
||||
BASE_URL="${BASE_URL%/}"
|
||||
|
||||
# --- Check the board is reachable. --------------------------------------------
|
||||
if ! curl -sS -m 10 -o /dev/null "$BASE_URL/health"; then
|
||||
echo "error: Paperclip is not reachable at $BASE_URL/health" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# --- Build the request body. --------------------------------------------------
|
||||
BODY="$(jq -n \
|
||||
--arg title "$TITLE" \
|
||||
--arg description "$DESCRIPTION" \
|
||||
--arg priority "$PRIORITY" \
|
||||
--arg projectId "$PROJECT_ID" \
|
||||
--arg assigneeAgentId "$ASSIGNEE" \
|
||||
--arg parentId "$PARENT" \
|
||||
--arg idempotencyKey "$IDEMPOTENCY_KEY" \
|
||||
'{title: $title, description: $description, priority: $priority}
|
||||
+ (if $projectId != "" then {projectId: $projectId} else {} end)
|
||||
+ (if $assigneeAgentId != "" then {assigneeAgentId: $assigneeAgentId} else {} end)
|
||||
+ (if $parentId != "" then {parentId: $parentId} else {} end)
|
||||
+ (if $idempotencyKey != "" then {idempotencyKey: $idempotencyKey} else {} end)')"
|
||||
|
||||
TMP="$(mktemp)"
|
||||
trap 'rm -f "$TMP"' EXIT
|
||||
|
||||
HEADERS=(-H "Authorization: Bearer $PAPERCLIP_API_KEY" -H "Content-Type: application/json")
|
||||
if [[ -n "${PAPERCLIP_RUN_ID:-}" ]]; then
|
||||
HEADERS+=(-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID")
|
||||
fi
|
||||
|
||||
HTTP_CODE="$(curl -sS -o "$TMP" -w '%{http_code}' -X POST \
|
||||
"${HEADERS[@]}" \
|
||||
--data "$BODY" \
|
||||
"$BASE_URL/api/companies/$COMPANY_ID/issues")" || die "curl failed to reach $BASE_URL"
|
||||
|
||||
if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "201" ]]; then
|
||||
echo "error: Paperclip returned HTTP $HTTP_CODE" >&2
|
||||
cat "$TMP" >&2
|
||||
echo >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
ISSUE_ID="$(jq -r '(.issue // .).id // empty' "$TMP")"
|
||||
IDENTIFIER="$(jq -r '(.issue // .).identifier // empty' "$TMP")"
|
||||
[[ -n "$ISSUE_ID" ]] || die "Paperclip response has no issue id"
|
||||
|
||||
# --- Record the result. -------------------------------------------------------
|
||||
mkdir -p "$STATE_DIR"
|
||||
printf '%s\t%s\t%s\n' "$(date -u +%FT%TZ)" "$ISSUE_ID" "$IDENTIFIER" \
|
||||
>> "$STATE_DIR/paperclip-roundtrip-issues.log"
|
||||
|
||||
PREFIX="${IDENTIFIER%%-*}"
|
||||
echo "Filed Paperclip job"
|
||||
echo " title: $TITLE"
|
||||
echo " priority: $PRIORITY"
|
||||
echo " issue id: $ISSUE_ID"
|
||||
echo " identifier: $IDENTIFIER"
|
||||
if [[ "$PREFIX" != "$IDENTIFIER" && -n "$PREFIX" ]]; then
|
||||
echo " board url: $BASE_URL/$PREFIX/issues/$IDENTIFIER"
|
||||
fi
|
||||
echo " record: $STATE_DIR/paperclip-roundtrip-issues.log"
|
||||
|
||||
if [[ "$PRINT_JSON" == "1" ]]; then
|
||||
echo
|
||||
jq . "$TMP"
|
||||
fi
|
||||
Reference in New Issue
Block a user