Write developer blog posts from video transcripts, meeting notes, or rough ideas — extracting narrative, structuring hooks and technical sections, formatting code placeholders, learning the author's voice through interactive onboarding, and checking drafts against 39 AI anti-patterns with three-pass scanning, craft sweep, and rewrite auditing — whenever the user wants to write a blog post, draft a blog, turn a transcript into a blog, work on blog content, or continue a blog series.
77
97%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
#!/usr/bin/env bash
#
# Fetch the raw wikitext of Wikipedia's "Signs of AI writing" article.
#
# Wikipedia blocks the standard WebFetch user agent, so the article is pulled
# over HTTP with an explicit browser user agent and the `action=raw` endpoint.
# The article is community-maintained and changes as LLM writing patterns
# change, which is why the skill re-reads it instead of trusting a snapshot.
#
# Usage:
# fetch-signs-of-ai-writing.sh [output-path]
#
# Input:
# $1 optional destination path. Defaults to a mktemp file. The article runs
# well past a comfortable inline read, so output always lands in a file
# and the path is what comes back on stdout.
#
# The fetch is atomic with respect to the destination: the download lands in a
# script-owned staging file beside it and is moved into place only after the
# status and size checks pass. A failed or truncated fetch therefore never
# replaces or empties an existing file at the caller's path.
#
# Output (stdout):
# On success, a single JSON object:
# {"ok": true, "path": "<file>", "bytes": N}
#
# Exit codes:
# 0 fetched; the file at .path holds the raw wikitext
# 1 fetch failed (network unreachable, HTTP error, or empty body). The
# caller proceeds with the existing anti-pattern file, which is
# self-contained and does not depend on this check.
# 2 tool or usage error (curl missing, destination not writable)
#
# A non-zero exit is a real signal, never swallowed: the diagnostic goes to
# stderr and the caller decides. This script does not fall back silently.
# Shell options are set inside main() rather than at file scope: the entry-point
# guard below makes this file sourceable, and a sourced file must not change the
# caller's shell options.
readonly ARTICLE_URL="https://en.wikipedia.org/w/index.php?title=Wikipedia:Signs_of_AI_writing&action=raw"
readonly USER_AGENT="Mozilla/5.0 (compatible; blog-writer-skill/1.0)"
readonly MIN_BYTES=1000
main() {
set -euo pipefail
if ! command -v curl >/dev/null; then
echo "error: curl not found on PATH — install curl, or skip the freshness check and proceed with references/ai-anti-patterns.md as-is" >&2
exit 2
fi
# The success line is JSON, and the path is caller-supplied, so it goes through a
# real encoder. A path holding a quote, backslash, or newline would otherwise
# produce output that parses as something else, or not at all.
if ! command -v jq >/dev/null; then
echo '{"ok": false, "reason": "jq not found on PATH — required to emit the result as JSON"}' >&2
exit 2
fi
if [ "$#" -gt 1 ]; then
echo "error: expected at most one argument (output path), got $# — usage: fetch-signs-of-ai-writing.sh [output-path]" >&2
exit 2
fi
if [ "$#" -eq 1 ]; then
out_path=$1
out_dir=$(dirname "$out_path")
if [ ! -d "$out_dir" ]; then
echo "error: destination directory does not exist: ${out_dir} — create it or pass a different path" >&2
exit 2
fi
if [ ! -w "$out_dir" ]; then
echo "error: destination directory is not writable: ${out_dir}" >&2
exit 2
fi
else
out_dir=${TMPDIR:-/tmp}
out_path=""
fi
# The download always lands in a script-owned staging file, never on the
# caller's path. curl truncates its --output target the moment it starts
# writing, so pointing it at the destination would destroy an existing file
# before the status and size checks have decided whether the fetch is any good.
# The staging file is created alongside the destination so the final move is a
# same-filesystem rename rather than a copy that could half-finish.
if ! staging=$(mktemp "${out_dir}/.signs-of-ai-writing.XXXXXX"); then
echo "error: could not create a staging file under ${out_dir}" >&2
exit 2
fi
# The staging file is owned from creation until it either moves into place or
# the script ends. An EXIT trap covers every way out — an explicit error branch,
# an interrupt, or an unexpected failure under `set -e` in wc, tr, mv, or jq —
# so no exit path can strand it. Ownership is released the moment the file stops
# being staging: on a successful move it belongs to the caller's path, and with
# no destination the caller owns the staging file itself.
staging_owned=1
discard_staging() {
if [ "$staging_owned" -eq 1 ] && [ -e "$staging" ]; then
rm -f "$staging"
fi
return 0
}
trap discard_staging EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
http_status=0
if ! http_status=$(curl --silent --location --show-error \
--user-agent "$USER_AGENT" \
--max-time 30 \
--write-out '%{http_code}' \
--output "$staging" \
"$ARTICLE_URL"); then
echo "error: curl could not reach Wikipedia (network error or timeout) — proceed with references/ai-anti-patterns.md as-is" >&2
exit 1
fi
if [ "$http_status" != "200" ]; then
echo "error: Wikipedia returned HTTP ${http_status} for the article — proceed with references/ai-anti-patterns.md as-is" >&2
exit 1
fi
bytes=$(wc -c < "$staging" | tr -d ' ')
if [ "$bytes" -lt "$MIN_BYTES" ]; then
echo "error: fetched body is ${bytes} bytes, under the ${MIN_BYTES}-byte floor — the page is likely an error stub rather than the article; proceed with references/ai-anti-patterns.md as-is" >&2
exit 1
fi
# Every validation passed, so the result is now fit to occupy the destination.
if [ -n "$out_path" ]; then
if ! mv -f "$staging" "$out_path"; then
echo "error: could not move the fetched article into place at ${out_path}" >&2
exit 2
fi
else
out_path=$staging
fi
# The file is the caller's from here, by either route. Releasing ownership stops
# the EXIT trap from deleting the very result being reported.
staging_owned=0
jq -n --arg path "$out_path" --argjson bytes "$bytes" '{ok: true, path: $path, bytes: $bytes}'
}
# Entry-point guard per `jbaruch/coding-policy: file-hygiene` — the script runs when
# executed and stays sourceable for testing or reuse.
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
main "$@"
fi