이야기: 한 번 더 붙은 해지 수수료
요 몇 주 포럼과 SNS에서 돌고 있는 이야기는 ArtCraft가 분노에서 태어났다는 것, 만든 사람이 「사기를 당했다」는 것입니다. 프로젝트의 주인공은 브랜던 토머스(Brandon Thomas), 미국 애틀랜타의 15년 차 소프트웨어 엔지니어로 Reddit에서 u/ai_art_is_art라는 이름으로 글을 썼습니다. 「사기」는 사람들의 표현이고, 그가 직접 말한 것은 Adobe가 구독 해지 수수료를 청구했다는 것입니다.
그는 Reddit에 「그들은 여러 번 나에게 ‘해지 수수료’를 물렸고, 그때부터 나는 그들이 싫다」고 썼습니다. Hacker News에서는 그 「다크 패턴」, 즉 월 단위로 내는 연간 요금제를 중도에 해지할 때 붙는 위약금에 너무 여러 번 걸렸다고 했습니다. 개인의 불만만은 아닙니다. 2024년 6월 미국 법무부는 이런 수수료를 숨기고 해지를 어렵게 했다며 Adobe를 제소했습니다. Adobe는 자사 약관이 투명하다고 주장합니다.
그의 대답은 프로그램을 다시 만들어 무료로 나눠 주는 것이었습니다. Anthropic의 인공지능 Claude와 Rust 언어의 도움을 받아, 2026년 9월 말부터 10월 초 사이 GitHub에 첫 일곱 개 앱 — Photoshop, Illustrator, Premiere, Lightroom, After Effects, InDesign, Acrobat에 해당하는 — 을 공개했고, 몇 주 만에 Word, PowerPoint, Excel, AutoCAD, Pro Tools, Figma에 해당하는 앱도 추가했습니다. 그는 이것들이 파일 형식의 공개 명세를 바탕으로, 누구의 코드도 쓰지 않고 만든 「클린룸」 재구현이라고 말합니다.
모든 것을 요약한 그의 말 「소프트웨어는 끝났다」는 전 세계로 퍼졌습니다. 한 사람이 AI와 함께 수십 년 걸린 것을 몇 주 만에 다시 만들 수 있다면 유료 소프트웨어는 힘들어진다는 뜻입니다. 의문도 있습니다. 영국 서식스 대학교의 안드레스 과다무스 교수는 그 「클린룸」 과정이 검증을 견딜지, 코드의 얼마를 그가 쓰고 얼마를 AI가 썼는지 아직 알 수 없다고 지적합니다. 토머스는 이것이 「곧 꺼질 반짝 유행」이 아니라고 강조하며, 지금까지는 거의 매일 나오는 새 버전이 그의 말을 뒷받침하고 있습니다.
ArtCraft는 storytold 팀의 오픈 소스 프로젝트(MIT 또는 Apache-2.0 라이선스)로, 우리가 매일 창작에 쓰는 도구를 Rust로 처음부터 다시 만들고 있습니다. 구독이 필요 없는 가볍고 네이티브한 프로그램으로, 가장 유명한 상용 프로그램의 작동 방식을 따라 하되 코드는 복사하지 않습니다. Windows, macOS, Linux, FreeBSD에서 돌아가고 상당수는 브라우저에서도 쓸 수 있으며, 거의 매주 새 버전이 나옵니다. 언급한 상용 제품 이름은 참고용일 뿐이며, 이 프로그램들은 해당 브랜드와 아무 관계가 없습니다.
도구 하나하나
ArtCraft Launcher v0.8.1
여기서 시작하세요. 나머지 전부를 설치해 주는 앱입니다. 전체 카탈로그를 보여 주고, 내 컴퓨터에 맞는 빌드를 골라 클릭 한 번으로 각 앱을 설치하며, 파일이 손상되지 않았는지 확인하고, 업데이트가 나오면 알려 줍니다. 모든 것을 담은 단일 패키지는 없지만 이 런처가 가장 가깝습니다.
PhotoCraft v0.16.0
Photoshop 계열의 이미지 편집기: 레이어, 선택 영역, 브러시, 보정, 리터칭. 이 프로젝트에서 GitHub 인기가 가장 높은 도구입니다.
LightCraft v0.14.0
Lightroom 스타일의 사진 라이브러리와 비파괴 RAW 현상: 사진을 정리하고 노출, 색, 자르기를 원본 파일은 건드리지 않고 조정합니다.
VectorCraft v0.17.0
Illustrator 같은 벡터 일러스트: 품질 손실 없이 확대되는 곡선, 도형, 선, 텍스트. 로고, 아이콘, 포스터에 좋습니다.
FilmCraft v0.13.0
Premiere Pro와 비슷한 비선형 영상 편집기: 멀티 트랙 타임라인, 컷 편집, 색 보정, 사운드.
EffectCraft v0.17.0
After Effects 계열의 모션 그래픽, 합성, 시각 효과: 타이틀, 키프레임 애니메이션, 겹쳐 쓰는 레이어.
SoundCraft v0.13.1
Pro Tools에서 영감을 받은 녹음·편집·믹싱용 오디오 워크스테이션: 여러 트랙, 컷 편집, 이펙트, 최종 믹스.
DesignCraft v0.13.0
InDesign 스타일의 편집 디자인과 출판: 페이지, 단, 텍스트 스타일, 이미지로 만드는 잡지, 브로슈어, 책.
FigCraft v0.11.2
Figma 같은 인터페이스 디자인과 프로토타이핑: 앱과 웹 화면, 재사용 가능한 컴포넌트, 만들기 전에 작동 방식을 보여 주는 목업.
PdfCraft v0.14.0
Acrobat 계열의 PDF 읽기·정리·보호 도구: 열기, 주석 달기, 페이지 순서 바꾸기, 보호된 문서로 저장하기.
WordCraft v0.11.0
Word와 비슷한 워드프로세서: 편지, 보고서, 스타일·표·이미지가 들어간 문서용.
DeckCraft v0.11.1
PowerPoint 스타일의 프레젠테이션과 슬라이드: 텍스트, 이미지, 도형을 넣어 전체 화면으로 발표합니다.
GridCraft v0.12.1
Excel 같은 스프레드시트: 수식, 데이터 표, 차트, 정렬과 필터.
CADCraft v0.13.0
AutoCAD 계열의 컴퓨터 지원 설계와 기술 도면: 정확한 치수, 레이어, 치수선이 있는 도면.
ArtCraft v0.41.0
프로젝트 이름의 유래가 된 앱이자 나머지와 따로 노는 유일한 앱: 예술가, 디자이너, 영화 제작자를 위해 인공지능으로 이미지와 영상을 만드는 스튜디오입니다. 지금은 Windows와 Mac만 지원합니다.
전부 보관하기: 소스 코드와 실행 파일
무엇이든 사본을 남겨 두고 싶다면(언젠가 프로젝트가 사라질 경우나 인터넷 없이 설치할 때를 대비해), 이 Linux 스크립트가 도구마다 소스 코드와 최신 릴리스의 모든 실행 파일을 내려받아 도구별·시스템별(Windows, macOS, Linux, FreeBSD, 웹) 폴더로 정리해 줍니다. 압축을 풀고 실행하세요. 인수는 저장할 폴더이고, PLATFORMS와 EXCLUDE로 무엇을 받을지 고릅니다. (스크립트의 주석은 스페인어입니다.)
⬇ 스크립트 내려받기 (artcraft-mirror.zip)
chmod +x artcraft-mirror.sh
./artcraft-mirror.sh ~/artcraft
PLATFORMS="windows linux" EXCLUDE='riscv64|aarch64' ./artcraft-mirror.sh ~/artcraft
- 중간에 멈춰도 됩니다: 끊기면 다시 실행하세요. 멈춘 곳부터 이어서 받습니다.
- 새것만 받습니다: 새 버전이 나오면 그 폴더를 만들고 이전 것은 건드리지 않습니다.
- 모두 검증합니다: 릴리스마다 공개되는 SHA-256 합계로 확인합니다.
- 용량이 큽니다: 모든 플랫폼을 합치면 릴리스당 약 24 GB입니다. 일부만 필요하면 걸러 내세요.
스크립트 전체 보기
#!/usr/bin/env bash
# artcraft-mirror.sh — espejo local de las Craft Apps de ArtCraft (github.com/storytold)
#
# Estructura que genera:
# <DEST>/<herramienta>/code/<rama>-<herramienta>-AAAAMMDD.zip (+ .sha con el commit)
# <DEST>/<herramienta>/release-<versión>/{windows,macos,linux,freebsd,web,otros}/...
# <DEST>/<herramienta>/release-<versión>/SHA256SUMS.txt
#
# - Reanudable: las descargas van a *.part y continúan con Range (curl -C -).
# Una release se da por terminada sólo cuando todo se verificó contra SHA256SUMS
# (queda el marcador .complete). Si se corta, se vuelve a ejecutar y sigue.
# - Incremental: sólo mira la última release de cada herramienta; las carpetas
# release-* anteriores y los zip de código de días anteriores no se tocan.
# - El código fuente se baja sólo si el commit de la rama principal cambió.
#
# Uso:
# ./artcraft-mirror.sh [DEST] (DEST por defecto: carpeta del script)
# Variables opcionales:
# GITHUB_TOKEN=ghp_... sube el límite de la API de 60 a 5000 llamadas/h
# PLATFORMS="windows linux macos" sólo estas plataformas (por defecto todas)
# EXCLUDE='riscv64|aarch64|flatpak' regex de nombres de archivo a omitir
# TOOLS="photocraft cadcraft" sólo estas herramientas
# EXTRA_TOOLS="anicraft" añadir repos de storytold no incluidos en el catálogo
# SKIP_CODE=1 / SKIP_RELEASES=1
set -uo pipefail
OWNER=storytold
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
DEST=${1:-$SCRIPT_DIR}
PLATFORMS=${PLATFORMS:-"windows macos linux freebsd web otros"}
EXCLUDE=${EXCLUDE:-}
TODAY=$(date +%Y%m%d)
# Catálogo de respaldo por si no se puede leer el del launcher
FALLBACK_TOOLS="photocraft vectorcraft filmcraft lightcraft pdfcraft effectcraft designcraft
figcraft wordcraft soundcraft deckcraft cadcraft gridcraft"
# Fuera del catálogo del launcher pero con releases propias
ALWAYS_TOOLS="craft-launcher artcraft"
for bin in curl jq git sha256sum flock; do
command -v "$bin" >/dev/null || { echo "Falta '$bin'" >&2; exit 1; }
done
mkdir -p "$DEST"
exec 9>"$DEST/.artcraft-mirror.lock"
flock -n 9 || { echo "Ya hay otra ejecución en curso en $DEST" >&2; exit 1; }
log() { printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*"; }
warn() { printf '[%s] AVISO: %s\n' "$(date +%H:%M:%S)" "$*" >&2; }
api() {
local auth=()
[[ -n ${GITHUB_TOKEN:-} ]] && auth=(-H "Authorization: Bearer $GITHUB_TOKEN")
curl -fsSL --retry 3 -H "Accept: application/vnd.github+json" "${auth[@]}" \
"https://api.github.com/$1"
}
# Descarga: $1 url, $2 destino final, $3 tamaño esperado.
# Con tamaño conocido continúa el .part (Range); sin él (zip de código, generado
# al vuelo y sin Range) empieza de cero.
fetch() {
local url=$1 out=$2 size=${3:-} part="$2.part" have resume=()
mkdir -p "$(dirname "$out")"
if [[ -n $size && -f $part ]]; then
have=$(stat -c %s "$part")
if (( have == size )); then mv -f "$part" "$out"; return 0; fi
if (( have > size )); then rm -f "$part"; fi
resume=(-C -)
else
rm -f "$part"
fi
curl -fL --retry 5 --retry-delay 5 --retry-all-errors "${resume[@]}" \
--progress-bar -o "$part" "$url" || return 1
mv -f "$part" "$out"
}
platform_of() {
case $1 in
SHA256SUMS*) echo . ;;
*windows*|*.msi|*.exe) echo windows ;;
*macos*|*.dmg|*.app.tar.gz|*universal*) echo macos ;;
*freebsd*) echo freebsd ;;
*linux*|*.AppImage*|*.deb|*.rpm|*.flatpak) echo linux ;;
*-web-*) echo web ;;
*) echo otros ;;
esac
}
wanted() {
local name=$1 plat=$2
[[ $plat == . ]] && return 0
[[ " $PLATFORMS " == *" $plat "* ]] || return 1
[[ -n $EXCLUDE && $name =~ $EXCLUDE ]] && return 1
return 0
}
tool_list() {
if [[ -n ${TOOLS:-} ]]; then echo $TOOLS; return; fi
local cat
cat=$(curl -fsSL "https://raw.githubusercontent.com/$OWNER/craft-launcher/main/crates/core/src/catalog.rs" 2>/dev/null \
| grep -oE "\"$OWNER/[a-z0-9-]+\"" | tr -d '"' | cut -d/ -f2 | grep -v '^craft-launcher$' | sort -u)
[[ -z $cat ]] && { warn "no pude leer el catálogo del launcher; uso la lista interna"; cat=$FALLBACK_TOOLS; }
echo $cat $ALWAYS_TOOLS ${EXTRA_TOOLS:-} | tr ' ' '\n' | awk 'NF && !seen[$0]++'
}
mirror_code() {
local tool=$1 dir="$DEST/$1/code" head branch sha last
head=$(git ls-remote --symref "https://github.com/$OWNER/$tool" HEAD 2>/dev/null) \
|| { warn "$tool: no pude consultar el repositorio"; return 1; }
branch=$(awk '/^ref:/ {sub("refs/heads/","",$2); print $2}' <<<"$head")
sha=$(awk '$2=="HEAD" && $1!="ref:" {print $1}' <<<"$head")
mkdir -p "$dir"
last=$(ls -1 "$dir"/*.zip.sha 2>/dev/null | sort | tail -1)
if [[ -n $last && $(<"$last") == "$sha" ]]; then
log "$tool: código al día ($branch@${sha:0:8})"
return 0
fi
local zip="$dir/$branch-$tool-$TODAY.zip"
log "$tool: bajando código $branch@${sha:0:8} → $(basename "$zip")"
# El zip por commit garantiza que el archivo corresponde al sha anotado
fetch "https://github.com/$OWNER/$tool/archive/$sha.zip" "$zip" || { warn "$tool: falló el zip"; return 1; }
echo "$sha" > "$zip.sha"
}
verify() { # $1 archivo, $2 SHA256SUMS → 0 ok / 1 mal / 2 sin suma publicada
local file=$1 sums=$2 expected
[[ -f $sums ]] || return 2
expected=$(awk -v n="$(basename "$file")" '{f=$2; sub(/^\*/,"",f)} f==n {print $1}' "$sums")
[[ -z $expected ]] && return 2
[[ $(sha256sum "$file" | cut -d' ' -f1) == "$expected" ]]
}
mirror_release() {
local tool=$1 json tag ver rdir
json=$(api "repos/$OWNER/$tool/releases/latest") || { warn "$tool: sin releases o límite de API alcanzado"; return 1; }
tag=$(jq -r .tag_name <<<"$json")
ver=${tag#"$tool"-}; ver=${ver#v}
rdir="$DEST/$tool/release-$ver"
if [[ -f $rdir/.complete && $(<"$rdir/.complete") == "$PLATFORMS|$EXCLUDE" ]]; then
log "$tool: release $ver ya completa"
return 0
fi
log "$tool: release $ver"
mkdir -p "$rdir"
jq -c '{tag_name, name, published_at, html_url, body}' <<<"$json" > "$rdir/release.json"
local ok=1 name url size plat out sums="$rdir/SHA256SUMS.txt"
# SHA256SUMS primero para poder verificar el resto
while IFS=$'\t' read -r name url size; do
plat=$(platform_of "$name")
wanted "$name" "$plat" || continue
if [[ $plat == . ]]; then out="$rdir/$name"; else out="$rdir/$plat/$name"; fi
if [[ -f $out ]]; then
verify "$out" "$sums"; case $? in
0) continue ;;
2) [[ $(stat -c %s "$out") == "$size" ]] && continue ;;
esac
warn "$tool: $name no coincide, se vuelve a bajar"; rm -f "$out"
fi
log " ↓ ${out#"$rdir"/} ($((size / 1048576)) MiB)"
if ! fetch "$url" "$out" "$size"; then warn " falló $name"; ok=0; continue; fi
verify "$out" "$sums"; case $? in
1) warn " checksum INCORRECTO: $name (se borra)"; rm -f "$out"; ok=0 ;;
2) [[ $name != SHA256SUMS* ]] && [[ $(stat -c %s "$out") != "$size" ]] && { warn " tamaño incorrecto: $name"; rm -f "$out"; ok=0; } ;;
esac
done < <(jq -r '.assets | sort_by(.name | startswith("SHA256SUMS") | not)[]
| [.name, .browser_download_url, .size] | @tsv' <<<"$json")
if (( ok )); then echo "$PLATFORMS|$EXCLUDE" > "$rdir/.complete"; log "$tool: release $ver completa ✔"
else warn "$tool: release $ver incompleta; vuelve a ejecutar el script para continuar"; return 1; fi
}
fail=0
for tool in $(tool_list); do
echo "──────── $tool"
[[ -z ${SKIP_CODE:-} ]] && { mirror_code "$tool" || fail=1; }
[[ -z ${SKIP_RELEASES:-} ]] && { mirror_release "$tool" || fail=1; }
done
(( fail )) && { warn "Hubo errores; ejecuta de nuevo para reintentar lo pendiente."; exit 1; }
log "Todo al día en $DEST"
모두 아주 새로운 프로그램이고 거의 전부 초기 알파 단계입니다. 가능성은 크지만 본격적인 작업에서 전문 도구를 대신하기에는 아직 이릅니다. 써 보고, 파일은 백업해 두고, 버그를 찾으면 각 GitHub에 알려 주세요.
참고 자료
- GitHub · storytold (todas las Crafting Apps)
- GitHub · ArtCraft Launcher
- GitHub · ArtCraft
- getartcraft.com
- Al Jazeera (14-10-2026) · How AI apps are offering free substitutes for Big Tech software
- Amateur Photographer (08-10-2026) · This home coder got so sick of Adobe he built free, open-source versions of Photoshop, Lightroom and Premiere
- PetaPixel (07-10-2026) · Someone Rebuilt Free, Open-Source Versions of Photoshop, Premiere, and Lightroom Using AI
- Creative Bloq · Someone just rebuilt Adobe Creative Cloud with AI – then gave it away for free
- CineD · ArtCraft Releases Free Open-Source Adobe Alternatives
- Korben · I tested the 12 free clones of Adobe and Microsoft