fix(skills): rewrite live agent text and the upload script onto the token-free API helper

Every live skill and reference the agent reads at run time still taught
curl with the run bearer as a command-line argument. Rewrite each
teaching occurrence onto the paperclip-api helper, which reads the
token from the environment and adds it itself. State the reason next
to each rewrite: a command-line argument appears in a process listing
and in a shell history file.

Regenerate the skills-catalog manifest for the three bundled skill
files that changed.

Rewrite the artifact upload script onto the same helper. The helper
rejects a carriage return, a line feed, a double quote, or a backslash
in the uploaded file name instead of escaping it, so a path carrying
one of those characters now fails with a clear message. The script
fails with a clear message when the helper is not on PATH, and does
not fall back to curl.

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
nickyleachandPaperclip committed 2026-09-16 11:04:04 +00:00
1 parent c164289df0
commit 3ae75a1d0e
13 files changed
+134 -255

No files matched your search

@@ -110,25 +110,22 @@ workspace that should be freshly ready.
## Managed start / stop
Use the runtime-service endpoints on the main control plane. Include
`X-Paperclip-Run-Id` so the mutation is associated with the current heartbeat.
Use the runtime-service endpoints on the main control plane. Call them with
the `paperclip-api` helper program on `PATH`, not `curl`. The helper reads
the token from the environment and adds it itself, and it sets
`X-Paperclip-Run-Id` for you, so the mutation is still associated with the
current heartbeat. The token never becomes a command-line argument; a
command-line argument appears in a process listing and in a shell history
file.
```sh
curl -sS -X POST \
"$PAPERCLIP_API_URL/api/execution-workspaces/$EXECUTION_WORKSPACE_ID/runtime-services/stop" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-H "Content-Type: application/json" \
--data-binary '{"workspaceCommandId":"service:paperclip-dev"}'
paperclip-api POST "/api/execution-workspaces/$EXECUTION_WORKSPACE_ID/runtime-services/stop" \
-d '{"workspaceCommandId":"service:paperclip-dev"}'
```
```sh
curl -sS -X POST \
"$PAPERCLIP_API_URL/api/execution-workspaces/$EXECUTION_WORKSPACE_ID/runtime-services/start" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-H "Content-Type: application/json" \
--data-binary '{"workspaceCommandId":"service:paperclip-dev"}'
paperclip-api POST "/api/execution-workspaces/$EXECUTION_WORKSPACE_ID/runtime-services/start" \
-d '{"workspaceCommandId":"service:paperclip-dev"}'
```
If the API returns an existing service, treat that as a candidate only. Verify
@@ -179,12 +176,8 @@ If the port owner is a sibling workspace's process (port squat):
service and re-run the identity check:
```sh
curl -sS -X POST \
"$PAPERCLIP_API_URL/api/execution-workspaces/$EXECUTION_WORKSPACE_ID/runtime-services/restart" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-H "Content-Type: application/json" \
--data-binary '{"workspaceCommandId":"service:paperclip-dev"}'
paperclip-api POST "/api/execution-workspaces/$EXECUTION_WORKSPACE_ID/runtime-services/restart" \
-d '{"workspaceCommandId":"service:paperclip-dev"}'
```
6. A restart only holds if the conflicting owner stays stopped. If the port is
@@ -253,12 +246,13 @@ process after managed stop fails. Do not kill unrelated processes.
## Verify main control-plane runtime state
Read the execution workspace from the main API and inspect the runtime service
record.
record. Use the `paperclip-api` helper program on `PATH`, not `curl`; the
helper reads the token from the environment and adds it itself, so the token
never becomes a command-line argument. A command-line argument appears in a
process listing and in a shell history file.
```sh
curl -sS \
"$PAPERCLIP_API_URL/api/execution-workspaces/$EXECUTION_WORKSPACE_ID" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" | jq
paperclip-api GET "/api/execution-workspaces/$EXECUTION_WORKSPACE_ID" | jq
```
The target service should show:
@@ -275,12 +269,13 @@ stale process through the managed runtime.
## Verify served workspace runtime state
The cloned Paperclip app must also know about the service. Query the same
execution workspace through the served app when agent auth is available there:
execution workspace through the served app when agent auth is available
there. Point the helper at the served app for this one call by setting
`PAPERCLIP_API_URL` only for that command, so the token still never becomes a
command-line argument:
```sh
curl -sS \
"$SERVICE_URL/api/execution-workspaces/$EXECUTION_WORKSPACE_ID" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" | jq
PAPERCLIP_API_URL="$SERVICE_URL" paperclip-api GET "/api/execution-workspaces/$EXECUTION_WORKSPACE_ID" | jq
```
The served app should agree that the service is `running` / `healthy` at the
@@ -298,10 +293,8 @@ Minimum API checks:
```sh
curl -sS "$SERVICE_URL/api/health" | jq '.status, .bootstrapStatus'
curl -sS "$SERVICE_URL/api/companies" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" | jq
curl -sS "$SERVICE_URL/api/agents/me" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" | jq
PAPERCLIP_API_URL="$SERVICE_URL" paperclip-api GET "/api/companies" | jq
PAPERCLIP_API_URL="$SERVICE_URL" paperclip-api GET "/api/agents/me" | jq
```
Then verify at least one expected cloned product record through the API, such
@@ -58,11 +58,12 @@ Every proposal must satisfy all of these:
## Procedure
Call every route below with the `paperclip-api` helper program on `PATH`, not `curl`. The helper reads the access token from the environment and adds the `Authorization` header itself, so the token never becomes a command-line argument. A command-line argument appears in a process listing and in a shell history file.
### 1) Confirm target and scope
```sh
curl -sS "$PAPERCLIP_API_URL/api/agents/<targetAgentId>" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/agents/<targetAgentId>"
```
Record `name`, `role`, `reportsTo`, `adapterType`, `adapterConfig.instructionsFilePath` (where `AGENTS.md` lives), and current assigned skills via `GET /api/agents/<targetAgentId>/skills`. Refuse and exit if `targetAgentId == $PAPERCLIP_AGENT_ID`.
@@ -70,15 +71,14 @@ Record `name`, `role`, `reportsTo`, `adapterType`, `adapterConfig.instructionsFi
### 2) Pull the recent record
```sh
curl -sS "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/issues?assigneeAgentId=<targetAgentId>&status=done,in_review,blocked&limit=25" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/companies/$PAPERCLIP_COMPANY_ID/issues?assigneeAgentId=<targetAgentId>&status=done,in_review,blocked&limit=25"
```
For each issue, pull the trajectory substrate — the issue body and its comments:
```sh
curl -sS "$PAPERCLIP_API_URL/api/issues/<issueId>" -H "Authorization: Bearer $PAPERCLIP_API_KEY"
curl -sS "$PAPERCLIP_API_URL/api/issues/<issueId>/comments" -H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/issues/<issueId>"
paperclip-api GET "/api/issues/<issueId>/comments"
```
Keep status transitions, blocker reasons, reviewer comments, approval outcomes, human corrections, and PR-link comments. Comments are the closest thing Paperclip has to an execution trace — treat them as first-class evidence.
@@ -24,33 +24,20 @@ Use this skill in one of two modes:
Agent-authored cards require `tasks:assign`, remain company-scoped, and are available only when `enableStatusCards` is enabled. An agent may manage only cards it authored, may author at most 20 cards, and may send at most 4,000 characters in `interestPrompt`.
Normalize the run-provided API base and create a manual card:
Create a manual card with the `paperclip-api` helper program on `PATH`, not `curl`. The helper reads the run-provided API base and the access token from the environment and adds them itself, so the token never becomes a command-line argument. A command-line argument appears in a process listing and in a shell history file.
```bash
PAPERCLIP_API_BASE="${PAPERCLIP_API_URL%/}"
PAPERCLIP_API_BASE="${PAPERCLIP_API_BASE%/api}"
curl -sS -X POST \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"interestPrompt":"Blocked or in-review launch work updated this week"}' \
"$PAPERCLIP_API_BASE/api/companies/$PAPERCLIP_COMPANY_ID/status-cards"
paperclip-api POST "/api/companies/$PAPERCLIP_COMPANY_ID/status-cards" \
-d '{"interestPrompt":"Blocked or in-review launch work updated this week"}'
```
Creation returns `201` and queues compilation automatically. Save the returned card id. To refine an owned card or request a refresh:
```bash
curl -sS -X PATCH \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"interestPrompt":"Blocked or in-review launch work updated this week. Call out the single next decision."}' \
"$PAPERCLIP_API_BASE/api/status-cards/$STATUS_CARD_ID"
paperclip-api PATCH "/api/status-cards/$STATUS_CARD_ID" \
-d '{"interestPrompt":"Blocked or in-review launch work updated this week. Call out the single next decision."}'
curl -sS -X POST \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"full":false}' \
"$PAPERCLIP_API_BASE/api/status-cards/$STATUS_CARD_ID/refresh"
paperclip-api POST "/api/status-cards/$STATUS_CARD_ID/refresh" -d '{"full":false}'
```
Do not call `/query` or `/summary` while authoring. Those write-back routes are reserved for the assigned Summarizer generation issue and run.
@@ -58,7 +58,7 @@ Use these routes directly. Do not guess unscoped `/api/issues` or alternate summ
- Gather execution-workspace issues: `GET /api/companies/{companyId}/issues?executionWorkspaceId=...`
- Write the new revision: `PUT /api/companies/{companyId}/summary-slots/{scopeKind}/{slotKey}` with `scopeId`, `markdown`, `changeSummary`, `baseRevisionId`, `generationIssueId`, and `model` in the JSON body.
For `workspaces_overview`, omit `scopeId` from the read query and send it as `null` in the write body. All calls use the run-scoped Paperclip API URL and bearer token already present in the environment.
For `workspaces_overview`, omit `scopeId` from the read query and send it as `null` in the write body. Call every route with the `paperclip-api` helper program on `PATH`, not `curl`. The helper reads the run-scoped Paperclip API URL and the access token from the environment and adds them itself, so the token never becomes a command-line argument. A command-line argument appears in a process listing and in a shell history file.
Complete project-slot write example:
@@ -89,11 +89,7 @@ jq -n \
generationIssueId: $generationIssueId,
model: $model
}' |
curl -sS -X PUT \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
"$PAPERCLIP_API_URL/api/companies/$COMPANY_ID/summary-slots/project/header" \
--data-binary @-
paperclip-api PUT "/api/companies/$COMPANY_ID/summary-slots/project/header" -d @-
```
## Procedure
+10 -10
View File
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"packageName": "@paperclipai/skills-catalog",
"packageVersion": "0.3.1",
"generatedAt": "2026-08-11T15:05:34.526Z",
"generatedAt": "2026-09-16T10:58:12.842Z",
"skills": [
{
"id": "paperclipai:bundled:docs:doc-maintenance",
@@ -102,11 +102,11 @@
{
"path": "SKILL.md",
"kind": "skill",
"sizeBytes": 12028,
"sha256": "083d5fa8dc43c3ce7747e89c1afb6f6f42d0603a31808bffc5f8e56735f2d865"
"sizeBytes": 12120,
"sha256": "d0e6e25da37111b57cca8ec758bde7c4c8f24e6b64770ddacb795bbb75c5883b"
}
],
"contentHash": "sha256:f2592d81f616628a3eba92636616424f2eed82ec22bb504240a58f6be115e407"
"contentHash": "sha256:a4208745a552233befb60829d9c45ad8d30f3a78a7c9171c76957ecf884cbbd6"
},
{
"id": "paperclipai:bundled:paperclip-operations:status-card-query",
@@ -137,11 +137,11 @@
{
"path": "SKILL.md",
"kind": "skill",
"sizeBytes": 6368,
"sha256": "0c6140f51d503cbadc98723ce54c0def48e7e31ea159b0935b8ad6369291498a"
"sizeBytes": 6201,
"sha256": "1d024ce62b394a41174afd06b4cf0ae120d58a0dc6171158c722736acf750796"
}
],
"contentHash": "sha256:2b6e53bf8491f027afdbd6961ad84f677cfd385a0a6a73616ea84111ca8886ed"
"contentHash": "sha256:8d605dd7a19bc4eafa9b6e344440573c38b02652d7679f06e1504d0f77811f3f"
},
{
"id": "paperclipai:bundled:paperclip-operations:summarize-status",
@@ -172,11 +172,11 @@
{
"path": "SKILL.md",
"kind": "skill",
"sizeBytes": 8877,
"sha256": "4cb3d177f5d16e3cc2052d2f80e7fb245ab95e79c1e947abdc84cd4ee3d8c61e"
"sizeBytes": 8980,
"sha256": "a0edcafcf1cf7ec04cc54aa956b0206d9e5afd81538eaeb390369b814b369e06"
}
],
"contentHash": "sha256:3e49ee2b8d83d1371fb0f59197bb96c46b64f722540405f46d4a5a756fbe1b89"
"contentHash": "sha256:86a89739d83ca40d330697c08e1fa3fed9273cced9e4015c36dc9c3d454d3b66"
},
{
"id": "paperclipai:bundled:paperclip-operations:task-planning",
+1 -1
View File
@@ -16,7 +16,7 @@ You are a board-level assistant helping a human manage their AI-agent company th
- `PAPERCLIP_API_URL` — base URL of the Paperclip server (e.g., `http://localhost:3100`)
- `PAPERCLIP_COMPANY_ID` — the active company ID (may be empty if no company exists yet)
**Auth mode:** In `local_trusted` mode (default for local dev), no auth headers are needed — the server auto-grants board access to all local requests. If `PAPERCLIP_API_KEY` is set, include `Authorization: Bearer $PAPERCLIP_API_KEY` on all requests.
**Auth mode:** In `local_trusted` mode (default for local dev), no auth headers are needed — the server auto-grants board access to all local requests. If `PAPERCLIP_API_KEY` is set, call the API with the `paperclip-api` helper program on `PATH` instead of `curl`. The helper reads the token from the environment and adds the `Authorization` header itself, so the token never becomes a command-line argument. A command-line argument appears in a process listing and in a shell history file.
**Making API calls:** Use `curl -sS` via bash. All endpoints are under `/api`. All request/response bodies are JSON. Always use `Content-Type: application/json` on POST/PATCH/PUT requests.
+16 -27
View File
@@ -21,29 +21,30 @@ If you do not have this permission, escalate to your CEO or board.
## Workflow
Call every route below with the `paperclip-api` helper program on `PATH`, not
`curl`. The helper reads the token from the environment and adds it itself,
so the token never becomes a command-line argument. A command-line argument
appears in a process listing and in a shell history file.
### 1. Confirm identity and company context
```sh
curl -sS "$PAPERCLIP_API_URL/api/agents/me" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/agents/me"
```
### 2. Discover adapter configuration for this Paperclip instance
```sh
curl -sS "$PAPERCLIP_API_URL/llms/agent-configuration.txt" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/llms/agent-configuration.txt"
# Then the specific adapter you plan to use, e.g. claude_local:
curl -sS "$PAPERCLIP_API_URL/llms/agent-configuration/claude_local.txt" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/llms/agent-configuration/claude_local.txt"
```
### 3. Compare existing agent configurations
```sh
curl -sS "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/agent-configurations" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/companies/$PAPERCLIP_COMPANY_ID/agent-configurations"
```
Note naming, icon, reporting-line, and adapter conventions the company already follows.
@@ -67,8 +68,7 @@ State which path you took in your hire-request comment so the board can see the
### 5. Discover allowed agent icons
```sh
curl -sS "$PAPERCLIP_API_URL/llms/agent-icons.txt" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/llms/agent-icons.txt"
```
### 6. Draft the new hire config
@@ -96,10 +96,7 @@ Before submitting, walk the draft-review checklist end-to-end and fix any item t
### 8. Submit hire request
```sh
curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/agent-hires" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
paperclip-api POST "/api/companies/$PAPERCLIP_COMPANY_ID/agent-hires" -d '{
"name": "CTO",
"role": "cto",
"title": "Chief Technology Officer",
@@ -122,32 +119,24 @@ curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/agent-h
- when the board approves, you will be woken with `PAPERCLIP_APPROVAL_ID`; read linked issues and close/comment follow-up
```sh
curl -sS "$PAPERCLIP_API_URL/api/approvals/<approval-id>" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/approvals/<approval-id>"
curl -sS -X POST "$PAPERCLIP_API_URL/api/approvals/<approval-id>/comments" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
paperclip-api POST "/api/approvals/<approval-id>/comments" \
-d '{"body":"## CTO hire request submitted\n\n- Approval: [<approval-id>](/approvals/<approval-id>)\n- Pending agent: [<agent-ref>](/agents/<agent-url-key-or-id>)\n- Source issue: [<issue-ref>](/issues/<issue-identifier-or-id>)\n\nUpdated prompt and adapter config per board feedback."}'
```
If the approval already exists and needs manual linking to the issue:
```sh
curl -sS -X POST "$PAPERCLIP_API_URL/api/issues/<issue-id>/approvals" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"approvalId":"<approval-id>"}'
paperclip-api POST "/api/issues/<issue-id>/approvals" -d '{"approvalId":"<approval-id>"}'
```
After approval is granted, run this follow-up loop:
```sh
curl -sS "$PAPERCLIP_API_URL/api/approvals/$PAPERCLIP_APPROVAL_ID" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/approvals/$PAPERCLIP_APPROVAL_ID"
curl -sS "$PAPERCLIP_API_URL/api/approvals/$PAPERCLIP_APPROVAL_ID/issues" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/approvals/$PAPERCLIP_APPROVAL_ID/issues"
```
For each linked issue, either:
+7 -12
View File
@@ -17,7 +17,7 @@ In Paperclip, **task** and **issue** refer to the same work item. The UI may use
## Authentication
Env vars auto-injected: `PAPERCLIP_AGENT_ID`, `PAPERCLIP_COMPANY_ID`, `PAPERCLIP_API_URL`, `PAPERCLIP_RUN_ID`. Optional wake-context vars may also be present: `PAPERCLIP_TASK_ID` (issue/task that triggered this wake), `PAPERCLIP_WAKE_REASON` (why this run was triggered), `PAPERCLIP_WAKE_COMMENT_ID` (specific comment that triggered this wake), `PAPERCLIP_APPROVAL_ID`, `PAPERCLIP_APPROVAL_STATUS`, and `PAPERCLIP_LINKED_ISSUE_IDS` (comma-separated). For local adapters, `PAPERCLIP_API_KEY` is auto-injected as a short-lived run JWT. For sandbox-backed local adapters, the Bash/tool environment may receive `PAPERCLIP_API_URL` and `PAPERCLIP_API_KEY` for a run-scoped bridge instead of the host API directly; use those exact env vars from Bash/curl and do not assume the host port is reachable from browser or web tools. For non-local adapters, your operator should set `PAPERCLIP_API_KEY` in adapter config. All requests use `Authorization: Bearer $PAPERCLIP_API_KEY`. All endpoints are under `/api`. Use JSON except for multipart attachment uploads and binary content downloads. Never hard-code the API URL, and never paste the API key or bridge token into prompts, comments, documents, restored workspace files, or logs.
Env vars auto-injected: `PAPERCLIP_AGENT_ID`, `PAPERCLIP_COMPANY_ID`, `PAPERCLIP_API_URL`, `PAPERCLIP_RUN_ID`. Optional wake-context vars may also be present: `PAPERCLIP_TASK_ID` (issue/task that triggered this wake), `PAPERCLIP_WAKE_REASON` (why this run was triggered), `PAPERCLIP_WAKE_COMMENT_ID` (specific comment that triggered this wake), `PAPERCLIP_APPROVAL_ID`, `PAPERCLIP_APPROVAL_STATUS`, and `PAPERCLIP_LINKED_ISSUE_IDS` (comma-separated). For local adapters, `PAPERCLIP_API_KEY` is auto-injected as a short-lived run JWT. For sandbox-backed local adapters, the Bash/tool environment may receive `PAPERCLIP_API_URL` and `PAPERCLIP_API_KEY` for a run-scoped bridge instead of the host API directly; use those exact env vars from Bash and do not assume the host port is reachable from browser or web tools. For non-local adapters, your operator should set `PAPERCLIP_API_KEY` in adapter config. Call every route with the `paperclip-api` helper program on `PATH`, not `curl`. The helper reads the token from the environment and adds the `Authorization` header itself, so the token never becomes a command-line argument. A command-line argument appears in a process listing and in a shell history file. All endpoints are under `/api`. Use JSON except for multipart attachment uploads and binary content downloads. Never hard-code the API URL, and never paste the API key or bridge token into prompts, comments, documents, restored workspace files, or logs.
Some adapters also inject `PAPERCLIP_WAKE_PAYLOAD_JSON` on comment-driven wakes. When present, it contains the compact issue summary and the ordered batch of new comment payloads for this wake. Use it first. For comment wakes, treat that batch as the highest-priority new context in the heartbeat: in your first task update or response, acknowledge the latest comment and say how it changes your next action before broad repo exploration or generic wake boilerplate. Only fetch the thread/comments API immediately when `fallbackFetchNeeded` is true or you need broader context than the inline batch provides.
@@ -115,12 +115,11 @@ Overrides and special cases:
- **Blocked-task dedup:** before touching a `blocked` task, check the thread. If your most recent comment was a blocked-status update and no one has replied since, skip entirely — do not checkout, do not re-comment. Only re-engage on new context (comment, status change, event wake).
- Nothing assigned and no valid mention handoff → exit the heartbeat.
**Step 5 — Checkout.** You MUST checkout before doing any work. Include the run ID header:
**Step 5 — Checkout.** You MUST checkout before doing any work. `paperclip-api` sets the run ID header for you:
```
POST /api/issues/{issueId}/checkout
Headers: Authorization: Bearer $PAPERCLIP_API_KEY, X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID
{ "agentId": "{your-agent-id}", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] }
paperclip-api POST "/api/issues/{issueId}/checkout" \
-d '{"agentId": "{your-agent-id}", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"]}'
```
If already checked out by you, returns normally. If owned by another agent: `409 Conflict` — stop, pick a different task. **Never retry a 409.**
@@ -520,20 +519,16 @@ Before proposing a credential you MUST read the "Agent secret proposals" section
## Reading Granted Secrets
When authenticated with the current run's agent JWT, list the secrets available to that run before fetching a value:
When authenticated with the current run's agent JWT, list the secrets available to that run before fetching a value. Use the `paperclip-api` helper program on `PATH`, not `curl`; the helper reads the token from the environment and adds it itself, so the token never becomes a command-line argument. A command-line argument appears in a process listing and in a shell history file.
```bash
PAPERCLIP_API_BASE="${PAPERCLIP_API_URL%/}"
PAPERCLIP_API_BASE="${PAPERCLIP_API_BASE%/api}"
curl -s -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
"$PAPERCLIP_API_BASE/api/agents/me/secrets"
paperclip-api GET "/api/agents/me/secrets"
```
The list is metadata-only. Fetch a specific value only when needed; the request has no body:
```bash
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
"$PAPERCLIP_API_BASE/api/agents/me/secrets/github_token/value"
paperclip-api POST "/api/agents/me/secrets/github_token/value"
```
- An `env.*` secret binding also grants API read access; `access.*` bindings grant API access without env injection.
+5 -23
View File
@@ -1474,21 +1474,15 @@ Terminal states: `done`, `cancelled`
**Never paste a credential into a comment, document, file, or transcript.** When a credential is supplied to an agent or returned by a secure flow — pasted by a user, returned by an OAuth flow, delivered by email, or obtained from another secure source — send it directly to `POST /api/agents/me/secret-proposals` using the current run-bound agent JWT. Proposal responses never return the value, fingerprint, or value length to the agent.
Keep the credential in memory or pass it directly from the secure source; do not place the literal value in the command text or echo it. The example assumes `PROPOSED_SECRET_VALUE` is already populated without printing it:
Keep the credential in memory or pass it directly from the secure source; do not place the literal value in the command text or echo it. Use the `paperclip-api` helper program on `PATH`, not `curl`; the helper reads the token from the environment and adds it itself, so the token never becomes a command-line argument. A command-line argument appears in a process listing and in a shell history file. The example assumes `PROPOSED_SECRET_VALUE` is already populated without printing it:
```bash
PAPERCLIP_API_BASE="${PAPERCLIP_API_URL%/}"
PAPERCLIP_API_BASE="${PAPERCLIP_API_BASE%/api}"
jq -n \
--arg name "integrations/vendor/api-token" \
--arg value "$PROPOSED_SECRET_VALUE" \
--arg justification "Credential supplied for the current task" \
'{kind:"secret", name:$name, value:$value, justification:$justification}' |
curl -s -X POST \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- \
"$PAPERCLIP_API_BASE/api/agents/me/secret-proposals"
paperclip-api POST "/api/agents/me/secret-proposals" -d @-
unset PROPOSED_SECRET_VALUE
```
@@ -1514,11 +1508,7 @@ jq -n \
--arg configPath "env.VENDOR_API_TOKEN" \
--arg justification "Inject the approved credential into my adapter environment" \
'{kind:"binding", secretProposalId:$secretProposalId, configPath:$configPath, justification:$justification}' |
curl -s -X POST \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- \
"$PAPERCLIP_API_BASE/api/agents/me/secret-proposals"
paperclip-api POST "/api/agents/me/secret-proposals" -d @-
```
A binding must specify exactly one of `secretProposalId`, `secretId`, or `sourceConfigPath`. `configPath` accepts `env.<KEY>` for environment injection or `access.<ALIAS>` for API-only access. Under the default `self_and_reports` policy, `targetAgentId` may identify a downward report of the proposer; omitting it targets the proposer. Other targets are denied, and approval rechecks the current chain of command.
@@ -1528,18 +1518,12 @@ A binding must specify exactly one of `secretProposalId`, `secretId`, or `source
Use `sourceConfigPath` when the secret is already bound to the proposing agent. The server resolves that agent's own `env.*` or `access.*` binding, so the request never needs a secret ID or `secretRef`:
```bash
PAPERCLIP_API_BASE="${PAPERCLIP_API_URL%/}"
PAPERCLIP_API_BASE="${PAPERCLIP_API_BASE%/api}"
jq -n \
--arg sourceConfigPath "access.openai_api_key" \
--arg configPath "access.evals_openai_api_key" \
--arg justification "Use the existing OpenAI credential under the eval-specific alias" \
'{kind:"binding", sourceConfigPath:$sourceConfigPath, configPath:$configPath, justification:$justification}' |
curl -s -X POST \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- \
"$PAPERCLIP_API_BASE/api/agents/me/secret-proposals"
paperclip-api POST "/api/agents/me/secret-proposals" -d @-
```
`sourceConfigPath` must name an existing binding on the proposing agent; another agent's path and an unknown path both return `404`. Omit `targetAgentId` to bind the alias back to yourself. Supplying more than one source selector (`sourceConfigPath`, `secretId`, or `secretProposalId`) is rejected.
@@ -1557,9 +1541,7 @@ The card uses `continuationPolicy: "wake_assignee"`. On resolution the issue ass
**After any secret card resolves, re-verify through `GET /api/agents/me/secrets`. Acceptance is not execution.** On the resumed run, call:
```bash
curl -s \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
"$PAPERCLIP_API_BASE/api/agents/me/secrets"
paperclip-api GET "/api/agents/me/secrets"
```
Confirm the expected secret metadata and delivery are present before using the new binding. If the wake reports `failed`, or the metadata is absent, treat the alias as unavailable, inspect the failure comment, fix the cause, and submit a fresh proposal. Never infer success merely because the card says accepted.
+6 -19
View File
@@ -49,36 +49,23 @@ Annotate the work product with `metadata.resourceRef`:
optional positive integers. `relativePath` must be relative to the selected
workspace root; do not use host-local absolute paths in `resourceRef`.
Create the work product with:
Create the work product with the `paperclip-api` helper program on `PATH`, not `curl`. The helper reads the token from the environment and adds it itself, so the token never becomes a command-line argument. A command-line argument appears in a process listing and in a shell history file.
```bash
curl -sS -X POST \
"$PAPERCLIP_API_URL/api/issues/$PAPERCLIP_TASK_ID/work-products" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-H "Content-Type: application/json" \
--data-binary @workspace-file-work-product.json
paperclip-api POST "/api/issues/$PAPERCLIP_TASK_ID/work-products" -d @workspace-file-work-product.json
```
If the helper is unavailable, use the Paperclip API directly:
If the upload helper is unavailable, use the Paperclip API directly:
```bash
curl -sS -X POST \
"$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/issues/$PAPERCLIP_TASK_ID/attachments" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-F 'file=@"path/to/output.webm";type=video/webm'
paperclip-api POST "/api/companies/$PAPERCLIP_COMPANY_ID/issues/$PAPERCLIP_TASK_ID/attachments" \
-F 'file=@path/to/output.webm;type=video/webm'
```
Then create a work product when the file is the deliverable. The server canonicalizes attachment-backed artifact metadata from the `attachmentId`:
```bash
curl -sS -X POST \
"$PAPERCLIP_API_URL/api/issues/$PAPERCLIP_TASK_ID/work-products" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-H "Content-Type: application/json" \
--data-binary '{
paperclip-api POST "/api/issues/$PAPERCLIP_TASK_ID/work-products" -d '{
"type": "artifact",
"provider": "paperclip",
"title": "Walkthrough render",
+20 -44
View File
@@ -78,17 +78,18 @@ Browse, inspect, and install catalog skills before reaching for an external
source. Bundled skills are the curated defaults for any company; optional
skills are role- or domain-specific.
Call every route in this reference with the `paperclip-api` helper program on
`PATH`, not `curl`. The helper reads the token from the environment and adds
it itself, so the token never becomes a command-line argument. A
command-line argument appears in a process listing and in a shell history
file.
```sh
curl -sS "$PAPERCLIP_API_URL/api/skills/catalog?kind=bundled" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/skills/catalog?kind=bundled"
curl -sS "$PAPERCLIP_API_URL/api/skills/catalog/ref?ref=github-pr-workflow" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/skills/catalog/ref?ref=github-pr-workflow"
curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/install-catalog" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
paperclip-api POST "/api/companies/$PAPERCLIP_COMPANY_ID/skills/install-catalog" -d '{
"catalogSkillId": "paperclipai:bundled:software-development:github-pr-workflow"
}'
```
@@ -116,10 +117,7 @@ Import using a **skills.sh URL**, a key-style source string, a GitHub URL, or a
### Example: skills.sh import (preferred)
```sh
curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/import" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
paperclip-api POST "/api/companies/$PAPERCLIP_COMPANY_ID/skills/import" -d '{
"source": "https://skills.sh/google-labs-code/stitch-skills/design-md"
}'
```
@@ -127,10 +125,7 @@ curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/
Or equivalently using the key-style string:
```sh
curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/import" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
paperclip-api POST "/api/companies/$PAPERCLIP_COMPANY_ID/skills/import" -d '{
"source": "google-labs-code/stitch-skills/design-md"
}'
```
@@ -138,10 +133,7 @@ curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/
### Example: GitHub import
```sh
curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/import" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
paperclip-api POST "/api/companies/$PAPERCLIP_COMPANY_ID/skills/import" -d '{
"source": "https://github.com/vercel-labs/agent-browser"
}'
```
@@ -155,27 +147,21 @@ You can also use source strings such as:
If the task is to discover skills from the company project workspaces first:
```sh
curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/scan-projects" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
paperclip-api POST "/api/companies/$PAPERCLIP_COMPANY_ID/skills/scan-projects" -d '{}'
```
## Inspect What Was Installed
```sh
curl -sS "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/companies/$PAPERCLIP_COMPANY_ID/skills"
```
Read the skill entry and its `SKILL.md`:
```sh
curl -sS "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/<skill-id>" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/companies/$PAPERCLIP_COMPANY_ID/skills/<skill-id>"
curl -sS "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/<skill-id>/files?path=SKILL.md" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/companies/$PAPERCLIP_COMPANY_ID/skills/<skill-id>/files?path=SKILL.md"
```
## Assign Skills To An Existing Agent
@@ -195,10 +181,7 @@ The request must include a merge mode:
- `replace` overwrites the complete desired skill set. Use it only after explicit confirmation.
```sh
curl -sS -X POST "$PAPERCLIP_API_URL/api/agents/<agent-id>/skills/sync" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
paperclip-api POST "/api/agents/<agent-id>/skills/sync" -d '{
"mode": "add",
"desiredSkills": [
"vercel-labs/agent-browser/agent-browser"
@@ -209,8 +192,7 @@ curl -sS -X POST "$PAPERCLIP_API_URL/api/agents/<agent-id>/skills/sync" \
If you need the current state first:
```sh
curl -sS "$PAPERCLIP_API_URL/api/agents/<agent-id>/skills" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
paperclip-api GET "/api/agents/<agent-id>/skills"
```
## Include Skills During Hire Or Create
@@ -218,10 +200,7 @@ curl -sS "$PAPERCLIP_API_URL/api/agents/<agent-id>/skills" \
Use the same company skill keys or references in `desiredSkills` when hiring or creating an agent:
```sh
curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/agent-hires" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
paperclip-api POST "/api/companies/$PAPERCLIP_COMPANY_ID/agent-hires" -d '{
"name": "QA Browser Agent",
"role": "qa",
"adapterType": "codex_local",
@@ -237,10 +216,7 @@ curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/agent-h
For direct create without approval:
```sh
curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/agents" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
paperclip-api POST "/api/companies/$PAPERCLIP_COMPANY_ID/agents" -d '{
"name": "QA Browser Agent",
"role": "qa",
"adapterType": "codex_local",
@@ -4,11 +4,10 @@ Use this reference when an issue has an isolated execution workspace and you nee
## Discover the Workspace
Start from the issue, not from memory:
Start from the issue, not from memory. Use the `paperclip-api` helper program on `PATH`, not `curl`; the helper reads the token from the environment and adds it itself, so the token never becomes a command-line argument. A command-line argument appears in a process listing and in a shell history file.
```sh
curl -sS -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
"$PAPERCLIP_API_URL/api/issues/$PAPERCLIP_TASK_ID/heartbeat-context"
paperclip-api GET "/api/issues/$PAPERCLIP_TASK_ID/heartbeat-context"
```
Read `currentExecutionWorkspace`:
@@ -26,28 +25,13 @@ Prefer Paperclip-managed runtime service controls over manual `pnpm dev &` or ad
```sh
# Start all configured services; waits for configured readiness checks.
curl -sS -X POST \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-H "Content-Type: application/json" \
"$PAPERCLIP_API_URL/api/execution-workspaces/<workspace-id>/runtime-services/start" \
-d '{}'
paperclip-api POST "/api/execution-workspaces/<workspace-id>/runtime-services/start" -d '{}'
# Restart all configured services.
curl -sS -X POST \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-H "Content-Type: application/json" \
"$PAPERCLIP_API_URL/api/execution-workspaces/<workspace-id>/runtime-services/restart" \
-d '{}'
paperclip-api POST "/api/execution-workspaces/<workspace-id>/runtime-services/restart" -d '{}'
# Stop all running services.
curl -sS -X POST \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-H "Content-Type: application/json" \
"$PAPERCLIP_API_URL/api/execution-workspaces/<workspace-id>/runtime-services/stop" \
-d '{}'
paperclip-api POST "/api/execution-workspaces/<workspace-id>/runtime-services/stop" -d '{}'
```
To target a configured service, pass one of:
@@ -50,6 +50,13 @@ require_command() {
fi
}
require_paperclip_api() {
if ! command -v paperclip-api >/dev/null 2>&1; then
printf 'Missing required command: paperclip-api. This program is staged onto PATH inside a Paperclip agent run; it is not available outside one.\n' >&2
exit 1
fi
}
json_bool() {
if [[ "${1:-0}" == "1" ]]; then
printf 'true'
@@ -115,32 +122,20 @@ sha256_text() {
request_json() {
local method="$1"
local url="$2"
local path="$2"
local body="${3:-}"
local response_file
local status_code
response_file="$(mktemp)"
if [[ -n "$body" ]]; then
status_code="$(
curl -sS -X "$method" -w '%{http_code}' -o "$response_file" \
"$url" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-H 'Content-Type: application/json' \
--data-binary "$body"
)"
status_code="$(paperclip-api "$method" "$path" -o "$response_file" --status -d "$body")"
else
status_code="$(
curl -sS -X "$method" -w '%{http_code}' -o "$response_file" \
"$url" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID"
)"
status_code="$(paperclip-api "$method" "$path" -o "$response_file" --status)"
fi
if [[ "$status_code" -lt 200 || "$status_code" -ge 300 ]]; then
printf 'Request failed (%s): %s\n' "$status_code" "$url" >&2
printf 'Request failed (%s): %s\n' "$status_code" "$path" >&2
cat "$response_file" >&2
printf '\n' >&2
rm -f "$response_file"
@@ -152,33 +147,27 @@ request_json() {
}
upload_file() {
local url="$1"
local path="$2"
local path="$1"
local file_path="$2"
local content_type="$3"
local escaped_path
local response_file
local status_code
local curl_status=0
local helper_status=0
local indeterminate=0
escaped_path="${path//\\/\\\\}"
escaped_path="${escaped_path//\"/\\\"}"
response_file="$(mktemp)"
status_code="$(
curl -sS -X POST -w '%{http_code}' -o "$response_file" \
"$url" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-F "file=@\"${escaped_path}\";type=${content_type}"
)" || curl_status=$?
paperclip-api POST "$path" -o "$response_file" --status \
-F "file=@${file_path};type=${content_type}"
)" || helper_status=$?
if [[ "$curl_status" -ne 0 ]]; then
if [[ "$helper_status" -ne 0 ]]; then
rm -f "$response_file"
return 75
fi
if [[ "$status_code" -lt 200 || "$status_code" -ge 300 ]]; then
printf 'Upload failed (%s): %s\n' "$status_code" "$url" >&2
printf 'Upload failed (%s): %s\n' "$status_code" "$path" >&2
cat "$response_file" >&2
printf '\n' >&2
# The bridge uses a structured 409 outcome after a possibly committed
@@ -369,7 +358,6 @@ if [[ -n "$chat_comment" && "$create_work_product" != "1" ]]; then
exit 1
fi
require_command curl
require_command jq
if [[ -z "$title" ]]; then
@@ -398,6 +386,8 @@ if [[ "$dry_run" == "1" ]]; then
exit 0
fi
require_paperclip_api
if [[ -z "${PAPERCLIP_API_URL:-}" || -z "${PAPERCLIP_API_KEY:-}" || -z "${PAPERCLIP_RUN_ID:-}" ]]; then
printf 'Missing PAPERCLIP_API_URL, PAPERCLIP_API_KEY, or PAPERCLIP_RUN_ID.\n' >&2
exit 1
@@ -429,7 +419,7 @@ if [[ -f "$unknown_upload_marker" && "$retry_unknown_upload" != "1" ]]; then
lookup_attempts=20
fi
for ((lookup_attempt = 1; lookup_attempt <= lookup_attempts; lookup_attempt++)); do
existing_attachments="$(request_json GET "$api_base/issues/$issue_id/attachments")"
existing_attachments="$(request_json GET "/api/issues/$issue_id/attachments")"
attachment="$(
jq -nc \
--argjson attachments "$existing_attachments" \
@@ -469,7 +459,7 @@ if [[ -z "$attachment" ]]; then
upload_status=0
attachment="$(
upload_file \
"$api_base/companies/$company_id/issues/$issue_id/attachments" \
"/api/companies/$company_id/issues/$issue_id/attachments" \
"$file_path" \
"$content_type"
)" || upload_status=$?
@@ -537,7 +527,7 @@ if [[ "$create_work_product" == "1" ]]; then
work_product="$(
request_json \
POST \
"$api_base/issues/$issue_id/work-products" \
"/api/issues/$issue_id/work-products" \
"$work_product_payload"
)"
fi
@@ -553,7 +543,7 @@ if [[ -n "$chat_comment" ]]; then
chat_response="$(
request_json \
POST \
"$api_base/issues/$issue_id/comments" \
"/api/issues/$issue_id/comments" \
"$chat_comment_payload"
)"
chat_comment_id="$(jq -r '.id // empty' <<<"$chat_response")"