@tickernelz/paperclip-pro-adapter-codex-local 2026.925.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/dist/cli/format-event.d.ts +2 -0
- package/dist/cli/format-event.d.ts.map +1 -0
- package/dist/cli/format-event.js +218 -0
- package/dist/cli/format-event.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/quota-probe.d.ts +3 -0
- package/dist/cli/quota-probe.d.ts.map +1 -0
- package/dist/cli/quota-probe.js +104 -0
- package/dist/cli/quota-probe.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +155 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +58 -0
- package/dist/index.test.js.map +1 -0
- package/dist/server/acp.d.ts +30 -0
- package/dist/server/acp.d.ts.map +1 -0
- package/dist/server/acp.js +532 -0
- package/dist/server/acp.js.map +1 -0
- package/dist/server/acp.test.d.ts +2 -0
- package/dist/server/acp.test.d.ts.map +1 -0
- package/dist/server/acp.test.js +1087 -0
- package/dist/server/acp.test.js.map +1 -0
- package/dist/server/adapter-auth-promotion.d.ts +102 -0
- package/dist/server/adapter-auth-promotion.d.ts.map +1 -0
- package/dist/server/adapter-auth-promotion.js +210 -0
- package/dist/server/adapter-auth-promotion.js.map +1 -0
- package/dist/server/adapter-auth-promotion.test.d.ts +2 -0
- package/dist/server/adapter-auth-promotion.test.d.ts.map +1 -0
- package/dist/server/adapter-auth-promotion.test.js +658 -0
- package/dist/server/adapter-auth-promotion.test.js.map +1 -0
- package/dist/server/auth-check.d.ts +8 -0
- package/dist/server/auth-check.d.ts.map +1 -0
- package/dist/server/auth-check.js +8 -0
- package/dist/server/auth-check.js.map +1 -0
- package/dist/server/auth-precedence.d.ts +16 -0
- package/dist/server/auth-precedence.d.ts.map +1 -0
- package/dist/server/auth-precedence.js +20 -0
- package/dist/server/auth-precedence.js.map +1 -0
- package/dist/server/auth-precedence.test.d.ts +2 -0
- package/dist/server/auth-precedence.test.d.ts.map +1 -0
- package/dist/server/auth-precedence.test.js +100 -0
- package/dist/server/auth-precedence.test.js.map +1 -0
- package/dist/server/codex-args.d.ts +13 -0
- package/dist/server/codex-args.d.ts.map +1 -0
- package/dist/server/codex-args.js +76 -0
- package/dist/server/codex-args.js.map +1 -0
- package/dist/server/codex-args.test.d.ts +2 -0
- package/dist/server/codex-args.test.d.ts.map +1 -0
- package/dist/server/codex-args.test.js +243 -0
- package/dist/server/codex-args.test.js.map +1 -0
- package/dist/server/codex-auth-cache.d.ts +206 -0
- package/dist/server/codex-auth-cache.d.ts.map +1 -0
- package/dist/server/codex-auth-cache.js +501 -0
- package/dist/server/codex-auth-cache.js.map +1 -0
- package/dist/server/codex-auth-cache.test.d.ts +2 -0
- package/dist/server/codex-auth-cache.test.d.ts.map +1 -0
- package/dist/server/codex-auth-cache.test.js +497 -0
- package/dist/server/codex-auth-cache.test.js.map +1 -0
- package/dist/server/codex-auth-copyback.d.ts +53 -0
- package/dist/server/codex-auth-copyback.d.ts.map +1 -0
- package/dist/server/codex-auth-copyback.js +123 -0
- package/dist/server/codex-auth-copyback.js.map +1 -0
- package/dist/server/codex-auth-copyback.test.d.ts +2 -0
- package/dist/server/codex-auth-copyback.test.d.ts.map +1 -0
- package/dist/server/codex-auth-copyback.test.js +507 -0
- package/dist/server/codex-auth-copyback.test.js.map +1 -0
- package/dist/server/codex-auth-merge-decision.cjs +170 -0
- package/dist/server/codex-auth-merge-decision.d.ts +22 -0
- package/dist/server/codex-auth-merge-decision.d.ts.map +1 -0
- package/dist/server/codex-auth-merge-decision.js +58 -0
- package/dist/server/codex-auth-merge-decision.js.map +1 -0
- package/dist/server/codex-auth-merge-decision.test.d.ts +2 -0
- package/dist/server/codex-auth-merge-decision.test.d.ts.map +1 -0
- package/dist/server/codex-auth-merge-decision.test.js +230 -0
- package/dist/server/codex-auth-merge-decision.test.js.map +1 -0
- package/dist/server/codex-auth-merge-extract.sh +73 -0
- package/dist/server/codex-auth-merge-scripts.d.ts +21 -0
- package/dist/server/codex-auth-merge-scripts.d.ts.map +1 -0
- package/dist/server/codex-auth-merge-scripts.js +40 -0
- package/dist/server/codex-auth-merge-scripts.js.map +1 -0
- package/dist/server/codex-auth-merge.test.d.ts +2 -0
- package/dist/server/codex-auth-merge.test.d.ts.map +1 -0
- package/dist/server/codex-auth-merge.test.js +714 -0
- package/dist/server/codex-auth-merge.test.js.map +1 -0
- package/dist/server/codex-auth-seed-write.d.ts +30 -0
- package/dist/server/codex-auth-seed-write.d.ts.map +1 -0
- package/dist/server/codex-auth-seed-write.js +41 -0
- package/dist/server/codex-auth-seed-write.js.map +1 -0
- package/dist/server/codex-home.d.ts +162 -0
- package/dist/server/codex-home.d.ts.map +1 -0
- package/dist/server/codex-home.js +771 -0
- package/dist/server/codex-home.js.map +1 -0
- package/dist/server/codex-home.test.d.ts +2 -0
- package/dist/server/codex-home.test.d.ts.map +1 -0
- package/dist/server/codex-home.test.js +1183 -0
- package/dist/server/codex-home.test.js.map +1 -0
- package/dist/server/config-schema.d.ts +3 -0
- package/dist/server/config-schema.d.ts.map +1 -0
- package/dist/server/config-schema.js +67 -0
- package/dist/server/config-schema.js.map +1 -0
- package/dist/server/device-login-export.d.ts +56 -0
- package/dist/server/device-login-export.d.ts.map +1 -0
- package/dist/server/device-login-export.js +230 -0
- package/dist/server/device-login-export.js.map +1 -0
- package/dist/server/device-login-export.test.d.ts +2 -0
- package/dist/server/device-login-export.test.d.ts.map +1 -0
- package/dist/server/device-login-export.test.js +245 -0
- package/dist/server/device-login-export.test.js.map +1 -0
- package/dist/server/device-login-parse.d.ts +20 -0
- package/dist/server/device-login-parse.d.ts.map +1 -0
- package/dist/server/device-login-parse.js +155 -0
- package/dist/server/device-login-parse.js.map +1 -0
- package/dist/server/device-login-parse.test.d.ts +2 -0
- package/dist/server/device-login-parse.test.d.ts.map +1 -0
- package/dist/server/device-login-parse.test.js +269 -0
- package/dist/server/device-login-parse.test.js.map +1 -0
- package/dist/server/device-login-runner.d.ts +69 -0
- package/dist/server/device-login-runner.d.ts.map +1 -0
- package/dist/server/device-login-runner.js +109 -0
- package/dist/server/device-login-runner.js.map +1 -0
- package/dist/server/device-login-runner.test.d.ts +2 -0
- package/dist/server/device-login-runner.test.d.ts.map +1 -0
- package/dist/server/device-login-runner.test.js +201 -0
- package/dist/server/device-login-runner.test.js.map +1 -0
- package/dist/server/engine-availability.test.d.ts +2 -0
- package/dist/server/engine-availability.test.d.ts.map +1 -0
- package/dist/server/engine-availability.test.js +41 -0
- package/dist/server/engine-availability.test.js.map +1 -0
- package/dist/server/execute.acp-fallback.test.d.ts +2 -0
- package/dist/server/execute.acp-fallback.test.d.ts.map +1 -0
- package/dist/server/execute.acp-fallback.test.js +123 -0
- package/dist/server/execute.acp-fallback.test.js.map +1 -0
- package/dist/server/execute.auth-precedence.test.d.ts +2 -0
- package/dist/server/execute.auth-precedence.test.d.ts.map +1 -0
- package/dist/server/execute.auth-precedence.test.js +137 -0
- package/dist/server/execute.auth-precedence.test.js.map +1 -0
- package/dist/server/execute.auth.test.d.ts +2 -0
- package/dist/server/execute.auth.test.d.ts.map +1 -0
- package/dist/server/execute.auth.test.js +197 -0
- package/dist/server/execute.auth.test.js.map +1 -0
- package/dist/server/execute.d.ts +40 -0
- package/dist/server/execute.d.ts.map +1 -0
- package/dist/server/execute.js +1290 -0
- package/dist/server/execute.js.map +1 -0
- package/dist/server/execute.remote.test.d.ts +2 -0
- package/dist/server/execute.remote.test.d.ts.map +1 -0
- package/dist/server/execute.remote.test.js +556 -0
- package/dist/server/execute.remote.test.js.map +1 -0
- package/dist/server/execute.stderr-error.test.d.ts +2 -0
- package/dist/server/execute.stderr-error.test.d.ts.map +1 -0
- package/dist/server/execute.stderr-error.test.js +142 -0
- package/dist/server/execute.stderr-error.test.js.map +1 -0
- package/dist/server/execute.test.d.ts +2 -0
- package/dist/server/execute.test.d.ts.map +1 -0
- package/dist/server/execute.test.js +240 -0
- package/dist/server/execute.test.js.map +1 -0
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +71 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/output-inactivity-monitor.d.ts +62 -0
- package/dist/server/output-inactivity-monitor.d.ts.map +1 -0
- package/dist/server/output-inactivity-monitor.integration.test.d.ts +2 -0
- package/dist/server/output-inactivity-monitor.integration.test.d.ts.map +1 -0
- package/dist/server/output-inactivity-monitor.integration.test.js +136 -0
- package/dist/server/output-inactivity-monitor.integration.test.js.map +1 -0
- package/dist/server/output-inactivity-monitor.js +112 -0
- package/dist/server/output-inactivity-monitor.js.map +1 -0
- package/dist/server/output-inactivity-monitor.test.d.ts +2 -0
- package/dist/server/output-inactivity-monitor.test.d.ts.map +1 -0
- package/dist/server/output-inactivity-monitor.test.js +284 -0
- package/dist/server/output-inactivity-monitor.test.js.map +1 -0
- package/dist/server/parse.d.ts +51 -0
- package/dist/server/parse.d.ts.map +1 -0
- package/dist/server/parse.js +261 -0
- package/dist/server/parse.js.map +1 -0
- package/dist/server/parse.test.d.ts +2 -0
- package/dist/server/parse.test.d.ts.map +1 -0
- package/dist/server/parse.test.js +186 -0
- package/dist/server/parse.test.js.map +1 -0
- package/dist/server/process-activity-monitor.d.ts +21 -0
- package/dist/server/process-activity-monitor.d.ts.map +1 -0
- package/dist/server/process-activity-monitor.js +103 -0
- package/dist/server/process-activity-monitor.js.map +1 -0
- package/dist/server/process-activity-monitor.test.d.ts +2 -0
- package/dist/server/process-activity-monitor.test.d.ts.map +1 -0
- package/dist/server/process-activity-monitor.test.js +82 -0
- package/dist/server/process-activity-monitor.test.js.map +1 -0
- package/dist/server/quota-spawn-error.test.d.ts +2 -0
- package/dist/server/quota-spawn-error.test.d.ts.map +1 -0
- package/dist/server/quota-spawn-error.test.js +174 -0
- package/dist/server/quota-spawn-error.test.js.map +1 -0
- package/dist/server/quota.d.ts +66 -0
- package/dist/server/quota.d.ts.map +1 -0
- package/dist/server/quota.js +511 -0
- package/dist/server/quota.js.map +1 -0
- package/dist/server/runtime-config.d.ts +11 -0
- package/dist/server/runtime-config.d.ts.map +1 -0
- package/dist/server/runtime-config.js +380 -0
- package/dist/server/runtime-config.js.map +1 -0
- package/dist/server/runtime-config.test.d.ts +2 -0
- package/dist/server/runtime-config.test.d.ts.map +1 -0
- package/dist/server/runtime-config.test.js +367 -0
- package/dist/server/runtime-config.test.js.map +1 -0
- package/dist/server/skills.d.ts +8 -0
- package/dist/server/skills.d.ts.map +1 -0
- package/dist/server/skills.js +26 -0
- package/dist/server/skills.js.map +1 -0
- package/dist/server/test.d.ts +3 -0
- package/dist/server/test.d.ts.map +1 -0
- package/dist/server/test.js +458 -0
- package/dist/server/test.js.map +1 -0
- package/dist/server/test.remote.test.d.ts +2 -0
- package/dist/server/test.remote.test.d.ts.map +1 -0
- package/dist/server/test.remote.test.js +396 -0
- package/dist/server/test.remote.test.js.map +1 -0
- package/dist/ui/build-config.d.ts +5 -0
- package/dist/ui/build-config.d.ts.map +1 -0
- package/dist/ui/build-config.js +229 -0
- package/dist/ui/build-config.js.map +1 -0
- package/dist/ui/build-config.test.d.ts +2 -0
- package/dist/ui/build-config.test.d.ts.map +1 -0
- package/dist/ui/build-config.test.js +272 -0
- package/dist/ui/build-config.test.js.map +1 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +8 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/parse-stdout.d.ts +3 -0
- package/dist/ui/parse-stdout.d.ts.map +1 -0
- package/dist/ui/parse-stdout.js +265 -0
- package/dist/ui/parse-stdout.js.map +1 -0
- package/dist/ui/parse-stdout.test.d.ts +2 -0
- package/dist/ui/parse-stdout.test.d.ts.map +1 -0
- package/dist/ui/parse-stdout.test.js +77 -0
- package/dist/ui/parse-stdout.test.js.map +1 -0
- package/package.json +61 -0
- package/skills/agentmail/SKILL.md +70 -0
- package/skills/paperclip/SKILL.md +712 -0
- package/skills/paperclip/references/api-reference.md +1675 -0
- package/skills/paperclip/references/artifacts.md +158 -0
- package/skills/paperclip/references/cases.md +295 -0
- package/skills/paperclip/references/company-skills.md +266 -0
- package/skills/paperclip/references/issue-workspaces.md +80 -0
- package/skills/paperclip/references/routines.md +231 -0
- package/skills/paperclip/references/workflows.md +141 -0
- package/skills/paperclip/scripts/paperclip-upload-artifact.sh +592 -0
- package/skills/paperclip-board/SKILL.md +619 -0
- package/skills/paperclip-converting-plans-to-tasks/SKILL.md +60 -0
- package/skills/paperclip-create-agent/SKILL.md +179 -0
- package/skills/paperclip-create-agent/references/agent-instruction-templates.md +123 -0
- package/skills/paperclip-create-agent/references/agents/coder.md +64 -0
- package/skills/paperclip-create-agent/references/agents/qa.md +88 -0
- package/skills/paperclip-create-agent/references/agents/securityengineer.md +135 -0
- package/skills/paperclip-create-agent/references/agents/uxdesigner.md +115 -0
- package/skills/paperclip-create-agent/references/api-reference.md +110 -0
- package/skills/paperclip-create-agent/references/baseline-role-guide.md +168 -0
- package/skills/paperclip-create-agent/references/draft-review-checklist.md +95 -0
- package/skills/para-memory-files/SKILL.md +100 -0
- package/skills/para-memory-files/references/schemas.md +35 -0
- package/skills/slack/SKILL.md +65 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
usage() {
|
|
6
|
+
cat <<'EOF'
|
|
7
|
+
Usage:
|
|
8
|
+
paperclip-upload-artifact.sh FILE [options]
|
|
9
|
+
|
|
10
|
+
Uploads a generated file from the current workspace to the current Paperclip
|
|
11
|
+
issue, then creates an attachment-backed artifact work product by default.
|
|
12
|
+
|
|
13
|
+
Required environment for live uploads:
|
|
14
|
+
PAPERCLIP_API_URL, PAPERCLIP_API_KEY, PAPERCLIP_COMPANY_ID, PAPERCLIP_TASK_ID, PAPERCLIP_RUN_ID
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--issue-id ID Issue id to attach to (default: PAPERCLIP_TASK_ID)
|
|
18
|
+
--company-id ID Company id (default: PAPERCLIP_COMPANY_ID)
|
|
19
|
+
--title TEXT Work product title (default: file basename)
|
|
20
|
+
--summary TEXT Work product summary
|
|
21
|
+
--content-type TYPE Override detected upload content type
|
|
22
|
+
--status STATUS Work product status (default: ready_for_review)
|
|
23
|
+
--chat-comment TEXT Bind this file to an explicit external-chat response
|
|
24
|
+
--retry-unknown-upload Retry after an unresolved transport failure (duplicate risk)
|
|
25
|
+
--no-work-product Only upload the issue attachment
|
|
26
|
+
--no-primary Do not mark the artifact work product primary for its type
|
|
27
|
+
--output FORMAT markdown or json (default: markdown)
|
|
28
|
+
--dry-run Print resolved upload settings without calling the API
|
|
29
|
+
--help, -h Show this help
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
bash scripts/paperclip-upload-artifact.sh dist/demo.mp4 \
|
|
33
|
+
--title "Demo video render" \
|
|
34
|
+
--summary "MP4 render for board review"
|
|
35
|
+
|
|
36
|
+
bash scripts/paperclip-upload-artifact.sh out/walkthrough.webm \
|
|
37
|
+
--title "Walkthrough video" \
|
|
38
|
+
--content-type video/webm
|
|
39
|
+
|
|
40
|
+
bash scripts/paperclip-upload-artifact.sh out/result.png \
|
|
41
|
+
--title "Generated image" \
|
|
42
|
+
--chat-comment "Here is the requested image."
|
|
43
|
+
EOF
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
require_command() {
|
|
47
|
+
if ! command -v "$1" >/dev/null 2>&1; then
|
|
48
|
+
printf 'Missing required command: %s\n' "$1" >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
json_bool() {
|
|
54
|
+
if [[ "${1:-0}" == "1" ]]; then
|
|
55
|
+
printf 'true'
|
|
56
|
+
else
|
|
57
|
+
printf 'false'
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
detect_content_type() {
|
|
62
|
+
local path="$1"
|
|
63
|
+
local lower
|
|
64
|
+
lower="$(printf '%s' "$path" | tr '[:upper:]' '[:lower:]')"
|
|
65
|
+
|
|
66
|
+
case "$lower" in
|
|
67
|
+
*.mp4|*.m4v) printf 'video/mp4' ;;
|
|
68
|
+
*.webm) printf 'video/webm' ;;
|
|
69
|
+
*.mov|*.qt) printf 'video/quicktime' ;;
|
|
70
|
+
*.png) printf 'image/png' ;;
|
|
71
|
+
*.jpg|*.jpeg) printf 'image/jpeg' ;;
|
|
72
|
+
*.gif) printf 'image/gif' ;;
|
|
73
|
+
*.webp) printf 'image/webp' ;;
|
|
74
|
+
*.svg) printf 'image/svg+xml' ;;
|
|
75
|
+
*.pdf) printf 'application/pdf' ;;
|
|
76
|
+
*.txt|*.log) printf 'text/plain' ;;
|
|
77
|
+
*.md|*.markdown) printf 'text/markdown' ;;
|
|
78
|
+
*.json) printf 'application/json' ;;
|
|
79
|
+
*.csv) printf 'text/csv' ;;
|
|
80
|
+
*.html|*.htm) printf 'text/html' ;;
|
|
81
|
+
*.zip) printf 'application/zip' ;;
|
|
82
|
+
*)
|
|
83
|
+
if command -v file >/dev/null 2>&1; then
|
|
84
|
+
file --brief --mime-type "$path"
|
|
85
|
+
else
|
|
86
|
+
printf 'application/octet-stream'
|
|
87
|
+
fi
|
|
88
|
+
;;
|
|
89
|
+
esac
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
sha256_file() {
|
|
93
|
+
local path="$1"
|
|
94
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
95
|
+
sha256sum -- "$path" | awk '{print tolower($1)}'
|
|
96
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
97
|
+
shasum -a 256 -- "$path" | awk '{print tolower($1)}'
|
|
98
|
+
else
|
|
99
|
+
printf 'Missing required command: sha256sum or shasum\n' >&2
|
|
100
|
+
exit 1
|
|
101
|
+
fi
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
sha256_text() {
|
|
105
|
+
local value="$1"
|
|
106
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
107
|
+
printf '%s' "$value" | sha256sum | awk '{print tolower($1)}'
|
|
108
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
109
|
+
printf '%s' "$value" | shasum -a 256 | awk '{print tolower($1)}'
|
|
110
|
+
else
|
|
111
|
+
printf 'Missing required command: sha256sum or shasum\n' >&2
|
|
112
|
+
exit 1
|
|
113
|
+
fi
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
request_json() {
|
|
117
|
+
local method="$1"
|
|
118
|
+
local url="$2"
|
|
119
|
+
local body="${3:-}"
|
|
120
|
+
local response_file
|
|
121
|
+
local status_code
|
|
122
|
+
|
|
123
|
+
response_file="$(mktemp)"
|
|
124
|
+
if [[ -n "$body" ]]; then
|
|
125
|
+
status_code="$(
|
|
126
|
+
curl -sS -X "$method" -w '%{http_code}' -o "$response_file" \
|
|
127
|
+
"$url" \
|
|
128
|
+
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
|
129
|
+
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
|
|
130
|
+
-H 'Content-Type: application/json' \
|
|
131
|
+
--data-binary "$body"
|
|
132
|
+
)"
|
|
133
|
+
else
|
|
134
|
+
status_code="$(
|
|
135
|
+
curl -sS -X "$method" -w '%{http_code}' -o "$response_file" \
|
|
136
|
+
"$url" \
|
|
137
|
+
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
|
138
|
+
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID"
|
|
139
|
+
)"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
if [[ "$status_code" -lt 200 || "$status_code" -ge 300 ]]; then
|
|
143
|
+
printf 'Request failed (%s): %s\n' "$status_code" "$url" >&2
|
|
144
|
+
cat "$response_file" >&2
|
|
145
|
+
printf '\n' >&2
|
|
146
|
+
rm -f "$response_file"
|
|
147
|
+
exit 1
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
cat "$response_file"
|
|
151
|
+
rm -f "$response_file"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
upload_file() {
|
|
155
|
+
local url="$1"
|
|
156
|
+
local path="$2"
|
|
157
|
+
local content_type="$3"
|
|
158
|
+
local escaped_path
|
|
159
|
+
local response_file
|
|
160
|
+
local status_code
|
|
161
|
+
local curl_status=0
|
|
162
|
+
local indeterminate=0
|
|
163
|
+
|
|
164
|
+
escaped_path="${path//\\/\\\\}"
|
|
165
|
+
escaped_path="${escaped_path//\"/\\\"}"
|
|
166
|
+
response_file="$(mktemp)"
|
|
167
|
+
status_code="$(
|
|
168
|
+
curl -sS -X POST -w '%{http_code}' -o "$response_file" \
|
|
169
|
+
"$url" \
|
|
170
|
+
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
|
171
|
+
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
|
|
172
|
+
-F "file=@\"${escaped_path}\";type=${content_type}"
|
|
173
|
+
)" || curl_status=$?
|
|
174
|
+
|
|
175
|
+
if [[ "$curl_status" -ne 0 ]]; then
|
|
176
|
+
rm -f "$response_file"
|
|
177
|
+
return 75
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
if [[ "$status_code" -lt 200 || "$status_code" -ge 300 ]]; then
|
|
181
|
+
printf 'Upload failed (%s): %s\n' "$status_code" "$url" >&2
|
|
182
|
+
cat "$response_file" >&2
|
|
183
|
+
printf '\n' >&2
|
|
184
|
+
# The bridge uses a structured 409 outcome after a possibly committed
|
|
185
|
+
# mutation. Keep the marker so the next invocation reconciles by listing.
|
|
186
|
+
if [[ "$status_code" == "409" ]] && jq -e '.outcome == "indeterminate"' "$response_file" >/dev/null 2>&1; then
|
|
187
|
+
indeterminate=1
|
|
188
|
+
fi
|
|
189
|
+
rm -f "$response_file"
|
|
190
|
+
if [[ "$status_code" == "408" || "$status_code" -ge 500 || "$indeterminate" == "1" ]]; then
|
|
191
|
+
return 75
|
|
192
|
+
fi
|
|
193
|
+
return 1
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
cat "$response_file"
|
|
197
|
+
rm -f "$response_file"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
operation_lock_path=""
|
|
201
|
+
operation_lock_owner=""
|
|
202
|
+
operation_lock_held=0
|
|
203
|
+
operation_state_root=""
|
|
204
|
+
|
|
205
|
+
process_start_identity() {
|
|
206
|
+
local pid="$1"
|
|
207
|
+
ps -p "$pid" -o lstart= 2>/dev/null | tr -s '[:space:]' ' ' | sed 's/^ //;s/ $//'
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
release_operation_lock() {
|
|
211
|
+
if [[ "$operation_lock_held" == "1" && -n "$operation_lock_path" ]]; then
|
|
212
|
+
local current_owner=""
|
|
213
|
+
current_owner="$(readlink "$operation_lock_path" 2>/dev/null || true)"
|
|
214
|
+
if [[ "$current_owner" == "$operation_lock_owner" ]]; then
|
|
215
|
+
rm -f "$operation_lock_path"
|
|
216
|
+
fi
|
|
217
|
+
operation_lock_held=0
|
|
218
|
+
fi
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
acquire_operation_lock() {
|
|
222
|
+
local operation_key="$1"
|
|
223
|
+
local attempts=0
|
|
224
|
+
|
|
225
|
+
umask 077
|
|
226
|
+
operation_state_root="${PAPERCLIP_HELPER_STATE_DIR:-${TMPDIR:-/tmp}/paperclip-upload-artifact}"
|
|
227
|
+
mkdir -p "$operation_state_root"
|
|
228
|
+
operation_lock_path="$operation_state_root/$operation_key.lock"
|
|
229
|
+
operation_lock_owner="$$|$(process_start_identity "$$" || true)"
|
|
230
|
+
while ! ln -s "$operation_lock_owner" "$operation_lock_path" 2>/dev/null; do
|
|
231
|
+
local owner_pid=""
|
|
232
|
+
local owner_start=""
|
|
233
|
+
local current_lock_owner=""
|
|
234
|
+
current_lock_owner="$(readlink "$operation_lock_path" 2>/dev/null || true)"
|
|
235
|
+
IFS='|' read -r owner_pid owner_start <<<"$current_lock_owner"
|
|
236
|
+
if [[ "$owner_pid" =~ ^[0-9]+$ ]]; then
|
|
237
|
+
local current_owner_start=""
|
|
238
|
+
current_owner_start="$(process_start_identity "$owner_pid" || true)"
|
|
239
|
+
if ! kill -0 "$owner_pid" 2>/dev/null ||
|
|
240
|
+
[[ -n "$owner_start" && -n "$current_owner_start" && "$owner_start" != "$current_owner_start" ]]; then
|
|
241
|
+
# Serialize stale-lock reclamation separately. Without this guard, two
|
|
242
|
+
# contenders can both observe the old owner and the slower one can
|
|
243
|
+
# delete the faster contender's newly acquired live lock.
|
|
244
|
+
local reclaim_lock_path="$operation_lock_path.reclaim"
|
|
245
|
+
if mkdir "$reclaim_lock_path" 2>/dev/null; then
|
|
246
|
+
local guarded_owner=""
|
|
247
|
+
guarded_owner="$(readlink "$operation_lock_path" 2>/dev/null || true)"
|
|
248
|
+
if [[ "$guarded_owner" == "$current_lock_owner" ]]; then
|
|
249
|
+
rm -f "$operation_lock_path"
|
|
250
|
+
fi
|
|
251
|
+
rmdir "$reclaim_lock_path" 2>/dev/null || true
|
|
252
|
+
continue
|
|
253
|
+
fi
|
|
254
|
+
fi
|
|
255
|
+
fi
|
|
256
|
+
attempts=$((attempts + 1))
|
|
257
|
+
if [[ "$attempts" -ge 400 ]]; then
|
|
258
|
+
printf 'Another matching artifact upload is still in progress; retry after it finishes.\n' >&2
|
|
259
|
+
exit 1
|
|
260
|
+
fi
|
|
261
|
+
sleep 0.05
|
|
262
|
+
done
|
|
263
|
+
operation_lock_held=1
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
file_path=""
|
|
267
|
+
issue_id="${PAPERCLIP_TASK_ID:-}"
|
|
268
|
+
company_id="${PAPERCLIP_COMPANY_ID:-}"
|
|
269
|
+
title=""
|
|
270
|
+
summary=""
|
|
271
|
+
content_type=""
|
|
272
|
+
status="ready_for_review"
|
|
273
|
+
chat_comment=""
|
|
274
|
+
create_work_product=1
|
|
275
|
+
is_primary=1
|
|
276
|
+
output_format="markdown"
|
|
277
|
+
dry_run=0
|
|
278
|
+
retry_unknown_upload=0
|
|
279
|
+
|
|
280
|
+
while [[ $# -gt 0 ]]; do
|
|
281
|
+
case "$1" in
|
|
282
|
+
--issue-id)
|
|
283
|
+
issue_id="${2:-}"
|
|
284
|
+
shift 2
|
|
285
|
+
;;
|
|
286
|
+
--company-id)
|
|
287
|
+
company_id="${2:-}"
|
|
288
|
+
shift 2
|
|
289
|
+
;;
|
|
290
|
+
--title)
|
|
291
|
+
title="${2:-}"
|
|
292
|
+
shift 2
|
|
293
|
+
;;
|
|
294
|
+
--summary)
|
|
295
|
+
summary="${2:-}"
|
|
296
|
+
shift 2
|
|
297
|
+
;;
|
|
298
|
+
--content-type)
|
|
299
|
+
content_type="${2:-}"
|
|
300
|
+
shift 2
|
|
301
|
+
;;
|
|
302
|
+
--status)
|
|
303
|
+
status="${2:-}"
|
|
304
|
+
shift 2
|
|
305
|
+
;;
|
|
306
|
+
--chat-comment)
|
|
307
|
+
chat_comment="${2:-}"
|
|
308
|
+
shift 2
|
|
309
|
+
;;
|
|
310
|
+
--no-work-product)
|
|
311
|
+
create_work_product=0
|
|
312
|
+
shift
|
|
313
|
+
;;
|
|
314
|
+
--retry-unknown-upload)
|
|
315
|
+
retry_unknown_upload=1
|
|
316
|
+
shift
|
|
317
|
+
;;
|
|
318
|
+
--no-primary)
|
|
319
|
+
is_primary=0
|
|
320
|
+
shift
|
|
321
|
+
;;
|
|
322
|
+
--output)
|
|
323
|
+
output_format="${2:-}"
|
|
324
|
+
shift 2
|
|
325
|
+
;;
|
|
326
|
+
--dry-run)
|
|
327
|
+
dry_run=1
|
|
328
|
+
shift
|
|
329
|
+
;;
|
|
330
|
+
--help|-h)
|
|
331
|
+
usage
|
|
332
|
+
exit 0
|
|
333
|
+
;;
|
|
334
|
+
--*)
|
|
335
|
+
printf 'Unknown argument: %s\n' "$1" >&2
|
|
336
|
+
usage >&2
|
|
337
|
+
exit 1
|
|
338
|
+
;;
|
|
339
|
+
*)
|
|
340
|
+
if [[ -n "$file_path" ]]; then
|
|
341
|
+
printf 'Unexpected positional argument: %s\n' "$1" >&2
|
|
342
|
+
usage >&2
|
|
343
|
+
exit 1
|
|
344
|
+
fi
|
|
345
|
+
file_path="$1"
|
|
346
|
+
shift
|
|
347
|
+
;;
|
|
348
|
+
esac
|
|
349
|
+
done
|
|
350
|
+
|
|
351
|
+
if [[ -z "$file_path" ]]; then
|
|
352
|
+
printf 'Missing file path.\n' >&2
|
|
353
|
+
usage >&2
|
|
354
|
+
exit 1
|
|
355
|
+
fi
|
|
356
|
+
|
|
357
|
+
if [[ ! -f "$file_path" ]]; then
|
|
358
|
+
printf 'Artifact file does not exist: %s\n' "$file_path" >&2
|
|
359
|
+
exit 1
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
if [[ "$output_format" != "markdown" && "$output_format" != "json" ]]; then
|
|
363
|
+
printf 'Unsupported output format: %s\n' "$output_format" >&2
|
|
364
|
+
exit 1
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
if [[ -n "$chat_comment" && "$create_work_product" != "1" ]]; then
|
|
368
|
+
printf '%s\n' '--chat-comment requires the attachment-backed work product created by this helper.' >&2
|
|
369
|
+
exit 1
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
require_command curl
|
|
373
|
+
require_command jq
|
|
374
|
+
|
|
375
|
+
if [[ -z "$title" ]]; then
|
|
376
|
+
title="$(basename "$file_path")"
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
if [[ -z "$content_type" ]]; then
|
|
380
|
+
content_type="$(detect_content_type "$file_path")"
|
|
381
|
+
fi
|
|
382
|
+
|
|
383
|
+
if [[ "$dry_run" == "1" ]]; then
|
|
384
|
+
create_work_product_json="$(json_bool "$create_work_product")"
|
|
385
|
+
is_primary_json="$(json_bool "$is_primary")"
|
|
386
|
+
jq -n \
|
|
387
|
+
--arg file "$file_path" \
|
|
388
|
+
--arg issueId "$issue_id" \
|
|
389
|
+
--arg companyId "$company_id" \
|
|
390
|
+
--arg title "$title" \
|
|
391
|
+
--arg summary "$summary" \
|
|
392
|
+
--arg contentType "$content_type" \
|
|
393
|
+
--arg status "$status" \
|
|
394
|
+
--arg chatComment "$chat_comment" \
|
|
395
|
+
--argjson createWorkProduct "$create_work_product_json" \
|
|
396
|
+
--argjson isPrimary "$is_primary_json" \
|
|
397
|
+
'{file: $file, issueId: $issueId, companyId: $companyId, title: $title, summary: $summary, contentType: $contentType, status: $status, chatComment: (if $chatComment == "" then null else $chatComment end), createWorkProduct: $createWorkProduct, isPrimary: $isPrimary}'
|
|
398
|
+
exit 0
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
if [[ -z "${PAPERCLIP_API_URL:-}" || -z "${PAPERCLIP_API_KEY:-}" || -z "${PAPERCLIP_RUN_ID:-}" ]]; then
|
|
402
|
+
printf 'Missing PAPERCLIP_API_URL, PAPERCLIP_API_KEY, or PAPERCLIP_RUN_ID.\n' >&2
|
|
403
|
+
exit 1
|
|
404
|
+
fi
|
|
405
|
+
|
|
406
|
+
if [[ -z "$issue_id" || -z "$company_id" ]]; then
|
|
407
|
+
printf 'Missing issue or company id. Pass --issue-id/--company-id or set PAPERCLIP_TASK_ID/PAPERCLIP_COMPANY_ID.\n' >&2
|
|
408
|
+
exit 1
|
|
409
|
+
fi
|
|
410
|
+
|
|
411
|
+
api_root="${PAPERCLIP_API_URL%/}"
|
|
412
|
+
case "$api_root" in
|
|
413
|
+
*/api) api_base="$api_root" ;;
|
|
414
|
+
*) api_base="$api_root/api" ;;
|
|
415
|
+
esac
|
|
416
|
+
file_sha256="$(sha256_file "$file_path")"
|
|
417
|
+
original_filename="$(basename "$file_path")"
|
|
418
|
+
operation_key="$(
|
|
419
|
+
sha256_text "$api_base|$company_id|$issue_id|$PAPERCLIP_RUN_ID|$original_filename|$file_sha256|$content_type"
|
|
420
|
+
)"
|
|
421
|
+
acquire_operation_lock "$operation_key"
|
|
422
|
+
trap release_operation_lock EXIT
|
|
423
|
+
|
|
424
|
+
attachment=""
|
|
425
|
+
reused_attachment=0
|
|
426
|
+
unknown_upload_marker="$operation_state_root/$operation_key.uncertain"
|
|
427
|
+
lookup_attempts=1
|
|
428
|
+
if [[ -f "$unknown_upload_marker" && "$retry_unknown_upload" != "1" ]]; then
|
|
429
|
+
lookup_attempts=20
|
|
430
|
+
fi
|
|
431
|
+
for ((lookup_attempt = 1; lookup_attempt <= lookup_attempts; lookup_attempt++)); do
|
|
432
|
+
existing_attachments="$(request_json GET "$api_base/issues/$issue_id/attachments")"
|
|
433
|
+
attachment="$(
|
|
434
|
+
jq -nc \
|
|
435
|
+
--argjson attachments "$existing_attachments" \
|
|
436
|
+
--arg runId "$PAPERCLIP_RUN_ID" \
|
|
437
|
+
--arg sha256 "$file_sha256" \
|
|
438
|
+
--arg originalFilename "$original_filename" \
|
|
439
|
+
--arg contentType "$content_type" \
|
|
440
|
+
'first(
|
|
441
|
+
$attachments[]
|
|
442
|
+
| select(
|
|
443
|
+
.originatingRunId == $runId
|
|
444
|
+
and ((.sha256 // "") | ascii_downcase) == ($sha256 | ascii_downcase)
|
|
445
|
+
and (.originalFilename // "") == $originalFilename
|
|
446
|
+
and ((.contentType // "") | ascii_downcase) == ($contentType | ascii_downcase)
|
|
447
|
+
)
|
|
448
|
+
) // empty'
|
|
449
|
+
)"
|
|
450
|
+
if [[ -n "$attachment" ]]; then
|
|
451
|
+
reused_attachment=1
|
|
452
|
+
rm -f "$unknown_upload_marker"
|
|
453
|
+
break
|
|
454
|
+
fi
|
|
455
|
+
if [[ "$lookup_attempt" -lt "$lookup_attempts" ]]; then
|
|
456
|
+
sleep 0.25
|
|
457
|
+
fi
|
|
458
|
+
done
|
|
459
|
+
|
|
460
|
+
if [[ -z "$attachment" && -f "$unknown_upload_marker" && "$retry_unknown_upload" != "1" ]]; then
|
|
461
|
+
printf '%s\n' 'A previous matching upload ended without a definitive response, and Paperclip has not exposed its durable attachment yet.' >&2
|
|
462
|
+
printf '%s\n' 'Retry this command later. If the upload definitely did not commit, pass --retry-unknown-upload to accept the duplicate-file risk.' >&2
|
|
463
|
+
exit 1
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
if [[ -z "$attachment" ]]; then
|
|
467
|
+
rm -f "$unknown_upload_marker"
|
|
468
|
+
: >"$unknown_upload_marker"
|
|
469
|
+
upload_status=0
|
|
470
|
+
attachment="$(
|
|
471
|
+
upload_file \
|
|
472
|
+
"$api_base/companies/$company_id/issues/$issue_id/attachments" \
|
|
473
|
+
"$file_path" \
|
|
474
|
+
"$content_type"
|
|
475
|
+
)" || upload_status=$?
|
|
476
|
+
if [[ "$upload_status" -ne 0 ]]; then
|
|
477
|
+
if [[ "$upload_status" -ne 75 ]]; then
|
|
478
|
+
rm -f "$unknown_upload_marker"
|
|
479
|
+
fi
|
|
480
|
+
exit 1
|
|
481
|
+
fi
|
|
482
|
+
fi
|
|
483
|
+
|
|
484
|
+
attachment_id="$(jq -r '.id // empty' <<<"$attachment")"
|
|
485
|
+
content_path="$(jq -r '.contentPath // empty' <<<"$attachment")"
|
|
486
|
+
download_path="$(jq -r '.downloadPath // (if .contentPath then (.contentPath + "?download=1") else "" end)' <<<"$attachment")"
|
|
487
|
+
if [[ -z "$attachment_id" || -z "$content_path" || -z "$download_path" ]]; then
|
|
488
|
+
printf 'Upload response did not include attachment path metadata.\n' >&2
|
|
489
|
+
printf '%s\n' "$attachment" >&2
|
|
490
|
+
exit 1
|
|
491
|
+
fi
|
|
492
|
+
rm -f "$unknown_upload_marker"
|
|
493
|
+
|
|
494
|
+
work_product="null"
|
|
495
|
+
if [[ "$create_work_product" == "1" ]]; then
|
|
496
|
+
is_primary_json="$(json_bool "$is_primary")"
|
|
497
|
+
byte_size="$(jq -r '.byteSize // 0' <<<"$attachment")"
|
|
498
|
+
open_path="$(jq -r '.openPath // .contentPath // empty' <<<"$attachment")"
|
|
499
|
+
original_filename="$(jq -r '.originalFilename // empty' <<<"$attachment")"
|
|
500
|
+
|
|
501
|
+
work_product_payload="$(
|
|
502
|
+
jq -nc \
|
|
503
|
+
--arg title "$title" \
|
|
504
|
+
--arg summary "$summary" \
|
|
505
|
+
--arg status "$status" \
|
|
506
|
+
--arg runId "$PAPERCLIP_RUN_ID" \
|
|
507
|
+
--arg attachmentId "$attachment_id" \
|
|
508
|
+
--arg contentType "$content_type" \
|
|
509
|
+
--argjson byteSize "$byte_size" \
|
|
510
|
+
--arg contentPath "$content_path" \
|
|
511
|
+
--arg openPath "$open_path" \
|
|
512
|
+
--arg downloadPath "$download_path" \
|
|
513
|
+
--arg originalFilename "$original_filename" \
|
|
514
|
+
--argjson isPrimary "$is_primary_json" \
|
|
515
|
+
'{
|
|
516
|
+
type: "artifact",
|
|
517
|
+
provider: "paperclip",
|
|
518
|
+
title: $title,
|
|
519
|
+
status: $status,
|
|
520
|
+
reviewState: "none",
|
|
521
|
+
isPrimary: $isPrimary,
|
|
522
|
+
healthStatus: "unknown",
|
|
523
|
+
summary: (if $summary == "" then null else $summary end),
|
|
524
|
+
createdByRunId: $runId,
|
|
525
|
+
metadata: {
|
|
526
|
+
attachmentId: $attachmentId,
|
|
527
|
+
contentType: $contentType,
|
|
528
|
+
byteSize: $byteSize,
|
|
529
|
+
contentPath: $contentPath,
|
|
530
|
+
openPath: $openPath,
|
|
531
|
+
downloadPath: $downloadPath,
|
|
532
|
+
originalFilename: (if $originalFilename == "" then null else $originalFilename end)
|
|
533
|
+
}
|
|
534
|
+
}'
|
|
535
|
+
)"
|
|
536
|
+
|
|
537
|
+
work_product="$(
|
|
538
|
+
request_json \
|
|
539
|
+
POST \
|
|
540
|
+
"$api_base/issues/$issue_id/work-products" \
|
|
541
|
+
"$work_product_payload"
|
|
542
|
+
)"
|
|
543
|
+
fi
|
|
544
|
+
|
|
545
|
+
chat_response="null"
|
|
546
|
+
if [[ -n "$chat_comment" ]]; then
|
|
547
|
+
chat_comment_payload="$(
|
|
548
|
+
jq -nc \
|
|
549
|
+
--arg body "$chat_comment" \
|
|
550
|
+
--arg attachmentId "$attachment_id" \
|
|
551
|
+
'{body: $body, attachmentIds: [$attachmentId]}'
|
|
552
|
+
)"
|
|
553
|
+
chat_response="$(
|
|
554
|
+
request_json \
|
|
555
|
+
POST \
|
|
556
|
+
"$api_base/issues/$issue_id/comments" \
|
|
557
|
+
"$chat_comment_payload"
|
|
558
|
+
)"
|
|
559
|
+
chat_comment_id="$(jq -r '.id // empty' <<<"$chat_response")"
|
|
560
|
+
if [[ -z "$chat_comment_id" ]]; then
|
|
561
|
+
printf 'Chat attachment response did not include a comment id.\n' >&2
|
|
562
|
+
exit 1
|
|
563
|
+
fi
|
|
564
|
+
fi
|
|
565
|
+
|
|
566
|
+
if [[ "$output_format" == "json" ]]; then
|
|
567
|
+
jq -n \
|
|
568
|
+
--argjson attachment "$attachment" \
|
|
569
|
+
--argjson workProduct "$work_product" \
|
|
570
|
+
--argjson chatComment "$chat_response" \
|
|
571
|
+
'{attachment: $attachment, workProduct: $workProduct, chatComment: $chatComment}'
|
|
572
|
+
exit 0
|
|
573
|
+
fi
|
|
574
|
+
|
|
575
|
+
work_product_id="$(jq -r '.id // empty' <<<"$work_product")"
|
|
576
|
+
|
|
577
|
+
if [[ "$reused_attachment" == "1" ]]; then
|
|
578
|
+
printf 'Reused matching artifact from this run\n\n'
|
|
579
|
+
else
|
|
580
|
+
printf 'Uploaded artifact\n\n'
|
|
581
|
+
fi
|
|
582
|
+
printf -- '- Attachment: [%s](%s)\n' "$title" "$content_path"
|
|
583
|
+
printf -- '- Download: [%s](%s)\n' "$title" "$download_path"
|
|
584
|
+
printf -- '- Attachment ID: `%s`\n' "$attachment_id"
|
|
585
|
+
if [[ -n "$work_product_id" ]]; then
|
|
586
|
+
printf -- '- Work product ID: `%s`\n' "$work_product_id"
|
|
587
|
+
fi
|
|
588
|
+
if [[ -n "$chat_comment" ]]; then
|
|
589
|
+
printf -- '- Paperclip comment binding: saved. External publication requires an authorized active chat origin; this helper does not confirm provider delivery.\n'
|
|
590
|
+
fi
|
|
591
|
+
printf '\nFinal comment snippet:\n\n'
|
|
592
|
+
printf -- '- Artifact: [%s](%s)\n' "$title" "$content_path"
|