@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.
Files changed (268) hide show
  1. package/LICENSE +22 -0
  2. package/dist/cli/format-event.d.ts +2 -0
  3. package/dist/cli/format-event.d.ts.map +1 -0
  4. package/dist/cli/format-event.js +218 -0
  5. package/dist/cli/format-event.js.map +1 -0
  6. package/dist/cli/index.d.ts +2 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +2 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/quota-probe.d.ts +3 -0
  11. package/dist/cli/quota-probe.d.ts.map +1 -0
  12. package/dist/cli/quota-probe.js +104 -0
  13. package/dist/cli/quota-probe.js.map +1 -0
  14. package/dist/index.d.ts +21 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +155 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/index.test.d.ts +2 -0
  19. package/dist/index.test.d.ts.map +1 -0
  20. package/dist/index.test.js +58 -0
  21. package/dist/index.test.js.map +1 -0
  22. package/dist/server/acp.d.ts +30 -0
  23. package/dist/server/acp.d.ts.map +1 -0
  24. package/dist/server/acp.js +532 -0
  25. package/dist/server/acp.js.map +1 -0
  26. package/dist/server/acp.test.d.ts +2 -0
  27. package/dist/server/acp.test.d.ts.map +1 -0
  28. package/dist/server/acp.test.js +1087 -0
  29. package/dist/server/acp.test.js.map +1 -0
  30. package/dist/server/adapter-auth-promotion.d.ts +102 -0
  31. package/dist/server/adapter-auth-promotion.d.ts.map +1 -0
  32. package/dist/server/adapter-auth-promotion.js +210 -0
  33. package/dist/server/adapter-auth-promotion.js.map +1 -0
  34. package/dist/server/adapter-auth-promotion.test.d.ts +2 -0
  35. package/dist/server/adapter-auth-promotion.test.d.ts.map +1 -0
  36. package/dist/server/adapter-auth-promotion.test.js +658 -0
  37. package/dist/server/adapter-auth-promotion.test.js.map +1 -0
  38. package/dist/server/auth-check.d.ts +8 -0
  39. package/dist/server/auth-check.d.ts.map +1 -0
  40. package/dist/server/auth-check.js +8 -0
  41. package/dist/server/auth-check.js.map +1 -0
  42. package/dist/server/auth-precedence.d.ts +16 -0
  43. package/dist/server/auth-precedence.d.ts.map +1 -0
  44. package/dist/server/auth-precedence.js +20 -0
  45. package/dist/server/auth-precedence.js.map +1 -0
  46. package/dist/server/auth-precedence.test.d.ts +2 -0
  47. package/dist/server/auth-precedence.test.d.ts.map +1 -0
  48. package/dist/server/auth-precedence.test.js +100 -0
  49. package/dist/server/auth-precedence.test.js.map +1 -0
  50. package/dist/server/codex-args.d.ts +13 -0
  51. package/dist/server/codex-args.d.ts.map +1 -0
  52. package/dist/server/codex-args.js +76 -0
  53. package/dist/server/codex-args.js.map +1 -0
  54. package/dist/server/codex-args.test.d.ts +2 -0
  55. package/dist/server/codex-args.test.d.ts.map +1 -0
  56. package/dist/server/codex-args.test.js +243 -0
  57. package/dist/server/codex-args.test.js.map +1 -0
  58. package/dist/server/codex-auth-cache.d.ts +206 -0
  59. package/dist/server/codex-auth-cache.d.ts.map +1 -0
  60. package/dist/server/codex-auth-cache.js +501 -0
  61. package/dist/server/codex-auth-cache.js.map +1 -0
  62. package/dist/server/codex-auth-cache.test.d.ts +2 -0
  63. package/dist/server/codex-auth-cache.test.d.ts.map +1 -0
  64. package/dist/server/codex-auth-cache.test.js +497 -0
  65. package/dist/server/codex-auth-cache.test.js.map +1 -0
  66. package/dist/server/codex-auth-copyback.d.ts +53 -0
  67. package/dist/server/codex-auth-copyback.d.ts.map +1 -0
  68. package/dist/server/codex-auth-copyback.js +123 -0
  69. package/dist/server/codex-auth-copyback.js.map +1 -0
  70. package/dist/server/codex-auth-copyback.test.d.ts +2 -0
  71. package/dist/server/codex-auth-copyback.test.d.ts.map +1 -0
  72. package/dist/server/codex-auth-copyback.test.js +507 -0
  73. package/dist/server/codex-auth-copyback.test.js.map +1 -0
  74. package/dist/server/codex-auth-merge-decision.cjs +170 -0
  75. package/dist/server/codex-auth-merge-decision.d.ts +22 -0
  76. package/dist/server/codex-auth-merge-decision.d.ts.map +1 -0
  77. package/dist/server/codex-auth-merge-decision.js +58 -0
  78. package/dist/server/codex-auth-merge-decision.js.map +1 -0
  79. package/dist/server/codex-auth-merge-decision.test.d.ts +2 -0
  80. package/dist/server/codex-auth-merge-decision.test.d.ts.map +1 -0
  81. package/dist/server/codex-auth-merge-decision.test.js +230 -0
  82. package/dist/server/codex-auth-merge-decision.test.js.map +1 -0
  83. package/dist/server/codex-auth-merge-extract.sh +73 -0
  84. package/dist/server/codex-auth-merge-scripts.d.ts +21 -0
  85. package/dist/server/codex-auth-merge-scripts.d.ts.map +1 -0
  86. package/dist/server/codex-auth-merge-scripts.js +40 -0
  87. package/dist/server/codex-auth-merge-scripts.js.map +1 -0
  88. package/dist/server/codex-auth-merge.test.d.ts +2 -0
  89. package/dist/server/codex-auth-merge.test.d.ts.map +1 -0
  90. package/dist/server/codex-auth-merge.test.js +714 -0
  91. package/dist/server/codex-auth-merge.test.js.map +1 -0
  92. package/dist/server/codex-auth-seed-write.d.ts +30 -0
  93. package/dist/server/codex-auth-seed-write.d.ts.map +1 -0
  94. package/dist/server/codex-auth-seed-write.js +41 -0
  95. package/dist/server/codex-auth-seed-write.js.map +1 -0
  96. package/dist/server/codex-home.d.ts +162 -0
  97. package/dist/server/codex-home.d.ts.map +1 -0
  98. package/dist/server/codex-home.js +771 -0
  99. package/dist/server/codex-home.js.map +1 -0
  100. package/dist/server/codex-home.test.d.ts +2 -0
  101. package/dist/server/codex-home.test.d.ts.map +1 -0
  102. package/dist/server/codex-home.test.js +1183 -0
  103. package/dist/server/codex-home.test.js.map +1 -0
  104. package/dist/server/config-schema.d.ts +3 -0
  105. package/dist/server/config-schema.d.ts.map +1 -0
  106. package/dist/server/config-schema.js +67 -0
  107. package/dist/server/config-schema.js.map +1 -0
  108. package/dist/server/device-login-export.d.ts +56 -0
  109. package/dist/server/device-login-export.d.ts.map +1 -0
  110. package/dist/server/device-login-export.js +230 -0
  111. package/dist/server/device-login-export.js.map +1 -0
  112. package/dist/server/device-login-export.test.d.ts +2 -0
  113. package/dist/server/device-login-export.test.d.ts.map +1 -0
  114. package/dist/server/device-login-export.test.js +245 -0
  115. package/dist/server/device-login-export.test.js.map +1 -0
  116. package/dist/server/device-login-parse.d.ts +20 -0
  117. package/dist/server/device-login-parse.d.ts.map +1 -0
  118. package/dist/server/device-login-parse.js +155 -0
  119. package/dist/server/device-login-parse.js.map +1 -0
  120. package/dist/server/device-login-parse.test.d.ts +2 -0
  121. package/dist/server/device-login-parse.test.d.ts.map +1 -0
  122. package/dist/server/device-login-parse.test.js +269 -0
  123. package/dist/server/device-login-parse.test.js.map +1 -0
  124. package/dist/server/device-login-runner.d.ts +69 -0
  125. package/dist/server/device-login-runner.d.ts.map +1 -0
  126. package/dist/server/device-login-runner.js +109 -0
  127. package/dist/server/device-login-runner.js.map +1 -0
  128. package/dist/server/device-login-runner.test.d.ts +2 -0
  129. package/dist/server/device-login-runner.test.d.ts.map +1 -0
  130. package/dist/server/device-login-runner.test.js +201 -0
  131. package/dist/server/device-login-runner.test.js.map +1 -0
  132. package/dist/server/engine-availability.test.d.ts +2 -0
  133. package/dist/server/engine-availability.test.d.ts.map +1 -0
  134. package/dist/server/engine-availability.test.js +41 -0
  135. package/dist/server/engine-availability.test.js.map +1 -0
  136. package/dist/server/execute.acp-fallback.test.d.ts +2 -0
  137. package/dist/server/execute.acp-fallback.test.d.ts.map +1 -0
  138. package/dist/server/execute.acp-fallback.test.js +123 -0
  139. package/dist/server/execute.acp-fallback.test.js.map +1 -0
  140. package/dist/server/execute.auth-precedence.test.d.ts +2 -0
  141. package/dist/server/execute.auth-precedence.test.d.ts.map +1 -0
  142. package/dist/server/execute.auth-precedence.test.js +137 -0
  143. package/dist/server/execute.auth-precedence.test.js.map +1 -0
  144. package/dist/server/execute.auth.test.d.ts +2 -0
  145. package/dist/server/execute.auth.test.d.ts.map +1 -0
  146. package/dist/server/execute.auth.test.js +197 -0
  147. package/dist/server/execute.auth.test.js.map +1 -0
  148. package/dist/server/execute.d.ts +40 -0
  149. package/dist/server/execute.d.ts.map +1 -0
  150. package/dist/server/execute.js +1290 -0
  151. package/dist/server/execute.js.map +1 -0
  152. package/dist/server/execute.remote.test.d.ts +2 -0
  153. package/dist/server/execute.remote.test.d.ts.map +1 -0
  154. package/dist/server/execute.remote.test.js +556 -0
  155. package/dist/server/execute.remote.test.js.map +1 -0
  156. package/dist/server/execute.stderr-error.test.d.ts +2 -0
  157. package/dist/server/execute.stderr-error.test.d.ts.map +1 -0
  158. package/dist/server/execute.stderr-error.test.js +142 -0
  159. package/dist/server/execute.stderr-error.test.js.map +1 -0
  160. package/dist/server/execute.test.d.ts +2 -0
  161. package/dist/server/execute.test.d.ts.map +1 -0
  162. package/dist/server/execute.test.js +240 -0
  163. package/dist/server/execute.test.js.map +1 -0
  164. package/dist/server/index.d.ts +18 -0
  165. package/dist/server/index.d.ts.map +1 -0
  166. package/dist/server/index.js +71 -0
  167. package/dist/server/index.js.map +1 -0
  168. package/dist/server/output-inactivity-monitor.d.ts +62 -0
  169. package/dist/server/output-inactivity-monitor.d.ts.map +1 -0
  170. package/dist/server/output-inactivity-monitor.integration.test.d.ts +2 -0
  171. package/dist/server/output-inactivity-monitor.integration.test.d.ts.map +1 -0
  172. package/dist/server/output-inactivity-monitor.integration.test.js +136 -0
  173. package/dist/server/output-inactivity-monitor.integration.test.js.map +1 -0
  174. package/dist/server/output-inactivity-monitor.js +112 -0
  175. package/dist/server/output-inactivity-monitor.js.map +1 -0
  176. package/dist/server/output-inactivity-monitor.test.d.ts +2 -0
  177. package/dist/server/output-inactivity-monitor.test.d.ts.map +1 -0
  178. package/dist/server/output-inactivity-monitor.test.js +284 -0
  179. package/dist/server/output-inactivity-monitor.test.js.map +1 -0
  180. package/dist/server/parse.d.ts +51 -0
  181. package/dist/server/parse.d.ts.map +1 -0
  182. package/dist/server/parse.js +261 -0
  183. package/dist/server/parse.js.map +1 -0
  184. package/dist/server/parse.test.d.ts +2 -0
  185. package/dist/server/parse.test.d.ts.map +1 -0
  186. package/dist/server/parse.test.js +186 -0
  187. package/dist/server/parse.test.js.map +1 -0
  188. package/dist/server/process-activity-monitor.d.ts +21 -0
  189. package/dist/server/process-activity-monitor.d.ts.map +1 -0
  190. package/dist/server/process-activity-monitor.js +103 -0
  191. package/dist/server/process-activity-monitor.js.map +1 -0
  192. package/dist/server/process-activity-monitor.test.d.ts +2 -0
  193. package/dist/server/process-activity-monitor.test.d.ts.map +1 -0
  194. package/dist/server/process-activity-monitor.test.js +82 -0
  195. package/dist/server/process-activity-monitor.test.js.map +1 -0
  196. package/dist/server/quota-spawn-error.test.d.ts +2 -0
  197. package/dist/server/quota-spawn-error.test.d.ts.map +1 -0
  198. package/dist/server/quota-spawn-error.test.js +174 -0
  199. package/dist/server/quota-spawn-error.test.js.map +1 -0
  200. package/dist/server/quota.d.ts +66 -0
  201. package/dist/server/quota.d.ts.map +1 -0
  202. package/dist/server/quota.js +511 -0
  203. package/dist/server/quota.js.map +1 -0
  204. package/dist/server/runtime-config.d.ts +11 -0
  205. package/dist/server/runtime-config.d.ts.map +1 -0
  206. package/dist/server/runtime-config.js +380 -0
  207. package/dist/server/runtime-config.js.map +1 -0
  208. package/dist/server/runtime-config.test.d.ts +2 -0
  209. package/dist/server/runtime-config.test.d.ts.map +1 -0
  210. package/dist/server/runtime-config.test.js +367 -0
  211. package/dist/server/runtime-config.test.js.map +1 -0
  212. package/dist/server/skills.d.ts +8 -0
  213. package/dist/server/skills.d.ts.map +1 -0
  214. package/dist/server/skills.js +26 -0
  215. package/dist/server/skills.js.map +1 -0
  216. package/dist/server/test.d.ts +3 -0
  217. package/dist/server/test.d.ts.map +1 -0
  218. package/dist/server/test.js +458 -0
  219. package/dist/server/test.js.map +1 -0
  220. package/dist/server/test.remote.test.d.ts +2 -0
  221. package/dist/server/test.remote.test.d.ts.map +1 -0
  222. package/dist/server/test.remote.test.js +396 -0
  223. package/dist/server/test.remote.test.js.map +1 -0
  224. package/dist/ui/build-config.d.ts +5 -0
  225. package/dist/ui/build-config.d.ts.map +1 -0
  226. package/dist/ui/build-config.js +229 -0
  227. package/dist/ui/build-config.js.map +1 -0
  228. package/dist/ui/build-config.test.d.ts +2 -0
  229. package/dist/ui/build-config.test.d.ts.map +1 -0
  230. package/dist/ui/build-config.test.js +272 -0
  231. package/dist/ui/build-config.test.js.map +1 -0
  232. package/dist/ui/index.d.ts +4 -0
  233. package/dist/ui/index.d.ts.map +1 -0
  234. package/dist/ui/index.js +8 -0
  235. package/dist/ui/index.js.map +1 -0
  236. package/dist/ui/parse-stdout.d.ts +3 -0
  237. package/dist/ui/parse-stdout.d.ts.map +1 -0
  238. package/dist/ui/parse-stdout.js +265 -0
  239. package/dist/ui/parse-stdout.js.map +1 -0
  240. package/dist/ui/parse-stdout.test.d.ts +2 -0
  241. package/dist/ui/parse-stdout.test.d.ts.map +1 -0
  242. package/dist/ui/parse-stdout.test.js +77 -0
  243. package/dist/ui/parse-stdout.test.js.map +1 -0
  244. package/package.json +61 -0
  245. package/skills/agentmail/SKILL.md +70 -0
  246. package/skills/paperclip/SKILL.md +712 -0
  247. package/skills/paperclip/references/api-reference.md +1675 -0
  248. package/skills/paperclip/references/artifacts.md +158 -0
  249. package/skills/paperclip/references/cases.md +295 -0
  250. package/skills/paperclip/references/company-skills.md +266 -0
  251. package/skills/paperclip/references/issue-workspaces.md +80 -0
  252. package/skills/paperclip/references/routines.md +231 -0
  253. package/skills/paperclip/references/workflows.md +141 -0
  254. package/skills/paperclip/scripts/paperclip-upload-artifact.sh +592 -0
  255. package/skills/paperclip-board/SKILL.md +619 -0
  256. package/skills/paperclip-converting-plans-to-tasks/SKILL.md +60 -0
  257. package/skills/paperclip-create-agent/SKILL.md +179 -0
  258. package/skills/paperclip-create-agent/references/agent-instruction-templates.md +123 -0
  259. package/skills/paperclip-create-agent/references/agents/coder.md +64 -0
  260. package/skills/paperclip-create-agent/references/agents/qa.md +88 -0
  261. package/skills/paperclip-create-agent/references/agents/securityengineer.md +135 -0
  262. package/skills/paperclip-create-agent/references/agents/uxdesigner.md +115 -0
  263. package/skills/paperclip-create-agent/references/api-reference.md +110 -0
  264. package/skills/paperclip-create-agent/references/baseline-role-guide.md +168 -0
  265. package/skills/paperclip-create-agent/references/draft-review-checklist.md +95 -0
  266. package/skills/para-memory-files/SKILL.md +100 -0
  267. package/skills/para-memory-files/references/schemas.md +35 -0
  268. 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"