CLI-Anything LibreOffice로 HWP 내보내기? Unknown preset hwp
명령줄에서 Writer 문서를 만들 수 있다 해도, 납품할 형식이 아니면 보고서 일은 끝나지 않습니다. 이번에 따진 것은 CLI-Anything의 LibreOffice 하네스가 Debian 13에서 요청한 형식을 정말 만들어 내는지, 한글 본문은 그대로 남는지입니다. 보고서 생성을 운영 서버에서 자동으로 돌리는 경우를 염두에 두고 재현, 해결, 점검 순서로 스크립트를 실행해 기록했습니다. HWP가 필요한 상황을 가정해, 파일 이름을 report.hwp로 두고 프리셋에 hwp를 넣었을 때 어디서 멈추는지부터 재현했습니다.
그 오류를 Debian 13에서 그대로 일으킨 다음, 같은 제목과 문단을 pdf와 docx로 내보내고 본문이 파일 안에 남았는지 확인했습니다. soffice를 PATH에서 찾지 못하는 경우, 이미 있는 PDF를 다시 쓰는 경우, 확장자와 프리셋이 어긋난 경우, document new의 -p에 내보내기 프리셋을 적은 경우도 같은 실행 안에서 비교했습니다. 명령은 모두 run 디렉터리 기준이고, 재현 스크립트, 해결 스크립트, 점검 스크립트가 각 명령을 화면에 찍은 뒤 실행합니다.
report.hwp를 요청하면 Unknown preset hwp에서 멈춘다
재현 환경부터 맞춥니다. 재현 스크립트는 HKUDS/CLI-Anything을 depth 1 sparse clone으로 받아 libreoffice/agent-harness만 작업 트리에 남기고, Python 3.12 가상환경에 설치합니다. 저장소 주소, 커밋, setup.py의 version 줄, 인터프리터 버전, 배포판 버전이 한 출력에 같이 찍힙니다. 하네스를 설치한 것과 변환기가 실제로 도는 것은 다른 문제라서 soffice 버전도 함께 남겼습니다. 아래 표는 실행 환경입니다. 값은 셋째 절의 점검 스크립트 출력에 찍힌 것만 옮겼습니다.
| 항목 | 값 |
|---|---|
| OS | Debian GNU/Linux 13 (trixie) |
| 실행일 | 2026-10-14 |
| Python | 3.12.15 |
| cli-anything-libreoffice | 1.0.1 |
| click | 8.5.0 |
| prompt-toolkit | 3.0.53 |
| defusedxml | 0.7.1 |
| LibreOffice | 25.2.3.2 520(Build:2) |
| soffice | /usr/bin/soffice |
| pdftotext | 25.03.0 |
| CLI-Anything 커밋 | 34f519533bc175d2fe287ab8316b0dd99bb9cc43 |
같은 결과를 다시 만들어 보려면 이 표의 배포판, Python, LibreOffice, 커밋을 먼저 맞추면 됩니다. 아래 명령은 그 환경에 하네스를 설치하는 부분입니다.
git -c color.ui=false clone --depth 1 --filter=blob:none --sparse -q https://github.com/HKUDS/CLI-Anything.git work/src
git -C work/src sparse-checkout set libreoffice/agent-harness
git -C work/src remote get-url origin
git -C work/src rev-parse HEAD
grep -n '^ version=' work/src/libreoffice/agent-harness/setup.py
uv venv -q --color never work/venv -p 3.12
uv pip install -q --color never -p work/venv -e work/src/libreoffice/agent-harness
export PATH="$(pwd)/work/venv/bin:$PATH"
다음으로 Writer 프로젝트를 만들어 제목과 문단을 넣고, 출력 경로는 report.hwp, 프리셋은 hwp로만 지정합니다. 프로젝트 이름은 주간보고, 제목 텍스트는 주간 보고, 문단은 CLI-Anything 테스트 문단이고 제목 수준은 1입니다.
cli-anything-libreoffice document new --type writer -n '주간보고' -o work/a/report.lo-cli.json
cli-anything-libreoffice --project work/a/report.lo-cli.json writer add-heading -t '주간 보고' -l 1
cli-anything-libreoffice --project work/a/report.lo-cli.json writer add-paragraph -t 'CLI-Anything 테스트 문단입니다.'
cli-anything-libreoffice --project work/a/report.lo-cli.json document save
cli-anything-libreoffice --project work/a/report.lo-cli.json export render work/a/report.hwp -p hwp
실행 결과입니다. 문서를 만들고 저장하는 데까지는 문제가 없는데, 내보내기에서 Unknown preset 오류가 납니다. hwp_exit:1과 hwp_file:absent가 나란히 남아 있으니 결과 파일도 만들어지지 않았습니다.
+ rm -rf work/src work/venv work/a
+ git -c color.ui=false clone --depth 1 --filter=blob:none --sparse -q https://github.com/HKUDS/CLI-Anything.git work/src
remote: Enumerating objects: 12, done.
remote: Counting objects: 8% (1/12)remote: Counting objects: 16% (2/12)remote: Counting objects: 25% (3/12)remote: Counting objects: 33% (4/12)remote: Counting objects: 41% (5/12)remote: Counting objects: 50% (6/12)remote: Counting objects: 58% (7/12)remote: Counting objects: 66% (8/12)remote: Counting objects: 75% (9/12)remote: Counting objects: 83% (10/12)remote: Counting objects: 91% (11/12)remote: Counting objects: 100% (12/12)remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 8% (1/12)remote: Compressing objects: 16% (2/12)remote: Compressing objects: 25% (3/12)remote: Compressing objects: 33% (4/12)remote: Compressing objects: 41% (5/12)remote: Compressing objects: 50% (6/12)remote: Compressing objects: 58% (7/12)remote: Compressing objects: 66% (8/12)remote: Compressing objects: 75% (9/12)remote: Compressing objects: 83% (10/12)remote: Compressing objects: 91% (11/12)remote: Compressing objects: 100% (12/12)remote: Compressing objects: 100% (12/12), done.
Receiving objects: 8% (1/12)Receiving objects: 16% (2/12)Receiving objects: 25% (3/12)Receiving objects: 33% (4/12)Receiving objects: 41% (5/12)Receiving objects: 50% (6/12)remote: Total 12 (delta 0), reused 4 (delta 0), pack-reused 0 (from 0)
Receiving objects: 58% (7/12)Receiving objects: 66% (8/12)Receiving objects: 75% (9/12)Receiving objects: 83% (10/12)Receiving objects: 91% (11/12)Receiving objects: 100% (12/12)Receiving objects: 100% (12/12), 120.76 KiB | 282.00 KiB/s, done.
+ git -C work/src sparse-checkout set libreoffice/agent-harness
remote: Enumerating objects: 24, done.
remote: Counting objects: 4% (1/24)remote: Counting objects: 8% (2/24)remote: Counting objects: 12% (3/24)remote: Counting objects: 16% (4/24)remote: Counting objects: 20% (5/24)remote: Counting objects: 25% (6/24)remote: Counting objects: 29% (7/24)remote: Counting objects: 33% (8/24)remote: Counting objects: 37% (9/24)remote: Counting objects: 41% (10/24)remote: Counting objects: 45% (11/24)remote: Counting objects: 50% (12/24)remote: Counting objects: 54% (13/24)remote: Counting objects: 58% (14/24)remote: Counting objects: 62% (15/24)remote: Counting objects: 66% (16/24)remote: Counting objects: 70% (17/24)remote: Counting objects: 75% (18/24)remote: Counting objects: 79% (19/24)remote: Counting objects: 83% (20/24)remote: Counting objects: 87% (21/24)remote: Counting objects: 91% (22/24)remote: Counting objects: 95% (23/24)remote: Counting objects: 100% (24/24)remote: Counting objects: 100% (24/24), done.
remote: Compressing objects: 4% (1/21)remote: Compressing objects: 9% (2/21)remote: Compressing objects: 14% (3/21)remote: Compressing objects: 19% (4/21)remote: Compressing objects: 23% (5/21)remote: Compressing objects: 28% (6/21)remote: Compressing objects: 33% (7/21)remote: Compressing objects: 38% (8/21)remote: Compressing objects: 42% (9/21)remote: Compressing objects: 47% (10/21)remote: Compressing objects: 52% (11/21)remote: Compressing objects: 57% (12/21)remote: Compressing objects: 61% (13/21)remote: Compressing objects: 66% (14/21)remote: Compressing objects: 71% (15/21)remote: Compressing objects: 76% (16/21)remote: Compressing objects: 80% (17/21)remote: Compressing objects: 85% (18/21)remote: Compressing objects: 90% (19/21)remote: Compressing objects: 95% (20/21)remote: Compressing objects: 100% (21/21)remote: Compressing objects: 100% (21/21), done.
Receiving objects: 4% (1/24)Receiving objects: 8% (2/24)Receiving objects: 12% (3/24)Receiving objects: 16% (4/24)Receiving objects: 20% (5/24)Receiving objects: 25% (6/24)Receiving objects: 29% (7/24)Receiving objects: 33% (8/24)Receiving objects: 37% (9/24)Receiving objects: 41% (10/24)Receiving objects: 45% (11/24)Receiving objects: 50% (12/24)remote: Total 24 (delta 0), reused 13 (delta 0), pack-reused 0 (from 0)
Receiving objects: 54% (13/24)Receiving objects: 58% (14/24)Receiving objects: 62% (15/24)Receiving objects: 66% (16/24)Receiving objects: 70% (17/24)Receiving objects: 75% (18/24)Receiving objects: 79% (19/24)Receiving objects: 83% (20/24)Receiving objects: 87% (21/24)Receiving objects: 91% (22/24)Receiving objects: 95% (23/24)Receiving objects: 100% (24/24)Receiving objects: 100% (24/24), 60.15 KiB | 354.00 KiB/s, done.
Updating files: 8% (2/24)Updating files: 12% (3/24)Updating files: 16% (4/24)Updating files: 20% (5/24)Updating files: 25% (6/24)Updating files: 29% (7/24)Updating files: 33% (8/24)Updating files: 37% (9/24)Updating files: 41% (10/24)Updating files: 45% (11/24)Updating files: 50% (12/24)Updating files: 54% (13/24)Updating files: 58% (14/24)Updating files: 62% (15/24)Updating files: 66% (16/24)Updating files: 70% (17/24)Updating files: 75% (18/24)Updating files: 79% (19/24)Updating files: 83% (20/24)Updating files: 87% (21/24)Updating files: 91% (22/24)Updating files: 95% (23/24)Updating files: 100% (24/24)Updating files: 100% (24/24), done.
+ git -C work/src remote get-url origin
https://github.com/HKUDS/CLI-Anything.git
+ git -C work/src rev-parse HEAD
34f519533bc175d2fe287ab8316b0dd99bb9cc43
+ git -C work/src log -1 --format=%ci
2026-09-22 10:01:28 +0800
+ grep -n '^ version=' work/src/libreoffice/agent-harness/setup.py
16: version="1.0.1",
+ uv venv -q --color never work/venv -p 3.12
+ uv pip install -q --color never -p work/venv -e work/src/libreoffice/agent-harness
+ export PATH="/home/user/cli/run/work/venv/bin:$PATH"
+ python3 --version
Python 3.12.15
+ python3 -c 'import importlib.metadata as m; print(m.version("cli-anything-libreoffice"))'
1.0.1
+ soffice --version
LibreOffice 25.2.3.2 520(Build:2)
+ grep ^PRETTY_NAME= /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
+ cli-anything-libreoffice document new --type writer -n '주간보고' -o work/a/report.lo-cli.json
Created writer document: 주간보고
name: 주간보고
version: 1.0
type: writer
settings:
page_width: 21cm
page_height: 29.7cm
margin_top: 2cm
margin_bottom: 2cm
margin_left: 2cm
margin_right: 2cm
metadata:
title:
author:
description:
subject:
created: 2026-10-14T15:04:50.814889
modified: 2026-10-14T15:04:50.814910
software: libreoffice-cli 1.0
style_count: 0
content_count: 0
+ cli-anything-libreoffice --project work/a/report.lo-cli.json writer add-heading -t '주간 보고' -l 1
Added heading (level 1)
type: heading
level: 1
text: 주간 보고
style:
+ cli-anything-libreoffice --project work/a/report.lo-cli.json writer add-paragraph -t 'CLI-Anything 테스트 문단입니다.'
Added paragraph
type: paragraph
text: CLI-Anything 테스트 문단입니다.
style:
+ cli-anything-libreoffice --project work/a/report.lo-cli.json document save
Saved to: work/a/report.lo-cli.json
saved: work/a/report.lo-cli.json
+ cli-anything-libreoffice --project work/a/report.lo-cli.json export render work/a/report.hwp -p hwp
Error: Unknown preset: hwp. Available: odt, ods, odp, html, text, pdf, docx, xlsx, pptx, csv
hwp_exit:1
hwp_file:absent

터미널 화면은 설치 환경을 확인한 뒤 HWP 내보내기를 요청한 장면입니다. setup.py와 importlib가 보고한 배포판 버전은 1.0.1, 가상환경의 Python은 3.12.15입니다. 새 문서의 content_count는 0이고, 제목과 문단을 추가한 결과가 차례로 찍힙니다. document save가 work/a/report.lo-cli.json에 저장했다고 알린 뒤에 내보내기 오류가 나므로, 손볼 곳은 저장 경로가 아니라 프리셋 선택입니다.
hwp 내보내기 출력은 Error: Unknown preset: hwp로 시작하고, Available 목록에는 odt, ods, odp, html, text, pdf, docx, xlsx, pptx, csv가 나옵니다. hwp_exit 값은 1, hwp_file 값은 absent입니다. 파일 이름만 바꿔 같은 요청을 되풀이해 봐야 소용이 없습니다. 출력 경로의 확장자가 지원 프리셋을 늘려 주지는 않으니, 허용 목록 안의 프리셋으로 요청을 바꾸고 그 목록이 업무에서 요구하는 형식과 맞는지부터 따져야 합니다. 덧붙여 문서 정보의 version은 1.0, software는 libreoffice-cli 1.0으로 찍혀 설치 배포판 1.0.1과 번호가 다릅니다. 그래서 환경 표에는 importlib가 보고한 1.0.1을 하네스 버전으로 적었고, 세 문자열은 출력 블록에 그대로 남아 있습니다.
같은 원문을 pdf와 docx로 내보내고 본문까지 대조하기
허용 목록에 있는 pdf와 docx를 쓰면 풀립니다. 해결 스크립트는 같은 제목과 문단으로 프로젝트를 다시 만들고, -p pdf로 내보낸 뒤 pdftotext -enc UTF-8로 텍스트를 뽑습니다. 이어서 -p docx로 내보내고, 표준 라이브러리 zipfile로 word/document.xml의 w:t 문자를 이어 붙입니다. 검사 스크립트는 기대한 문장과 다르면 그 자리에서 멈추고, 같으면 성공 줄을 찍습니다.
cli-anything-libreoffice document new --type writer -n '주간보고' -o work/b/report.lo-cli.json
cli-anything-libreoffice --project work/b/report.lo-cli.json writer add-heading -t '주간 보고' -l 1
cli-anything-libreoffice --project work/b/report.lo-cli.json writer add-paragraph -t 'CLI-Anything 테스트 문단입니다.'
cli-anything-libreoffice --project work/b/report.lo-cli.json document save
cli-anything-libreoffice --project work/b/report.lo-cli.json export render work/b/report.pdf -p pdf
pdftotext -enc UTF-8 work/b/report.pdf work/b/report.pdf.txt
cli-anything-libreoffice --project work/b/report.lo-cli.json export render work/b/report.docx -p docx
python3 work/b/check_text.py
check_text.py는 해결 스크립트가 검사 직전에 만드는 파일입니다. PDF는 폼피드와 빈 줄을 뺀 줄이 제목, 문단 순서와 같은지 봅니다. docx는 word/document.xml의 텍스트를 이어 붙인 뒤 제목 문자열과 문단 문자열이 각각 들어 있는지 봅니다. 파일이 생겼는지에서 그치지 않고 입력한 문장까지 비교하니, 내용이 빠진 결과가 성공으로 넘어가지 않습니다.
import html
import pathlib
import re
import sys
import zipfile
root = pathlib.Path(__file__).resolve().parent
heading = "주간 보고"
paragraph = "CLI-Anything 테스트 문단입니다."
pdf = (root / "report.pdf.txt").read_text(encoding="utf-8").replace("\f", "")
pdf_lines = [line.strip() for line in pdf.splitlines() if line.strip()]
print("pdf_lines", pdf_lines)
if pdf_lines != [heading, paragraph]:
sys.exit("PDF mismatch")
print("SUCCESS: pdf text matches heading and paragraph")
with zipfile.ZipFile(root / "report.docx") as zf:
xml = zf.read("word/document.xml").decode("utf-8")
texts = [html.unescape(part) for part in re.findall(r"<w:t[^>]*>(.*?)</w:t>", xml, flags=re.S)]
joined = "".join(texts)
print("docx_text", joined)
if heading not in joined or paragraph not in joined:
sys.exit("DOCX mismatch")
print("SUCCESS: docx document.xml contains heading and paragraph")
아래가 실행 결과입니다. PDF의 file_size는 28401, DOCX는 5717이고 두 내보내기 모두 method가 libreoffice-headless로 나옵니다. 그다음 본문 검사에서 성공 문구가 찍히고 check_exit:0으로 끝나는지까지 이어서 봐야 합니다.
+ rm -rf work/b
+ cli-anything-libreoffice document new --type writer -n '주간보고' -o work/b/report.lo-cli.json
Created writer document: 주간보고
name: 주간보고
version: 1.0
type: writer
settings:
page_width: 21cm
page_height: 29.7cm
margin_top: 2cm
margin_bottom: 2cm
margin_left: 2cm
margin_right: 2cm
metadata:
title:
author:
description:
subject:
created: 2026-10-14T15:04:55.648107
modified: 2026-10-14T15:04:55.648128
software: libreoffice-cli 1.0
style_count: 0
content_count: 0
+ cli-anything-libreoffice --project work/b/report.lo-cli.json writer add-heading -t '주간 보고' -l 1
Added heading (level 1)
type: heading
level: 1
text: 주간 보고
style:
+ cli-anything-libreoffice --project work/b/report.lo-cli.json writer add-paragraph -t 'CLI-Anything 테스트 문단입니다.'
Added paragraph
type: paragraph
text: CLI-Anything 테스트 문단입니다.
style:
+ cli-anything-libreoffice --project work/b/report.lo-cli.json document save
Saved to: work/b/report.lo-cli.json
saved: work/b/report.lo-cli.json
+ cli-anything-libreoffice --project work/b/report.lo-cli.json export render work/b/report.pdf -p pdf
Exported to: work/b/report.pdf
action: convert
output: /home/user/cli/run/work/b/report.pdf
format: pdf
method: libreoffice-headless
libreoffice_version: LibreOffice 25.2.3.2 520(Build:2)
file_size: 28401
preset: pdf
source_type: writer
+ pdftotext -enc UTF-8 work/b/report.pdf work/b/report.pdf.txt
pdftotext_exit:0
+ cat work/b/report.pdf.txt
주간 보고
CLI-Anything 테스트 문단입니다.
+ cli-anything-libreoffice --project work/b/report.lo-cli.json export render work/b/report.docx -p docx
Exported to: work/b/report.docx
action: convert
output: /home/user/cli/run/work/b/report.docx
format: docx
method: libreoffice-headless
libreoffice_version: LibreOffice 25.2.3.2 520(Build:2)
file_size: 5717
preset: docx
source_type: writer
+ cat > work/b/check_text.py << 'PY'
+ python3 work/b/check_text.py
pdf_lines ['주간 보고', 'CLI-Anything 테스트 문단입니다.']
SUCCESS: pdf text matches heading and paragraph
docx_text 주간 보고CLI-Anything 테스트 문단입니다.
SUCCESS: docx document.xml contains heading and paragraph
check_exit:0

캡처의 output 줄은 실제 작업 폴더 경로를 보여 주고, 본문은 그 앞부분만 /home/user/cli로 바꿔 옮겼습니다. 명령과 만들어진 파일 이름, 변환 결과는 같습니다.
이 화면은 PDF·DOCX를 만들고 본문 검사까지 끝낸 모습입니다. PDF 출력의 format과 preset은 pdf, method는 libreoffice-headless입니다. pdftotext_exit 값은 0이고, 빈 줄을 뺀 pdf_lines에는 제목과 그 뒤의 문단, 두 줄이 남습니다. 변환 완료 메시지만이 아니라 입력 문장과 일치하는지까지 확인한 것이 이번 실행에서 잡은 성공 기준입니다.
DOCX 출력도 method는 libreoffice-headless, format과 preset은 docx입니다. document.xml의 w:t를 이어 붙인 docx_text에서는 제목과 문단이 공백 없이 붙어 있지만, 검사할 문자열은 둘 다 들어 있습니다. check_exit 값이 0이니 이 검사는 제목과 문단을 모두 찾았습니다. PDF 쪽은 줄 순서를, DOCX 쪽은 문자열 포함 여부를 본 것이라 두 검사가 보장하는 범위가 조금 다릅니다.
같은 Writer 프로젝트에서 PDF와 DOCX를 차례로 만들고 본문까지 대조할 수 있으니 보고서 배치 작업에 붙이기 수월합니다. 대신 변환에는 soffice가 있어야 하고, 출력 이름과 실제 형식은 따로 관리해야 합니다. 저라면 프로젝트 작성 성공, 변환 성공, 본문 검사 성공을 각각 기록해 두겠습니다. 그래야 문장이 빠진 문제와 변환기 실행 환경 문제가 한 장애로 뭉뚱그려지지 않습니다.
빈 PATH, 기존 파일, 이름만 docx인 파일
본문 검증이 끝나면 배치 작업에서 다시 마주칠 법한 환경·옵션 문제를 비교합니다. 점검 스크립트는 빈 PATH, 같은 PDF 경로 재사용, 확장자와 프리셋 불일치, document new의 -p 오용을 차례로 돌립니다. 그 전에 실행 계정의 환경부터 기록합니다.
첫머리의 환경 표가 이 버전 확인 명령에서 나왔습니다. 하네스가 도는 환경과 변환기가 보이는 경로를 한 번에 보려고 넣은 명령입니다. OS 값은 PRETTY_NAME 줄의 따옴표 안에서, 실행일은 date -I 출력에서 읽습니다. command -v soffice는 /usr/bin/soffice를 내놓았고 pdftotext -v의 첫 줄에는 25.03.0이 남았습니다. 서버에 도구가 깔려 있어도 작업 프로세스의 PATH가 다르면 변환은 실패하니, 이 값은 실행 계정의 환경에서 읽은 것이어야 의미가 있습니다.
grep ^PRETTY_NAME= /etc/os-release
date -I
python3 --version
soffice --version
command -v soffice
pdftotext -v
document new의 -p는 페이지 프로필입니다. 여기에 내보내기 프리셋인 pdf를 넘기면 Unknown profile 오류가 나고, 허용 목록에는 a4_portrait, a4_landscape 같은 페이지 구성이 나옵니다. 이번 profile_exit 값은 1, bad_profile_file 값은 absent로, 프로젝트 생성 단계에서 이미 실패했습니다. 페이지 크기는 document new에서, 파일 형식은 export render의 -p에서 정하도록 설정 항목을 아예 나눠 두면 헷갈릴 일이 없습니다.
cli-anything-libreoffice document new --type writer -n '주간보고' -p pdf -o work/c/bad-profile.lo-cli.json
이번에는 soffice가 없는 디렉터리만 PATH에 두고 같은 프로젝트를 내보냅니다. 이 환경에서 PDF는 LibreOffice is not installed 오류로 멈추는데 text는 파일을 만듭니다. text 결과에는 수준 1 제목 앞에 #가 붙고 입력한 문단이 이어집니다. 텍스트 저장이 됐다고 PDF 변환 환경까지 멀쩡하다고 보면 안 되는 이유입니다.
mkdir -p work/c/emptybin
PATH="$(pwd)/work/c/emptybin" command -v soffice || echo soffice_on_limited_path:no
PATH="$(pwd)/work/c/emptybin" work/venv/bin/cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/nolo.pdf -p pdf
PATH="$(pwd)/work/c/emptybin" work/venv/bin/cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/report.txt -p text
원래 PATH에서는 -p pdf가 파일을 만들지만, 같은 경로를 –overwrite 없이 다시 쓰면 Output file exists 오류가 납니다. –overwrite를 붙이면 PDF를 다시 쓰고 본문을 대조합니다. 이름만 named.docx인 경로에 -p pdf를 주면 format은 pdf이고 앞 5바이트는 %PDF-입니다. 확장자로는 형식을 알 수 없다는 뜻입니다. 같은 경로에 -p docx와 –overwrite를 주면 앞 바이트가 PK로 바뀌는지, 본문도 들어 있는지를 이어서 검사합니다.
cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/report.pdf -p pdf
cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/report.pdf -p pdf
cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/report.pdf -p pdf --overwrite
pdftotext -enc UTF-8 work/c/report.pdf work/c/report.pdf.txt
cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/named.docx -p pdf
python3 -c 'print(open("work/c/named.docx","rb").read(5))'
cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/named.docx -p docx --overwrite
python3 work/c/check_out.py
결과를 보면 제한된 PATH의 PDF 요청은 nolo_exit:1로, 기존 PDF 재실행은 reexport_exit:1로 끝납니다. 마지막 검사에서는 text, 덮어쓴 PDF, DOCX의 본문 확인이 차례로 나오고 check_exit:0이 남습니다.
+ rm -rf work/c
+ grep ^PRETTY_NAME= /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
+ date -I
2026-10-14
+ python3 --version
Python 3.12.15
+ python3 -c 'import importlib.metadata as m
for name in ("cli-anything-libreoffice", "click", "prompt-toolkit", "defusedxml"):
print(name, m.version(name))'
cli-anything-libreoffice 1.0.1
click 8.5.0
prompt-toolkit 3.0.53
defusedxml 0.7.1
+ soffice --version
LibreOffice 25.2.3.2 520(Build:2)
+ command -v soffice
/usr/bin/soffice
+ pdftotext -v
pdftotext version 25.03.0
Copyright 2005-2025 The Poppler Developers - http://poppler.freedesktop.org
Copyright 1996-2011, 2022 Glyph & Cog, LLC
pdftotext_version_exit:0
commit:34f519533bc175d2fe287ab8316b0dd99bb9cc43
+ curl -sI --max-time 20 https://github.com/HKUDS/CLI-Anything
curl_repo_exit:0
HTTP/2 200
+ curl -sI --max-time 20 https://github.com/HKUDS/CLI-Anything/blob/34f519533bc175d2fe287ab8316b0dd99bb9cc43/libreoffice/agent-harness/LIBREOFFICE.md
curl_blob_exit:0
HTTP/2 200
+ curl -sI --max-time 20 https://www.libreoffice.org/
curl_lo_exit:0
HTTP/2 200
+ curl -sI --max-time 20 https://help.libreoffice.org/latest/en-US/text/shared/guide/start_parameters.html
curl_lo_params_exit:0
HTTP/2 200
+ curl -sI --max-time 20 https://docs.python.org/3/library/zipfile.html
curl_zipfile_exit:0
HTTP/2 200
+ curl -sI --max-time 20 https://manpages.debian.org/trixie/poppler-utils/pdftotext.1.en.html
curl_pdftotext_exit:0
HTTP/2 200
+ cli-anything-libreoffice document new --type writer -n '주간보고' -p pdf -o work/c/bad-profile.lo-cli.json
Error: Unknown profile: pdf. Available: a4_portrait, a4_landscape, letter_portrait, letter_landscape, legal_portrait, presentation_16_9, presentation_4_3
profile_exit:1
bad_profile_file:absent
+ cli-anything-libreoffice document new --type writer -n '주간보고' -o work/c/report.lo-cli.json
Created writer document: 주간보고
name: 주간보고
version: 1.0
type: writer
settings:
page_width: 21cm
page_height: 29.7cm
margin_top: 2cm
margin_bottom: 2cm
margin_left: 2cm
margin_right: 2cm
metadata:
title:
author:
description:
subject:
created: 2026-10-14T15:05:12.884732
modified: 2026-10-14T15:05:12.884759
software: libreoffice-cli 1.0
style_count: 0
content_count: 0
+ cli-anything-libreoffice --project work/c/report.lo-cli.json writer add-heading -t '주간 보고' -l 1
Added heading (level 1)
type: heading
level: 1
text: 주간 보고
style:
+ cli-anything-libreoffice --project work/c/report.lo-cli.json writer add-paragraph -t 'CLI-Anything 테스트 문단입니다.'
Added paragraph
type: paragraph
text: CLI-Anything 테스트 문단입니다.
style:
+ cli-anything-libreoffice --project work/c/report.lo-cli.json document save
Saved to: work/c/report.lo-cli.json
saved: work/c/report.lo-cli.json
+ mkdir -p work/c/emptybin
+ PATH=/home/user/cli/run/work/c/emptybin command -v soffice || echo soffice_on_limited_path:no
soffice_on_limited_path:no
+ PATH=/home/user/cli/run/work/c/emptybin work/venv/bin/cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/nolo.pdf -p pdf
Error: LibreOffice is not installed. Install it with:
apt install libreoffice # Debian/Ubuntu
brew install --cask libreoffice # macOS
winget install TheDocumentFoundation.LibreOffice # Windows
nolo_exit:1
nolo_file:absent
+ PATH=/home/user/cli/run/work/c/emptybin work/venv/bin/cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/report.txt -p text
Exported to: work/c/report.txt
output: /home/user/cli/run/work/c/report.txt
format: text
file_size: 55
+ cat work/c/report.txt
# 주간 보고
CLI-Anything 테스트 문단입니다.
+ cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/report.pdf -p pdf
Exported to: work/c/report.pdf
action: convert
output: /home/user/cli/run/work/c/report.pdf
format: pdf
method: libreoffice-headless
libreoffice_version: LibreOffice 25.2.3.2 520(Build:2)
file_size: 28401
preset: pdf
source_type: writer
+ cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/report.pdf -p pdf
Error: Output file exists: work/c/report.pdf. Use --overwrite.
reexport_exit:1
+ cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/report.pdf -p pdf --overwrite
Exported to: work/c/report.pdf
action: convert
output: /home/user/cli/run/work/c/report.pdf
format: pdf
method: libreoffice-headless
libreoffice_version: LibreOffice 25.2.3.2 520(Build:2)
file_size: 28401
preset: pdf
source_type: writer
+ pdftotext -enc UTF-8 work/c/report.pdf work/c/report.pdf.txt
pdftotext_exit:0
+ cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/named.docx -p pdf
Exported to: work/c/named.docx
action: convert
output: /home/user/cli/run/work/c/named.docx
format: pdf
method: libreoffice-headless
libreoffice_version: LibreOffice 25.2.3.2 520(Build:2)
file_size: 28401
preset: pdf
source_type: writer
+ python3 -c 'print(open("work/c/named.docx","rb").read(5))'
b'%PDF-'
+ cli-anything-libreoffice --project work/c/report.lo-cli.json export render work/c/named.docx -p docx --overwrite
Exported to: work/c/named.docx
action: convert
output: /home/user/cli/run/work/c/named.docx
format: docx
method: libreoffice-headless
libreoffice_version: LibreOffice 25.2.3.2 520(Build:2)
file_size: 5717
preset: docx
source_type: writer
+ cat > work/c/check_out.py << 'PY'
+ python3 work/c/check_out.py
text_preset # 주간 보고
CLI-Anything 테스트 문단입니다.
SUCCESS: text preset matches heading and paragraph
pdf_lines ['주간 보고', 'CLI-Anything 테스트 문단입니다.']
SUCCESS: overwritten pdf text matches heading and paragraph
named_docx_prefix b'PK'
named_docx_text 주간 보고CLI-Anything 테스트 문단입니다.
SUCCESS: named.docx with preset docx contains heading and paragraph
check_exit:0

이 캡처에는 환경 확인, 오류 재현, 덮어쓴 DOCX 검사가 함께 들어 있습니다. 제한된 PATH에서 soffice_on_limited_path 값은 no이고, PDF 요청은 nolo_exit:1, nolo_file:absent로 끝납니다. 같은 실행 앞부분에서 /usr/bin/soffice가 확인됐으니, 이 설치 안내를 서버 전체에 LibreOffice가 없다는 말로 받아들이면 원인을 놓칩니다. 실제 변환 작업에 어떤 PATH가 넘어갔는지를 먼저 되짚어야 합니다.
덮어쓰기 비교에서는 기존 경로 오류와 –overwrite 안내가 먼저 나오고, 다시 변환한 PDF의 본문 검사가 성공합니다. named.docx에 pdf 프리셋을 준 직후에는 b’%PDF-‘가, docx로 덮어쓴 뒤에는 b’PK’가 나옵니다. 마지막 본문 검사가 check_exit:0으로 끝났으니 파일 이름만 고친 경우와 형식을 실제로 바꾼 경우가 분명히 갈립니다. 재시도 작업에서 경로, 프리셋, 덮어쓰기 허용 여부를 한 묶음으로 다뤄야 하는 이유가 여기 있습니다.
버전 기록과 헬스체크에 어느 값을 걸지도 따로 정해 둬야 합니다. 이 출력에서 헷갈리기 쉬운 것은 설치 배포판의 1.0.1, 문서 정보의 version 1.0, software의 libreoffice-cli 1.0이 서로 다른 자리에 남는다는 점입니다. 업데이트 점검에서는 문서 메타데이터 말고 importlib의 배포판 버전과 soffice –version을 배포 기록에 묶겠습니다. 같은 -p라도 document new에서는 페이지 프로필, export render에서는 출력 프리셋이니 설정 파일에서도 두 값을 다른 항목으로 둡니다. 하네스를 바꿀 때는 기존 설정을 넘기기 전에 허용 목록과, 성공했던 본문 검사 결과를 다시 맞대어 봐야 합니다.
헬스체크는 text 파일이 만들어졌다고 통과시키지 않고, 변환 작업과 같은 PATH에서 soffice를 찾는지 보도록 짜겠습니다. 배포 뒤에 작은 Writer 프로젝트를 PDF로 변환하고 본문을 뽑는 검사까지 붙이면 변환 경로가 살아 있는지 가늠하기 쉽습니다. Kubernetes에서 돌린다면 준비 상태 점검도 실제 변환 작업의 실행 환경에 맞추고, 배포할 때 점검 경로와 환경 설정을 같이 갱신해야 합니다. 보고서 요청을 받는 서비스와 변환 작업을 떼어 두면 soffice 경로 오류를 변환 작업 쪽에서 쫓기도 편합니다.
PDF·DOCX는 본문 대조까지, HWP는 다른 변환 경로로
실행 결과를 용도와 실패 조건별로 모으면 아래 표가 됩니다. PDF와 DOCX는 같은 원문으로 만들었고, 각 형식에 맞는 방식으로 본문을 대조했습니다. hwp 요청은 허용 프리셋 검사에서 멈추니, 납품 형식을 정하는 단계에서 따로 길을 찾아야 합니다.
| 선택 | 이번 실행 결과 |
|---|---|
-p hwp와 report.hwp | Unknown preset, 종료 코드 1, 파일 없음 |
-p pdf | pdftotext 두 줄이 제목과 문단과 같음 |
-p docx | document.xml에 제목과 문단이 있음 |
빈 PATH와 -p pdf | LibreOffice is not installed, 파일 없음 |
빈 PATH와 -p text | 제목 앞에 # 가 붙은 텍스트 파일 |
| 같은 PDF 경로를 다시 실행 | Output file exists, –overwrite 안내 |
--overwrite | PDF를 다시 쓰고 본문 두 줄이 같음 |
이름만 .docx, -p pdf | format은 pdf, 앞 바이트는 %PDF- |
그 경로에 -p docx --overwrite | format은 docx, 앞 바이트는 PK, 본문 포함 |
document new -p pdf | Unknown profile, 프로젝트 파일 없음 |
세 스크립트의 출력으로 판단하면 이 하네스는 JSON 프로젝트를 원본으로 두고 PDF·DOCX를 뽑는 보고서 작업에 어울립니다. PDF가 목적이면 -p pdf 뒤에 pdftotext -enc UTF-8 검사를 붙이는 구성을 추천합니다. Word 파일이 필요하면 -p docx로 내보내고 document.xml에서 제목과 문단을 대조하면 됩니다. HWP가 꼭 있어야 하는 업무라면 이번 허용 목록으로 보아 다른 변환 경로를 선택해야 합니다.
운영에서는 /usr/bin/soffice가 보이는 실행 환경을 고정하고, 작업마다 출력 경로를 나눠 의도치 않은 덮어쓰기를 막겠습니다. –overwrite는 같은 파일을 다시 만들 때만 쓰고, format과 실제 파일 내용을 같이 확인합니다. 실시간 요청에 붙인다면 문서 생성 요청을 받는 처리와 LibreOffice 변환 작업을 나누고, 작업을 접수한 뒤 검사를 마친 파일을 건네는 구성을 택하겠습니다. 텍스트만 필요한 업무라면 -p text가 더 간단한 선택입니다. 이번 점검 스크립트가 성공 기준으로 삼은 것도 파일 생성 메시지가 아니라, 입력한 한글 제목과 문단이 최종 결과에 살아 있는지였습니다.
CLI-Anything과 LibreOffice 내보내기 자료
아래 주소는 점검 스크립트에서 curl -sI로 요청했고 상태 줄은 HTTP/2 200이었습니다. 도구 동작은 이번 실행 결과를 기준으로 썼고, 링크는 각 프로젝트의 문서 위치입니다. HKUDS CLI-Anything 저장소는 이번 클론의 origin과 같은 프로젝트이고, 클론한 리비전의 설명은 이번 커밋의 LibreOffice 하네스 문서에 있습니다. 변환에 쓰인 soffice는 LibreOffice 프로젝트의 것이고, headless 변환에 쓰는 시작 인자는 LibreOffice 시작 매개변수 문서가 설명합니다. docx 안의 document.xml을 읽을 때 쓴 표준 라이브러리는 Python zipfile 문서를, 이번 -enc UTF-8 추출은 Debian 13 pdftotext 매뉴얼을 보면 됩니다.






