openartifacts-publish~20261023-214244.sh 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. #!/bin/sh
  2. # Publish one HTML file to OpenArtifacts over HTTPS with the license key Copilot
  3. # supplies in the environment. Needs only sh, curl, and awk.
  4. usage() {
  5. printf '%s\n' "Usage: sh openartifacts-publish.sh publish <html-file> <title> [docId]" >&2
  6. printf '%s\n' " sh openartifacts-publish.sh unshare <docId>" >&2
  7. exit 1
  8. }
  9. KEY=$COPILOT_PLUS_LICENSE_KEY
  10. [ -n "$KEY" ] || {
  11. printf '%s\n' "Publishing to OpenArtifacts needs a Copilot Plus license key. Add it in Copilot Settings and try again." >&2
  12. exit 1
  13. }
  14. API_HOST=$OPENARTIFACTS_API_HOST
  15. [ -n "$API_HOST" ] || API_HOST="https://api.openartifacts.ai"
  16. API_HOST=$(printf '%s' "$API_HOST" | sed 's,/*$,,')
  17. COMMAND=$1
  18. DOC_ID=
  19. case "$COMMAND" in
  20. publish)
  21. [ "$#" -eq 3 ] || [ "$#" -eq 4 ] || usage
  22. HTML_FILE=$2
  23. TITLE=$3
  24. [ "$#" -eq 4 ] && DOC_ID=$4
  25. [ -f "$HTML_FILE" ] || {
  26. printf '%s\n' "HTML file not found: $HTML_FILE" >&2
  27. exit 1
  28. }
  29. ;;
  30. unshare)
  31. [ "$#" -eq 2 ] || usage
  32. DOC_ID=$2
  33. ;;
  34. *)
  35. usage
  36. ;;
  37. esac
  38. if [ -n "$DOC_ID" ] && ! printf '%s' "$DOC_ID" | grep -Eq '^[0-9abcdefghjkmnpqrstvwxyz]{16}$'; then
  39. printf '%s\n' "Invalid OpenArtifacts document id: $DOC_ID" >&2
  40. exit 1
  41. fi
  42. # stdin -> one JSON string literal. Bytes pass through untouched except the escapes
  43. # JSON requires: backslash, quote, tab, CR, LF, and every other control character as
  44. # \u00XX. Only NUL is dropped (awk cannot carry it, and HTML never contains it). A
  45. # sentinel byte keeps a trailing newline in the file from being lost. Backslashes are
  46. # joined in rather than substituted because awk implementations disagree on
  47. # backslashes inside gsub replacements.
  48. json_string() {
  49. { cat; printf 'x'; } | LC_ALL=C tr -d '\000' | LC_ALL=C awk '
  50. BEGIN { ORS = ""; bs = sprintf("%c", 92); printf "\"" }
  51. NR > 1 { printf "%s\\n", prev }
  52. {
  53. n = split($0, parts, /\\/)
  54. line = parts[1]
  55. for (i = 2; i <= n; i++) line = line bs bs parts[i]
  56. gsub(/"/, "\\\"", line)
  57. gsub(/\t/, "\\t", line)
  58. gsub(/\r/, "\\r", line)
  59. for (c = 1; c < 32; c++) {
  60. if (c != 9 && c != 10 && c != 13) gsub(sprintf("%c", c), sprintf("\\u%04x", c), line)
  61. }
  62. prev = line
  63. }
  64. END { printf "%s\"", substr(prev, 1, length(prev) - 1) }
  65. '
  66. }
  67. HEADERS=$(mktemp) || exit 1
  68. BODY=$(mktemp) || exit 1
  69. RESPONSE=$(mktemp) || exit 1
  70. trap 'rm -f "$HEADERS" "$BODY" "$RESPONSE"' EXIT
  71. printf 'Authorization: Bearer %s\n' "$KEY" > "$HEADERS"
  72. if [ "$COMMAND" = unshare ]; then
  73. STATUS=$(curl -sS -o "$RESPONSE" -w '%{http_code}' -X DELETE -H "@$HEADERS" "$API_HOST/api/v1/docs/$DOC_ID")
  74. CURL_STATUS=$?
  75. else
  76. {
  77. printf '{"title":'
  78. printf '%s' "$TITLE" | json_string
  79. printf ',"html":'
  80. json_string < "$HTML_FILE"
  81. printf '}'
  82. } > "$BODY"
  83. if [ -n "$DOC_ID" ]; then
  84. METHOD=PUT
  85. URL="$API_HOST/api/v1/docs/$DOC_ID"
  86. else
  87. METHOD=POST
  88. URL="$API_HOST/api/v1/docs"
  89. fi
  90. STATUS=$(curl -sS -o "$RESPONSE" -w '%{http_code}' -X "$METHOD" -H "@$HEADERS" -H 'Content-Type: application/json; charset=utf-8' --data-binary "@$BODY" "$URL")
  91. CURL_STATUS=$?
  92. fi
  93. if [ "$CURL_STATUS" -ne 0 ]; then
  94. printf '%s\n' "Could not reach OpenArtifacts at $API_HOST." >&2
  95. exit 1
  96. fi
  97. # The API answers a structured not_found when the page is already gone, which is the
  98. # outcome asked for. Any other 404 is an error.
  99. if [ "$COMMAND" = unshare ] && [ "$STATUS" = 404 ] && grep -q '"not_found"' "$RESPONSE"; then
  100. STATUS=204
  101. fi
  102. case "$STATUS" in
  103. 2??)
  104. if [ "$COMMAND" = unshare ]; then
  105. printf '{"docId":"%s","status":"unshared"}\n' "$DOC_ID"
  106. else
  107. cat "$RESPONSE"
  108. printf '\n'
  109. fi
  110. exit 0
  111. ;;
  112. *)
  113. printf '%s\n' "OpenArtifacts returned HTTP $STATUS" >&2
  114. cat "$RESPONSE" >&2
  115. printf '\n' >&2
  116. exit 1
  117. ;;
  118. esac