@kaydash9999/banker-plugins 0.20.1 → 0.20.5

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 (82) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +195 -1
  4. package/PRIVACY.md +13 -3
  5. package/README.md +27 -4
  6. package/bin/banker.js +84 -40
  7. package/bin/lib/telemetry-config.mjs +31 -16
  8. package/codex/manifest.json +1 -1
  9. package/codex/transform-matrix.md +1 -1
  10. package/commands/setup.md +23 -0
  11. package/hooks/progress.mjs +143 -38
  12. package/hooks/register.mjs +13 -3
  13. package/hooks/update-checkin.mjs +15 -7
  14. package/hooks/update-fetch.mjs +15 -9
  15. package/hooks/update-notify.mjs +2 -9
  16. package/package.json +1 -1
  17. package/skills/3d-intro-build/SKILL.md +11 -5
  18. package/skills/3d-intro-build/references/azure-adapter.mjs +374 -81
  19. package/skills/3d-intro-build/references/curate.mjs +48 -14
  20. package/skills/3d-intro-build/references/preview-lib.mjs +51 -15
  21. package/skills/3d-intro-build/references/prompts.md +4 -4
  22. package/skills/3d-intro-build/references/serve.mjs +6 -1
  23. package/skills/3d-intro-build/references/verify-intro.mjs +16 -9
  24. package/skills/3d-intro-build/references/video-pool.mjs +250 -135
  25. package/skills/3d-intro-setup/SKILL.md +41 -10
  26. package/skills/3d-intro-setup/references/azure-adapter.mjs +374 -81
  27. package/skills/3d-intro-setup/references/video-pool.mjs +250 -135
  28. package/skills/anti-drm/SKILL.md +6 -3
  29. package/skills/anti-drm/scripts/anti-drm.mjs +49 -12
  30. package/skills/anti-drm/scripts/instruction-toggle.mjs +105 -20
  31. package/skills/audit-mock/SKILL.md +1 -1
  32. package/skills/audit-security/SKILL.md +64 -2
  33. package/skills/deep-research/SKILL.md +5 -0
  34. package/skills/docs-setup/SKILL.md +37 -7
  35. package/skills/lineage/SKILL.md +12 -12
  36. package/skills/lineage/lineage.py +399 -300
  37. package/skills/motion-graphic-make/SKILL.md +1 -1
  38. package/skills/motion-graphic-make/references/curate.mjs +48 -14
  39. package/skills/motion-graphic-make/references/preview-lib.mjs +51 -15
  40. package/skills/motion-graphic-make/references/serve.mjs +6 -1
  41. package/skills/motion-graphic-setup/SKILL.md +58 -13
  42. package/skills/obsidizer/SKILL.md +1 -1
  43. package/skills/obsidizer/obsidize.mjs +8 -1
  44. package/skills/omc-patch/SKILL.md +80 -23
  45. package/skills/omc-patch/scripts/omc-patch.mjs +460 -116
  46. package/skills/payload-mon/SKILL.md +5 -2
  47. package/skills/payload-mon/scripts/payload-mon.mjs +137 -30
  48. package/skills/payload-mon/scripts/payload-size.mjs +4 -1
  49. package/skills/pdf-vision-extract/SKILL.md +11 -6
  50. package/skills/ralph-qa/SKILL.md +125 -151
  51. package/skills/ralph-qa/references/gemini-seat.mjs +67 -19
  52. package/skills/ralph-qa/references/payload-scan.mjs +22 -7
  53. package/skills/ralph-qa/references/seat-payload.mjs +646 -0
  54. package/skills/ralph-qa/references/seat-run.mjs +956 -0
  55. package/skills/ralph-qa/references/seat-spawn.mjs +205 -0
  56. package/skills/ralph-qa/references/verifier-probe.mjs +57 -16
  57. package/skills/setup-harness-factory/SKILL.md +17 -1
  58. package/skills/setup-insane-search/SKILL.md +16 -0
  59. package/skills/setup-java/SKILL.md +20 -1
  60. package/skills/setup-lsp/SKILL.md +56 -13
  61. package/skills/setup-mcp/SKILL.md +73 -4
  62. package/skills/setup-node/SKILL.md +31 -6
  63. package/skills/setup-omc/SKILL.md +24 -0
  64. package/skills/setup-omc-hud/SKILL.md +13 -5
  65. package/skills/setup-omc-hud/assets/apply-hud.mjs +381 -27
  66. package/skills/setup-omc-hud/assets/omc-hud-custom.mjs +81 -14
  67. package/skills/setup-omc-hud/scripts/claude-update-last.mjs +5 -3
  68. package/skills/setup-playwright/SKILL.md +45 -2
  69. package/skills/setup-pwsh/SKILL.md +19 -0
  70. package/skills/setup-python/SKILL.md +45 -7
  71. package/skills/setup-sandbox/SKILL.md +30 -5
  72. package/skills/setup-stitch/SKILL.md +22 -0
  73. package/skills/setup-tmux/SKILL.md +24 -4
  74. package/skills/tone-compact/SKILL.md +2 -1
  75. package/skills/tone-compact/scripts/instruction-toggle.mjs +105 -20
  76. package/skills/ultra-chain/SKILL.md +4 -3
  77. package/skills/ultra-chain/references/preview-lib.mjs +51 -15
  78. package/skills/ultra-chain/references/serve.mjs +6 -1
  79. package/skills/ultra-interview/SKILL.md +1 -0
  80. package/skills/update-banker/SKILL.md +19 -3
  81. package/skills/vertical-pptx/SKILL.md +6 -3
  82. package/skills/vertical-pptx-setup/SKILL.md +76 -20
@@ -36,6 +36,44 @@ for v in 3.13 3.12 3.11 3.10 3.9; do command -v python$v >/dev/null && echo "pyt
36
36
  **python 버전 주의.** `python3` 이 3.6 이나 3.7 인 배포판이 있다(RHEL8/Rocky8 계열의 기본이 3.6.8 이다).
37
37
  python-pptx 는 그보다 높은 버전을 요구하므로 `python3` 을 그대로 쓰지 말고 위 스캔에서 고른 인터프리터를 쓴다.
38
38
 
39
+ ### 네트워크 확인 (내려받기 전)
40
+
41
+ 이 스킬이 내려받는 원천에 닿는지 먼저 본다(curl 은 10초, npm 은 15초 안). 닿지 않으면 그 원천에서 받는 단계는 하지 않고, 이미 설치된 것의 확인만 한 뒤 "오프라인이라 설치하지 않음(필요한 것: <못 깐 것>)" 으로 보고한다.
42
+
43
+ ```bash
44
+ [ "${CODEX_SANDBOX_NETWORK_DISABLED:-}" = 1 ] && echo "Codex sandbox 가 네트워크를 막음(아래 실패는 오프라인이 아님)"
45
+ in15() { "$@" & p=$!; ( sleep 15 && kill "$p" ) > /dev/null 2>&1 & k=$!; wait "$p" 2>/dev/null; r=$?; kill "$k" 2>/dev/null; wait "$k" 2>/dev/null; [ "$r" = 143 ] && echo "15초 안에 끝나지 않아 멈춤(패킷을 버리는 망일 수 있음)"; return "$r"; }
46
+ in15 sh -c 'cd ~ && exec npm ping --fetch-retries=0 --fetch-timeout=10000 --no-update-notifier' # pptxgenjs
47
+ # pip 이 읽는 설정 전부(PIP_CONFIG_FILE 포함)를 홈 폴더에서 읽는다. install 절이 global 절보다 앞선다
48
+ L=$( (cd ~ && { pip config list || pip3 config list || python3.13 -m pip config list || python3.12 -m pip config list || python3.11 -m pip config list || python3 -m pip config list; }) 2>/dev/null) || echo "pip 설정을 읽지 못함(pip 이 없거나 10 미만). 사내 미러를 쓰면 확인 주소를 그 미러로 바꿔 본다"
49
+ I="${PIP_INDEX_URL:-$(printf '%s\n' "$L" | sed -n "s/^install\.index-url='\(.*\)'$/\1/p")}"
50
+ I="${I:-$(printf '%s\n' "$L" | sed -n "s/^global\.index-url='\(.*\)'$/\1/p")}"
51
+ I=$(printf %s "${I:-https://pypi.org/simple}" | sed -E 's#^([A-Za-z][A-Za-z0-9+.-]*://)[^/]*@#\1#; s#/$##') # 주소의 사용자 정보는 뗌
52
+ printf 'url = "%s"\n' "$I/pip/" | curl --ssl-no-revoke -K - -sSI -o /dev/null --connect-timeout 5 --max-time 10 # python-pptx
53
+ curl --ssl-no-revoke -sSI -o /dev/null --connect-timeout 5 --max-time 10 https://download.documentfoundation.org # LibreOffice
54
+ ```
55
+
56
+ - `npm ping` 은 홈 폴더에서 부른다. 지금 폴더에 있는 프로젝트의 `.npmrc` 가 정한 레지스트리와 토큰을 따르지 않게 하려는 것이다.
57
+ - `npm ping` 은 15초 감시(`in15`) 안에서 부른다. 패킷을 버리는 망에서 npm 은 연결에 시간 제한을 두지 않아(`--fetch-timeout` 은 받는 중의 쉼만 잰다) OS 의 연결 재시도가 끝날 때까지 기다린다(Rocky 8.10 실측 130초). 15초 안에 끝나지 않으면 `15초 안에 끝나지 않아 멈춤(패킷을 버리는 망일 수 있음)` 과 종료 143 이다. `--no-update-notifier` 는 npm 이 제 판을 묻는 요청을 하나 더 내지 않게 한다.
58
+ - `npm ping` 이 실패하면 ping 에 답하지 않는 사내 미러일 수 있다. `(cd ~ && T=$(mktemp -d) && { npm view pptxgenjs version --fetch-retries=0 --fetch-timeout=10000 --cache "$T" --no-update-notifier; r=$?; rm -rf "$T"; exit $r; })` 도 실패해야 오프라인으로 본다. 이 스킬이 설치할 패키지(`pptxgenjs`)를 빈 캐시로 물으므로 옛 값이 나올 수 없다. ping 이 15초 감시에 걸려 멈췄으면(종료 143) 이 확인은 하지 않는다. 같은 레지스트리라 같은 시간을 또 기다린다.
59
+ - `npm` 이 없으면(명령 없음) 오프라인이 아니라 확인하지 못한 것이다. Node 를 먼저 갖춘다(`setup-node`).
60
+ - `npm ping` 은 npm 의 프록시 설정(`proxy`, `https-proxy`)을 따른다. 사내 미러만 열린 망이면 그 미러가 쓰이는지 npm 설정을 본다.
61
+ - `curl -sSI` 는 머리만 받는다(본문을 받지 않음). 닿지 못하면 종료 코드가 0 이 아니다(HTTP 상태와 관계없음). `curl` 은 `HTTPS_PROXY` 를 따른다. 확인 줄은 `--ssl-no-revoke` 를 준다. Windows 의 Git Bash curl 도 Schannel 이라, 폐지 목록 서버에 닿지 못하는 망에서 폐지 확인이 실패하면 종료 60 이다(Windows 박스 실측). 다른 curl 은 이 깃발을 받고 무시한다(macOS curl 8.7.1, Rocky 8.10 curl 7.61.1 실측).
62
+ - PyPI 는 pip 이 쓰는 색인(`PIP_INDEX_URL`, `pip config list` 의 `install.index-url` 이나 `global.index-url`, 없으면 pypi.org)의 작은 쪽(`<색인>/pip/`)을 본다.
63
+ - `pip config get` 은 `PIP_CONFIG_FILE` 로 준 파일을 읽지 않아(pip 25.0 실측) `pip config list` 를 읽는다.
64
+ - Rocky 8 의 기본 pip(9.0.3, `pip3`)은 `config` 명령이 없고 `pip` 이름은 없다(Rocky 8.10 실측). 그래서 판 붙은 python(3.13, 3.12, 3.11)의 pip 도 차례로 본다.
65
+ - 홈 폴더에서 읽는다. `python3 -m pip` 는 지금 폴더를 import 경로 맨 앞에 두어, 지금 폴더에 있는 남의 저장소의 `pip/` 가 실행될 수 있다.
66
+ - bash 블록은 주소를 명령 인자가 아니라 표준 입력의 curl 설정(`-K -`)으로 넘긴다. 명령 인자는 같은 PC 의 다른 사용자가 `ps` 로 볼 수 있어, 경로나 질의에 든 토큰도 거기 싣지 않는다. 사용자 정보(`사용자:토큰@`)는 떼고 묻는다. 닿는지만 보므로 인증은 필요 없다(401 도 닿은 것이다). Windows 에서도 뗀 주소를 쓴다.
67
+ - Windows PowerShell 에서는 같은 주소를 `curl.exe --ssl-no-revoke -sSI -o NUL --connect-timeout 5 --max-time 10 <주소>` 로 보고 `$LASTEXITCODE` 가 0 인지 본다(PowerShell 5.1 의 `curl` 은 `Invoke-WebRequest` 별칭이다). `curl.exe` 는 Windows 시스템 프록시를 따르지 않고 `HTTPS_PROXY` 만 따른다. `--ssl-no-revoke` 는 폐지 목록 서버에 닿지 못하는 망에서 Schannel 의 폐지 확인 실패를 오프라인으로 읽지 않게 한다. 닿는지만 보고 받은 내용은 쓰지 않으므로 이 확인에만 준다. `curl.exe` 가 없으면 `$LASTEXITCODE` 에 앞 명령의 값이 남으므로 `Get-Command curl.exe -ErrorAction SilentlyContinue` 로 먼저 보고, 없으면 확인하지 못한 것으로 적는다. `npm ping` 은 `Push-Location ~; npm ping --fetch-retries=0 --fetch-timeout=10000 --no-update-notifier; Pop-Location` 처럼 홈에서 부르고 돌아온다(돌아오지 않으면 뒤 단계가 프로젝트가 아니라 홈에서 돈다). PowerShell 에는 15초 감시를 두지 않는다(Windows 의 연결 시간 제한은 약 21초로 알려져 있다. 미실측).
68
+ - 사내 미러만 열린 망이면 확인 주소를 그 미러로 바꿔 본다.
69
+ - OS 패키지 관리자(dnf, apt, brew, winget)의 단계는 자기 설정의 미러를 쓰므로 이 확인과 관계없이 시도하고, 실패하면 그 단계를 실패로 보고한다.
70
+ - 패킷을 버리거나 답하지 않는 망에서 dnf 는 저장소마다 약 2분(SYN 을 버리는 망 실측 121초), apt 는 원천마다 약 4분(답하지 않는 원천 실측 247초) 기다린 뒤 실패로 끝난다. 시간 제한은 관리자 설정을 따르므로 이 스킬이 바꾸지 않는다.
71
+ - `apt-get update` 는 받지 못해도 종료 0 이다. 출력의 `W: Failed to fetch` 가 있으면 그 단계를 실패로 보고한다.
72
+ - Codex sandbox 가 네트워크를 막는 모드(기본 `workspace-write`, `read-only`)에서는 `CODEX_SANDBOX_NETWORK_DISABLED=1` 이 서고 확인이 모두 실패한다(curl 종료 7, npm `EPERM`). 블록의 첫 줄이 그렇다고 알린다. 그때는 '오프라인' 이 아니라 'sandbox 가 네트워크를 막음' 으로 보고하고, 승인을 받아 sandbox 밖에서 확인부터 다시 한다. Claude Code sandbox 안(`SANDBOX_RUNTIME=1`)에서는 허용 목록 밖 주소를 sandbox 프록시가 막는다. Windows PowerShell 에서는 `$env:CODEX_SANDBOX_NETWORK_DISABLED -eq '1'` 로 본다.
73
+ - 확인이 실패하면 보고에 그 명령, 종료 코드, stderr 첫 줄을 붙인다(예: `npm error code ETIMEDOUT`, `curl: (28) ...`). 원인을 '오프라인' 하나로 짐작하지 않는다.
74
+ - curl 의 종료 60(인증서 검증 실패)도 오프라인이 아니다. 망은 열려 있고, TLS 를 가로채는 사내 프록시의 인증서를 OS 가 믿지 않는 것이다. '인증서 문제로 설치하지 않음' 으로 보고한다.
75
+ - curl 은 닿는데 npm 만 인증서 오류(`UNABLE_TO_VERIFY_LEAF_SIGNATURE`, `SELF_SIGNED_CERT_IN_CHAIN`, `UNABLE_TO_GET_ISSUER_CERT_LOCALLY`)로 실패하면 망은 열려 있고 npm 이 사내 인증서를 믿지 않는 것이다(npm 은 OS 인증서 저장소를 쓰지 않는다). '인증서 문제로 설치하지 않음' 으로 보고하고 `NODE_EXTRA_CA_CERTS=<사내 CA 파일>` 을 안내한다. Node 24 는 `NODE_OPTIONS=--use-system-ca` 도 된다.
76
+
39
77
  ## 1. 빌더 설치 (둘 중 하나면 충분하다)
40
78
 
41
79
  **node 경로(권장).** `fontFace` 하나로 한글 3슬롯(`a:latin`·`a:ea`·`a:cs`)이 전부 채워져 한글 깨짐 위험이 구조적으로 낮다.
@@ -53,12 +91,12 @@ node -e "require('pptxgenjs')" && echo "pptxgenjs ok"
53
91
 
54
92
  ```bash
55
93
  PY=<위 스캔에서 고른 3.9+ 인터프리터>
56
- $PY -m pip install --user python-pptx
57
- $PY -c "import pptx; print('python-pptx', pptx.__version__)"
94
+ (cd ~ && $PY -m pip install --user python-pptx) # 홈에서: -c, -m, 표준 입력 스크립트는 지금 폴더의 모듈을 먼저 불러온다
95
+ (cd ~ && $PY -c "import pptx; print('python-pptx', pptx.__version__)")
58
96
  ```
59
97
 
60
98
  `fonttools` 는 필수가 아니다.
61
- 폭 예측 정확도를 직접 대조하고 싶을 때만 넣는다(`$PY -m pip install --user fonttools`).
99
+ 폭 예측 정확도를 직접 대조하고 싶을 때만 넣는다(`(cd ~ && $PY -m pip install --user fonttools)`).
62
100
 
63
101
  ## 2. LibreOffice 설치 — OS x 권한 분기
64
102
 
@@ -91,18 +129,19 @@ oosplash: /lib64/libc.so.6: version `GLIBC_2.33' not found
91
129
  배포판 빌드는 그 배포판의 glibc 에 맞춰져 있으므로 이게 정답이다. `dnf download` 는 root 가 필요 없다.
92
130
 
93
131
  ```bash
94
- SP=/tmp/lo-distro; PREFIX=$HOME/.local/lo-distro
95
- mkdir -p $SP $PREFIX
132
+ SP=$(mktemp -d); PREFIX=$HOME/.local/lo-distro # SP: 받은 RPM 을 둘 개인 임시 폴더(끝나면 지운다)
133
+ mkdir -p $PREFIX
96
134
  dnf -q -y --disablerepo='*' --enablerepo='appstream,baseos,powertools' \
97
135
  download --resolve --alldeps --destdir=$SP libreoffice-impress libreoffice-writer
98
136
  cd $PREFIX
99
- for r in $SP/*.rpm; do
137
+ find "$SP" -name '*.rpm' -type f | while read -r r; do # 글롭 없이(zsh 는 맞는 것이 없으면 멈춤)
100
138
  rpm2cpio "$r" | cpio -idmu --quiet 2>/dev/null
101
- chmod -R u+rwX $PREFIX/usr 2>/dev/null # ★ 이 줄이 없으면 중간부터 전부 실패한다
139
+ chmod -R u+rwX "$PREFIX/usr" 2>/dev/null # ★ 이 줄이 없으면 중간부터 전부 실패한다
102
140
  done
103
141
  export LD_LIBRARY_PATH=$PREFIX/usr/lib64:$PREFIX/usr/lib64/libreoffice/program:$LD_LIBRARY_PATH
104
142
  SOFFICE=$PREFIX/usr/lib64/libreoffice/program/soffice
105
143
  $SOFFICE --version # 6.4.7.2 처럼 배포판 버전이 찍히면 성공이다
144
+ cd ~; rm -rf "$SP"
106
145
  ```
107
146
 
108
147
  ★ **`LD_LIBRARY_PATH` 를 반드시 걸어라.** 추출본은 시스템 경로에 없으므로 링커가 자기 라이브러리를 못 찾는다.
@@ -118,18 +157,31 @@ $SOFFICE --version # 6.4.7.2 처럼 배포판 버전이 찍히면 성
118
157
  **glibc 2.29 이상: 업스트림 tarball 도 쓸 수 있다.** 자체 라이브러리를 번들해 배포판과 안 싸운다.
119
158
 
120
159
  ```bash
121
- V=$(curl -s https://download.documentfoundation.org/libreoffice/stable/ \
160
+ V=$(curl -fsS --connect-timeout 10 --max-time 30 https://download.documentfoundation.org/libreoffice/stable/ \
122
161
  | grep -oE '[0-9]+\.[0-9]+\.[0-9]+/' | sort -V | tail -1 | tr -d '/')
123
162
  A=$(uname -m) # x86_64 또는 aarch64. 업스트림이 둘 다 제공한다
124
- curl -sL -o /tmp/lo.tar.gz \
125
- "https://download.documentfoundation.org/libreoffice/stable/$V/rpm/$A/LibreOffice_${V}_Linux_${A/x86_64/x86-64}_rpm.tar.gz"
163
+ PREFIX=$HOME/.local/lo-upstream; T=$(mktemp -d) # T: 받은 tarball 과 푼 RPM. 끝나면 지운다
164
+ if [ -n "$V" ] && curl -fsSL --connect-timeout 10 --speed-limit 1000 --speed-time 30 -o "$T/lo.tar.gz" \
165
+ "https://download.documentfoundation.org/libreoffice/stable/$V/rpm/$A/LibreOffice_${V}_Linux_${A/x86_64/x86-64}_rpm.tar.gz" \
166
+ && tar -xzf "$T/lo.tar.gz" -C "$T" && mkdir -p "$PREFIX" && cd "$PREFIX"; then
167
+ find "$T" -path '*/RPMS/*.rpm' -type f | while read -r r; do # 글롭 없이(zsh 는 맞는 것이 없으면 멈춤)
168
+ rpm2cpio "$r" | cpio -idmu --quiet 2>/dev/null
169
+ chmod -R u+rwX "$PREFIX" 2>/dev/null # RPM 마다(위 배포판 경로와 같은 까닭)
170
+ done
171
+ cd ~; S=$(find "$PREFIX/opt" -path '*/program/soffice' -type f 2>/dev/null | head -1)
172
+ [ -n "$S" ] && echo "SOFFICE=$S" || echo "soffice 를 찾지 못함(tarball 의 꼴이 바뀌었을 수 있음)"
173
+ else
174
+ cd ~; echo "받지 못했거나 풀지 못함(판: ${V:-알 수 없음})"
175
+ fi
176
+ rm -rf "$T" # 성공과 실패 모두 지운다
126
177
  ```
127
178
 
128
- 이후는 위와 같은 추출 절차이고 `chmod` 주의도 동일하다.
179
+ 블록 하나로 받기, 풀기, 추출, 정리까지 끝낸다. 업스트림 RPM 은 `opt/libreoffice<판>/` 아래에 깔린다. 찾지 못하면 그렇다고 알린다(미실측: 내려받기 없이 꼴을 확인하지 못했다).
180
+ 판을 알 수 없으면(목록 페이지에 닿지 못함, 페이지 모양이 바뀜) 받지 않고 그렇게 보고한다.
129
181
  deb 계열은 `rpm/` 대신 `deb/` 를 받아 `dpkg-deb -x` 로 편다.
130
182
  `rpm2cpio` 와 `cpio` 가 없으면 이 경로는 못 쓴다. 그때는 root 를 얻거나 시각 검증을 건너뛴다.
131
183
 
132
- 다운로드가 250~320MB 다. 느린 회선에서는 백그라운드로 돌린다.
184
+ 다운로드가 250~320MB 다. 느린 회선에서는 백그라운드로 돌린다. 총 시간 제한 대신 정체 감지(`--speed-limit 1000 --speed-time 30`: 30초 동안 초당 1000바이트 미만이면 끊음)를 쓰므로 느려도 받는 중이면 끊지 않는다.
133
185
 
134
186
  ### 2-c. macOS
135
187
 
@@ -141,10 +193,13 @@ brew 가 없으면 업스트림 dmg 를 받는다. 아키텍처 이름이 `aarch
141
193
 
142
194
  ```bash
143
195
  V=<위와 동일하게 조회>
144
- curl -sL -o /tmp/lo.dmg "https://download.documentfoundation.org/libreoffice/stable/$V/mac/aarch64/LibreOffice_${V}_MacOS_aarch64.dmg"
145
- hdiutil attach /tmp/lo.dmg -nobrowse -quiet
146
- cp -R "/Volumes/LibreOffice/LibreOffice.app" /Applications/ # root 또는 쓰기 권한 필요
147
- hdiutil detach "/Volumes/LibreOffice" -quiet
196
+ T=$(mktemp -d)
197
+ if [ -n "$V" ] && curl -fsSL --connect-timeout 10 --speed-limit 1000 --speed-time 30 -o "$T/lo.dmg" "https://download.documentfoundation.org/libreoffice/stable/$V/mac/aarch64/LibreOffice_${V}_MacOS_aarch64.dmg"; then
198
+ hdiutil attach "$T/lo.dmg" -nobrowse -quiet
199
+ cp -R "/Volumes/LibreOffice/LibreOffice.app" /Applications/ # root 또는 쓰기 권한 필요
200
+ hdiutil detach "/Volumes/LibreOffice" -quiet
201
+ fi
202
+ rm -rf "$T"
148
203
  /Applications/LibreOffice.app/Contents/MacOS/soffice --version
149
204
  ```
150
205
 
@@ -171,7 +226,7 @@ $SOFFICE --headless --convert-to pdf --outdir <출력디렉터리> <입력.pptx>
171
226
  **렌더 결과의 페이지 크기를 반드시 재라.** 변환이 성공했다는 것과 A4 로 나왔다는 것은 다른 말이다.
172
227
 
173
228
  ```bash
174
- python -c "
229
+ (cd ~ && python -c "
175
230
  import re,zlib,sys
176
231
  d=open(sys.argv[1],'rb').read()
177
232
  m=re.search(rb'/MediaBox\s*\[\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)', d)
@@ -179,14 +234,15 @@ if m:
179
234
  w=float(m.group(3))-float(m.group(1)); h=float(m.group(4))-float(m.group(2))
180
235
  print('MediaBox %.1f x %.1f pt = %.1f x %.1f mm' % (w,h,w*25.4/72,h*25.4/72))
181
236
  print('A4 세로 판정:', abs(w*25.4/72-210)<1 and abs(h*25.4/72-297)<1)
182
- " <출력.pdf>
237
+ " <출력.pdf 의 절대 경로>)
183
238
  ```
184
239
 
185
240
  A4 세로는 **210 x 297 mm = 595 x 842 pt** 다.
186
241
  1mm 안쪽이면 통과로 본다(PDF 포인트 반올림 때문이다).
187
242
 
188
243
  `soffice` 가 이미 떠 있으면 두 번째 호출이 조용히 아무것도 안 하고 끝날 수 있다.
189
- 자동화에서는 프로파일을 분리한다: `-env:UserInstallation=file:///tmp/lo-profile-$$`.
244
+ 자동화에서는 프로파일을 분리한다: `P=$(mktemp -d)` 로 만든 개인 폴더를 `-env:UserInstallation=file://$P` 로 주고, 끝나면 `rm -rf "$P"` 로 지운다.
245
+ `/tmp/lo-profile-$$` 처럼 짐작할 수 있는 이름은 쓰지 않는다. 다른 사용자가 먼저 만들어 둔 프로파일(매크로 설정 포함)로 `soffice` 가 돌 수 있다.
190
246
 
191
247
  ## 4. 검증 (보고 의무)
192
248
 
@@ -195,7 +251,7 @@ A4 세로는 **210 x 297 mm = 595 x 842 pt** 다.
195
251
  ```bash
196
252
  node -v # 검증기 전제
197
253
  node -e "require('pptxgenjs')" && echo builder-node-ok
198
- $PY -c "import pptx" && echo builder-python-ok # 둘 중 하나면 충분
254
+ (cd ~ && $PY -c "import pptx") && echo builder-python-ok # 둘 중 하나면 충분
199
255
  $SOFFICE --version # 선택
200
256
  ```
201
257