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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.env
|
.env
|
||||||
*.visual-check.*
|
*.visual-check.*
|
||||||
|
.state/
|
||||||
|
|||||||
@@ -19,7 +19,11 @@ result that returns.
|
|||||||
`Hub`.
|
`Hub`.
|
||||||
- Note: `POST /api/agents/{id}/keys` is **board-only**. A run JWT gets
|
- Note: `POST /api/agents/{id}/keys` is **board-only**. A run JWT gets
|
||||||
`403 Board access required`. Mint keys with the local board key.
|
`403 Board access required`. Mint keys with the local board key.
|
||||||
2. File one job with `curl`. Use the issue-create endpoint.
|
2. File one job with the round-trip script. The script uses the issue-create
|
||||||
|
endpoint: `POST /api/companies/{companyId}/issues`.
|
||||||
|
- The old `POST /api/issues` route does not exist. It returns `404`.
|
||||||
|
- Company id: `d4921013-e5da-4d88-8016-57d5746e2d03`.
|
||||||
|
- Script: `scripts/paperclip-roundtrip.sh`.
|
||||||
3. Confirm the job appears in the Paperclip board.
|
3. Confirm the job appears in the Paperclip board.
|
||||||
4. Wait for the agent to close the job.
|
4. Wait for the agent to close the job.
|
||||||
5. Send the result to Jervis `POST /announce` so the house speaks it.
|
5. Send the result to Jervis `POST /announce` so the house speaks it.
|
||||||
@@ -28,11 +32,14 @@ result that returns.
|
|||||||
### Commands
|
### Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export PAPERCLIP_API_KEY=<from secrets>
|
# File one job with the script. It reads PAPERCLIP_API_KEY from the
|
||||||
curl -s -X POST http://192.168.20.13:3100/api/issues \
|
# environment, then from ~/.config/environment.d/10-secrets.conf.
|
||||||
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
scripts/paperclip-roundtrip.sh \
|
||||||
-H "Content-Type: application/json" \
|
--title "Hub round-trip job: confirm the return path" \
|
||||||
-d '{"title":"round-trip test","description":"prove the return path"}'
|
--description "Prove the outbound Paperclip path." \
|
||||||
|
--assignee 403de1f7-645c-4d35-bc7e-19b01d77bacc \
|
||||||
|
--priority high \
|
||||||
|
--project 7ae791bb-8d9a-462f-99bf-013421d20623
|
||||||
|
|
||||||
# Speak the result.
|
# Speak the result.
|
||||||
curl -s -X POST http://192.168.20.13:8501/announce \
|
curl -s -X POST http://192.168.20.13:8501/announce \
|
||||||
|
|||||||
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