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>
183 lines
6.8 KiB
Bash
Executable File
183 lines
6.8 KiB
Bash
Executable File
#!/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
|