æ10äžã®å€§åŠçãæãæã¡ã§æ60äžãŸã§ç©ã¿äžããäŒç€Ÿéœåã§è§£éãããŠ0ã«æ»ããå幎ã§Claude CodeèªåŸç°å¢ã建ãŠãŠä»ã¯æå120äžââãã®åå°ã«ãªã£ãã®ã¯ãå¯ãŠããéã«èšäºãçããŠãããä»çµã¿ã ããã ããã®ä»çµã¿ã5æ¥éã倱æããŠã誰ã«ãç¥ãããã«é»ã£ãŠèœã¡ç¶ããŠãããšãã話ãããã
ãªããã®ä»çµã¿ãå¹ãã®ã
èªååã®è©±ããããšããããŠãã¯ãä»çµã¿ããäœãã°æ¥œã«ãªãããšããçµè«ã§çµãããã ãç§ããã®å幎ã§ãã¡ã°ãåŠãã ã®ã¯ããã®éã ãä»çµã¿ã¯äœã£ãç¬éããè ãå§ãããããè ã£ãŠããããšãæããŠãããªãã
article-daily-stock.shïŒ~/.claude/scripts/article-daily-stock.shïŒã¯æ¯æ8æãš10æ35åã®2ã¹ããããlaunchdããèµ·åããŠèšäºã1æ¬çæã ~/content/article/ ã«ã¹ããã¯ããã¹ã¯ãªããã ãèšèšã®æ žå¿ã¯ã³ã¡ã³ãã«æžããŠããã
# èšèšã®è:
# - Zennãããã€(deploy-next)ãè©°ãŸã£ãŠããããã¯æ¢ãŸããªããçæã®æåŠã¯
# ãcontent/article ã«ã¹ããã¯ãæžããããã ãã§å€å®ããã
Zennãžã®pushãšã¹ããã¯çæãåãé¢ããŠããããããã€ãè©°ãŸã£ãŠãçæãããã¡ã¯ç©ã¿äžããããšããèšèšã ããã®ãçæãšå ¬éã®åé¢ããçµæãšããŠæ30æ¬ã®å®å®åºåãæ¯ããŠããââã¯ãã ã£ãã
åé¡ã¯ããã®ã¹ã¯ãªãããæ£åžžçµäºããŠããããšã§ã¯ãªãã倱æãããšãã®éç¥ãéé£ãã§æ»ãã§ããããšã ã
ãæã§å©ãããšãã ãæ»ã¬ããšããé転
ãœãããŠã§ã¢éçºã«ãããŠæãåä»ãªãã°ã®åã¯äœãããæ¬çªã ãèœã¡ãŠããŒã«ã«ã§ã¯åçŸããªããââ誰ããäžåºŠã¯èžã鬌ã ããããä»åééããã®ã¯ãã®å®å šãªè£é¢ã ã£ãã
launchdã¯LANGãèšå®ããªãã ã€ãŸããlaunchdé
äžã®ã¹ã¯ãªããã¯Cãã±ãŒã«ã§åããã¿ãŒããã«ãéããŠã¹ã¯ãªãããæã§å©ããšãã·ã§ã«ã¯LANG=ja_JP.UTF-8ãåŒãç¶ãããã®å·®ããéåžžãšã¯çéã®é害ãã¿ãŒã³ãçãã
- æ¯æ8æã®å®æå®è¡ïŒCãã±ãŒã«ïŒâ äœäºããªãéé
- æã§ãããã°ã®ããã«å©ãïŒ
ja_JP.UTF-8ïŒâ exit 127 ã§å³æ»
宿å®è¡ã¯æ¯æ¥éã£ãŠãããã ãã誰ãçããªããæã§å©ãããšãã ãæ»ã¬ãããå©ãæ¹ãééãããã§æµãããããããŠä»åã¯ââæ»ã¬å Žæã倱æéç¥ã®è¡ãã®ãã®ã ã£ãã
Threadsãžã®æçš¿ã倱æãããšããDiscordãžéç¥ãé£ã°ãã¯ãã®ã¹ã¯ãªããïŒscent-mediaã®daily_post.shïŒãéç¥è¡ã§ã¯ã©ãã·ã¥ãã倱æãããšããæ
å ±èªäœãæ¡ã朰ãããã5æ¥éãæçš¿ã¯æ¢ãŸã£ãŠãããç§ã®ã¹ããŒããã©ã³ã«ã¯äœãå±ããªãã£ãã
æçš¿ã倱æãã
â 倱æéç¥ã¹ã¯ãªãããèµ·å
â éç¥è¡ã§ã¯ã©ãã·ã¥ïŒexit 127ïŒ
â ã¢ã©ãŒããåºãªã
â 5æ¥éèª°ãæ°ã¥ããªã
éç¥ç³»ã®ãã°ã¯ã倱æãããšãã«ããèžãŸããªãããšããæ§è³ªäžãæåããŠããéã¯çµ¶å¯Ÿã«æ€åºã§ããªããæ¥æ¬èªã§ãã°ãéç¥ãæžãã¹ã¯ãªããââã€ãŸãç§ãæžãã»ãŒå šãŠã®èªååââã¯æ§é çã«ãã®å°é·ãèžãå¯èœæ§ãæã£ãŠããã
å šäœã®æµã
ãã°ã®åšãããçè§£ããããã«ããŸãarticle-daily-stock.shã®å
šäœåã远ããã¹ã¯ãªããã¯528è¡ããããåŠçã®éªšæ Œã¯12ã®ãã§ãŒãºã«åããããã
launchd (com.shun.article-daily)
8:00 JST ââââââââââââââââââââââââââââââââââââââ
10:35 JST (catch-up) âââââââââââââââââââââââââ€
â
âââââââââââââââââââââââââââââââ
â Phase 0: æ¬æ¥çææžã¿ãã§ã㯠â
â $DONE_MARKER ãååš â SKIP_GEN=1 â
ââââââââââââââââ¬âââââââââââââââ
â
âââââââââââââââââââââââââââââââ
â Phase 1: ç°å¢è£å® â
â PATH += nvm / homebrew â
â caffeinate -i -s â
â äºéå®è¡ãã㯠(lockdir) â
ââââââââââââââââ¬âââââââââââââââ
â
âââââââââââââââââââââââââââââââ
â audit_repair() â
â å
šã¹ããã¯ã®ç£æ» + èªå·±ä¿®åŸ© â
â ãµã ãæ¬ æ â èªååçæ â
â æ¬æç Žæ â QUEUEãžåæå
¥ â
ââââââââââââââââ¬âââââââââââââââ
â
SKIP_GEN=1? âââââ exit 0 (audit only)
â No
âââââââââââââââââââââââââââââââ
â Phase 2: äºç®ãã§ã㯠â
â token-budget-advisor.sh â
â ðŽ critical â exit 0 â
ââââââââââââââââ¬âââââââââââââââ
â
âââââââââââââââââââââââââââââââ
â Phase 3: ãã¥ãŒç©ºãªãèªåç«æ¡ â
â claude -p ã§ãã¿1æ¬ãçæ â
â slugéè€ãã§ã㯠â QUEUE远å â
ââââââââââââââââ¬âââââââââââââââ
â
âââââââââââââââââââââââââââââââ
â Phase 4-5: èšäºå·ç â
â QUEUEå
é ã®topicãååŸ â
â claude -p --max-turns 40 â
â å®ãã¡ã€ã«ãRead/GrepããŠåŒçš â
ââââââââââââââââ¬âââââââââââââââ
â
âââââââââââââââââââââââââââââââ
â Phase 6: 倿®µæ€èšŒ â
â frontmatter.title †70å â
â æ¬æ ⥠1200 bytes â
â çŠæ¢èª (ã¿ã€ã ã¢ãŠãç) ã¹ãã£ã³â
â ç§å¯ã¹ãã£ã³ + å®ãã¹æ£èŠå â
ââââââââââââââââ¬âââââââââââââââ
â
âââââââââââââââââââââââââââââââ
â Phase 7-8: ã¹ããã¯å â
â ~/content/article/articles/ â
â gen_note_thumbs.py â
â ~/content/article/thumbnailsâ
ââââââââââââââââ¬âââââââââââââââ
â
âââââââââââââââââââââââââââââââ
â Phase 9-12: åŸåŠç â
â coverage.json upsert â
â QUEUE pop â done queue â
â DONE_MARKER touch â
â git push (best-effort) â
âââââââââââââââââââââââââââââââ
ãã®ãã€ãã©ã€ã³ãæ¯æ2ã¹ãããèªèµ°ããããšã§ãç§ãããŒããŒãã«è§ŠããªããŠãèšäºã®ãããã¡ãç©ã¿äžãã£ãŠããã
ç°å¢è£å®ââlaunchdã®èãå®è¡ç°å¢
æåã®ãã§ãŒãºã§è¡ã£ãŠããåŠçã«æ³šç®ããŠã»ããã
# launchd æå°PATHè£å®ïŒnode/git/jq/claude/python3/chrome ãéãïŒ
NODE_BIN=$(ls -d "$HOME"/.nvm/versions/node/*/bin 2>/dev/null | sort -V | tail -1)
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
[ -n "$NODE_BIN" ] && export PATH="${NODE_BIN}:$PATH"
launchdã®PlistïŒcom.shun.article-dailyïŒã¯ã·ã§ã«ã®ãããã¡ã€ã«ãèªãŸãªãã.zshrcã.bash_profileãååšããªããåç¶ã®ç°å¢ã§èµ·åãããã ããnvmã®ãã¹ããHomebrewã®ãã¹ããæåã§è£å®ããªããã°ãªããªããåãçªã§çºèŠããscent-mediaã®daily_generate.shããclaude: No such file or directoryãã§å
šæ»
ããŠããã®ãåãåå ã ââ.local/binãPATHã«ç¡ãã£ãã
launchdé
äžã®ã¹ã¯ãªããã¯ç°å¢å€æ°ãåæã«ã§ããªãã ããã¯ãã±ãŒã«ã«ã€ããŠãåãã ãLANG=ja_JP.UTF-8ã¯ã¿ãŒããã«ãèšå®ãããlaunchdã¯èšå®ããªããã ããCãã±ãŒã«ã«ãªãããã®ãã¿ãŒããã«ãšlaunchdã§å®è¡ç°å¢ãéãããšããäºå®ããä»åã®ãã°ã5æ¥éçºèŠãããªãã£ãçŽæ¥ã®çç±ã ã
äºéå®è¡ããã¯ãšcaffeinate
# å®è¡äžã¹ãªãŒã鲿¢ïŒããããªåçµæã¯æ¬¡ã¹ããããæŸãïŒ
if [ -z "${CAFFEINATED:-}" ]; then
exec /usr/bin/caffeinate -i -s env CAFFEINATED=1 /bin/bash "$0" "$@"
fi
# äºéå®è¡ããã¯
LOCKDIR="$HOME/.claude/locks/article-daily.lock"
if ! /bin/mkdir "$LOCKDIR" 2>/dev/null; then
oldpid=$(cat "$LOCKDIR/pid" 2>/dev/null || true)
if [ -n "${oldpid:-}" ] && kill -0 "$oldpid" 2>/dev/null; then
log "å¥ã€ã³ã¹ã¿ã³ã¹å®è¡äž(pid=$oldpid) â skip"; exit 0
fi
rm -rf "$LOCKDIR"; /bin/mkdir "$LOCKDIR" 2>/dev/null || exit 0
fi
echo $$ > "$LOCKDIR/pid"
trap 'rm -rf "$LOCKDIR"' EXIT INT TERM
caffeinate -i -sã§MacBookã®ã¹ãªãŒããææ¢ããCAFFEINATEDç°å¢å€æ°ã§èªåèªèº«ãexecã§åèµ·åããããã©ãããå€å®ããã8æã¹ããããèšäºçæäžã«10æ35åã¹ããããèµ·åããŠããlockdirã®pidãaliveïŒkill -0ïŒã§ããã°å³exit 0ã§éããstaleãªããã¯ãæ®ã£ãå Žåã¯rm -rfããŠããåãçŽãââãã®å€å®ãmkdirã®ã¢ãããã¯æ§ã«ä¹ããããšã§ç«¶åãé²ãã§ããã
audit_repair()ââæ¯ã¹ãããå¿
ãèµ°ãèªå·±ä¿®åŸ©
èšäºçæãã¹ãããããæ¥ïŒSKIP_GEN=1ïŒã§ãaudit_repair()ã ãã¯å¿
ãèµ°ãã
# ç£æ»ïŒèªå·±ä¿®åŸ©ãå
ã«èµ°ãããïŒaudit / æ¬æ¥çææžã¿ã¯ ããã§å®çµïŒ
audit_repair
if [ "$MODE" = "audit" ] || [ "$SKIP_GEN" = "1" ]; then
log "===== article-daily done(audit$([ "$SKIP_GEN" = "1" ] && echo '+gen-skipped')) ====="
exit 0
fi
audit_repair()ã®äžã§ã¯ ~/content/article/articles/ ã®å
šãã¡ã€ã«ãèããŠãæ¬æã®å質ãã§ãã¯ïŒ1200 bytes以äžã»frontmatter titleååšã»çŠæ¢èªäžåšïŒãšãµã ãã®å¹
ãã§ãã¯ïŒsips -g pixelWidthã§2000px以äžïŒã宿œããããµã ããæ¬ æããŠããã°gen_note_thumbs.pyã§å³åçæããæ¬æãå£ããŠããã°done-queueããmetaãæãèµ·ãããŠtopic-queueã®å
é ã«åæå
¥ããââæå€§2åãŸã§ã
article_ok() {
local f="$1"
[ -s "$f" ] || return 1
[ "$(stat -f%z "$f" 2>/dev/null || echo 0)" -ge "$MIN_ARTICLE_BYTES" ] || return 1
grep -qE '^title:' "$f" || return 1
frontmatter_title_ok "$f" || return 1
grep -qiE 'request timed out|äžæãªåå|TODO: *æ¬æ|\(çæå€±æ\)' "$f" && return 1
return 0
}
ãã®article_ok()颿°ããã¹ã¿ãæ€åºãã®çªäººã ãClaude APIãã¿ã€ã ã¢ãŠããããšãã«åããrequest timed outããšããæååããplaceholderçãªãTODO: æ¬æããæç€ºçã«åŒŸããæ¯ã¹ãããèµ°ãããšã§ãæšæ¥ã®çæãå®ã¯å£ããŠããââãšããç¶æ
ãç¿æã«ã¯å¿
ãæ€ç¥ããã
ãã¥ãŒã空ãªãå®äœæ¥ãããã¿ãèªåç«æ¡ãã
ãã¿ãã¥ãŒïŒ~/zenn-articles/.topic-queue.jsonïŒã空ã«ãªããšãclaudeèªèº«ã次ã®ãã¿ãç«æ¡ããŠãã¥ãŒã«è¶³ãã
REPLENISH_PROMPT=$(cat <<EOF
ããªã㯠Lily ã®ãClaude Codeç°å¢ãæè¡ã·ãªãŒãºã®ç·šéè
ãæ¬¡ã«æžãèšäºãã¿ã1æ¬ã ãç«æ¡ããã
ãã¿ã¯ãèãå®éã«ãã£ãèªååã»ç°å¢æ§ç¯ã»Claude Codeéçšã®å·¥å€«ãããéžã¶ãæé çŠæ¢ïŒå¿
ãå®åšãããã¡ã€ã«ãã¹ã¯ãªãããæ ¹æ ã«ããã
...
EOF
)
TOPIC_JSON=$(run_to 600 "$CLAUDE" -p "$REPLENISH_PROMPT" \
--model "${ARTICLE_MODEL:-sonnet}" --effort high \
--allowedTools "Read,Grep,Glob,Bash" --max-turns 20 ...)
ã¢ãã«ã«ã¯Read,Grep,Glob,Bashã ããèš±å¯ããå®åšãããã¡ã€ã«ãæ ¹æ ã«ãã¿ãéžã°ãããåºåã¯å³æ ŒãªJSONã¹ããŒãïŒslugã»titleã»sourcesã»thumb_titleã®ååšãã§ãã¯ä»ãïŒã§åãåããslugã®éè€å€å®ãŸã§æ©æ¢°çã«è¡ããslugéè€ãªãå³exit 0ããŠæ¬¡ã¹ãããå詊è¡ââæé ãšéè€ãAPIã¬ãã«ã§å°ããèšèšã ã
èšäºå·çããå ¬éãŸã§
ãã¥ãŒããtopicãåãåºããããå¥ã®claude -pã»ãã·ã§ã³ã--max-turns 40ã§èµ·åããŠå®éã®èšäºãæžãããã
run_to 1500 "$CLAUDE" -p "$PROMPT" \
--model "${ARTICLE_MODEL:-sonnet}" --effort high \
--output-format text --allowedTools "Read,Grep,Glob,Write,Bash" --max-turns 40 >> "$LOG" 2>&1
ã¿ã€ã ã¢ãŠãã¯1500ç§ïŒ25åïŒã倱æããŠãlogã«æ®ãã ãã§exit 0ââarticle_ok()ã倱æãæ€ç¥ããŠæ¬¡ã¹ãããã§å詊è¡ããããããããã§ã¯æ¢ããªãã
çæåŸã®ãã€ãã©ã€ã³ãå¿µå ¥ãã ã
-
frontmatter_title_chars()ã§titleæåæ°ãååŸïŒPython3ã®inline heredocïŒâ 70åè¶ ãªãç Žæ£ -
article_ok()ã§æ¬æå質ãã§ã㯠â NGãªãç Žæ£ -
sedã§published: falseã匷å¶ã»ãã -
lily-footer.pyã§ããã¿ãŒæ³šå ¥ -
sed -E 's#/Users/[A-Za-z0-9._-]+/#~/#g'ã§å®ããŒã ãã¹ã~/ã«æ£èŠå - ç§å¯ã¹ãã£ã³ïŒAWSããŒæ£èŠè¡šçŸã
api_key =ãã¿ãŒã³çïŒâ æ€åºãããå³ç Žæ£
æåŸã«~/content/article/articles/$NO-$SLUG.mdã«ã¹ããã¯ããgen_note_thumbs.pyã§ãµã ããçæãcoverage.jsonã«upsertããŠããQUEUEãpopããŠdone-queueã«ç§»ããgitãžã®pushã¯best-effortââ倱æããŠããçææåãã¯æºãããªããDONE_MARKERã¯git pushããåã«touchããŠãããããpush倱æã§ç¿ã¹ããããåèµ·åããŠãéè€çæã¯èµ·ããªãã
ãã®ãã€ãã©ã€ã³ã¯èšèšã®æ®µéããã倱æãããã®ãšããŠãäœãããŠãããã¿ã€ã ã¢ãŠããçæå質äžè¶³ããµã ãç Žæããããã¯ãŒã¯æââããããã«èªå·±ä¿®åŸ©ã®çµè·¯ãçšæãããŠãããç£æ»ã¯æ¯ã¹ãããèµ°ããç§ãããŒããŒãããé¢ããŠããŠããäœããå£ããŠããã°ç¿æã«ã¯~/content/article/_NEEDS-FIX.txtãç«ã¡ãmacOSã®éç¥ãé£ãã§ããã
é£ãã§ããã¯ãã ã£ãã
å®è£ ã®è©³çް
"$VARïŒå
šè§ïŒ" ããã±ãŒã«ã§æ»ã¬çç±
ãŸãçŸè±¡ãæå
ã§ç¢ºèªããã~/Documents/claude-obsidian/wiki/learning/locale-dependent-shell-bugs.mdã«èšé²ãããŠããåçŸã³ãŒãããã®ãŸãŸäœ¿ããã
# æ»ã¬ïŒä¿®æ£åã®åœ¢ïŒ
LC_ALL=ja_JP.UTF-8 bash -c 'set -u; ID=test; echo "â
å®äº: $IDïŒèŠç¢ºèªïŒ"'
#=> bash: IDïŒèŠç¢ºèªïŒ: unbound variable (exit 127)
# éãïŒä¿®æ£åŸïŒ
LC_ALL=ja_JP.UTF-8 bash -c 'set -u; ID=test; echo "â
å®äº: ${ID}ïŒèŠç¢ºèªïŒ"'
#=> â
å®äº: testïŒèŠç¢ºèªïŒ (exit 0)
$IDïŒèŠç¢ºèªïŒãšæžããšãja_JP.UTF-8ãã±ãŒã«äžã®bashã¯å€æ°åã®çµç«¯ãå€å®ãããšãã«å
šè§æåã倿°åã®æ§æèŠçŽ ãšããŠèªãããšãããïŒã¯ASCIIã®(ã§ã¯ãªãU+FF08ïŒFULLWIDTH LEFT PARENTHESISïŒã ãset -uãæå¹ã§ããã°ãIDïŒèŠç¢ºèªïŒãšãã倿°ã¯ååšããªãããšããŠå³åº§ã«exit 127ã§æ»ã¬ã
Cãã±ãŒã«ïŒLC_ALL=CïŒã§ã¯å
šè§æåã倿°åã®äžéšãšããŠè§£éãããªãããã$IDã§å±éãæ¢ãŸã£ãŠæ£åžžã«åããlaunchdã¯LANGãèšå®ããªãã®ã§Cãã±ãŒã«ââã ãã宿å®è¡ã§ã¯éããæã§å©ããšæ»ã¬ãšããé転ãçãŸããã
å±éºãªæåã¯ïŒã ãã§ã¯ãªãããã»ãã»ãã»ïŒã»ã»ã»ïŒ
ã»âãªã©UTF-8ã®å
šè§æåå
šè¬ãåãçœ ã«ãªããããæ¥æ¬èªã§ãã°ãéç¥ãæžãèªååã¹ã¯ãªããã¯ãã¹ãŠãã®å°é·ãèžãå¯èœæ§ãããã ASCIIæåãç¶ãã¶ãã«ã¯åé¡ã«ãªããªãââã€ãŸãè±èªã ãã§æžããã¹ã¯ãªããã¯ãã®ãã°ã«ééããªããæ¥æ¬èªã§äžå¯§ã«æžãã°æžãã»ã©èžã確çãäžããããšããç®èãªæ§è³ªã ã
article-daily-stock.sh ã®é²åŸ¡ç·ââä¿®æ£åŸã®åœ¢
article-daily-stock.shïŒ~/.claude/scripts/article-daily-stock.shïŒã®äžãèŠããšãä»ã®ã³ãŒãã«ã¯ãã§ã«æ³¢æ¬åŒ§ãä»ããŠããã
# notify() ã®åŒã³åºãäŸïŒä¿®æ£åŸã®åœ¢ãã¹ã¯ãªããã®æ€èšŒãã§ãŒãºããïŒ
notify "titleé·ãã: ${SLUG}ïŒå詊è¡ïŒ"
notify "èšäºçæãäžå®å
š: ${SLUG}ïŒå詊è¡ïŒ"
ãããä¿®æ£åã¯$SLUGïŒå詊è¡ïŒãšæžãããŠãããïŒãå
šè§ãªã®ã§ãã¿ãŒããã«ããæã§èµ°ããããšSLUGïŒå詊è¡ïŒãšããæªå®çŸ©å€æ°ãšã©ãŒã«ãªããset -uo pipefailãããã°å³æ»ããã宿å®è¡ïŒCãã±ãŒã«ïŒã§ã¯æ£åžžã«éããããäœæ¥çµã£ãŠãçºèŠãããªãã
ä»åèŠéããã¡ã ã£ãã®ã¯éç¥è¡ã®å±éºæ§ã ãarticle_ok()ãaudit_repair()ã®ãããªæ¬äœã®åŠçã«ã¯æ°ã䜿ããã§ã倱æãããšãã«åŒã°ããnotifyã®åŒæ°ââã${SLUG}ïŒå詊è¡ïŒããšããæååââã¯ãæåæã«ã¯å°éããªãçµè·¯ã«ããããã¹ãã§æåçµè·¯ã ã確èªããŠãæ°žé ã«èžãŸããªãã
ããäžã€ãarticle-daily-stock.shã®Phase 1ã«ããclaudeãã€ããªã®æ€åºã³ãŒãã¯åãæèã®é²åŸ¡ç·ã ã
# claude 解決ïŒ3段ãã©ãŒã«ããã¯ïŒ
CLAUDE="${CLAUDE_BIN:-$(command -v claude 2>/dev/null)}"
[ -z "$CLAUDE" ] && [ -x "$HOME/.local/bin/claude" ] && CLAUDE="$HOME/.local/bin/claude"
[ -z "$CLAUDE" ] && CLAUDE=$(ls -t "$HOME"/.nvm/versions/node/*/bin/claude 2>/dev/null | head -1)
[ -x "$CLAUDE" ] || { log "ABORT: claude binary not found"; notify "claude binaryãç¡ã"; exit 0; }
command -v claudeâ~/.local/bin/claudeânvmã®bin以äžâããã§ãèŠã€ãããªããã°ABORTããšãã3段ãã©ãŒã«ããã¯ã«ãªã£ãŠãããlaunchdã®Plistã¯ã·ã§ã«ãããã¡ã€ã«ãèªãŸãªãããã~/.local/binãPATHã«ååšããªãããã®ããšãç¥ã£ãäžã§æžããªããã°ããclaude: command not foundãã§æ¯æãµã€ã¬ã³ãã«å
šæ»
ããã¹ã¯ãªãããåºæ¥äžããã
æ€åºã¯ rg 1è¡ã§æžã
ãã°ã®ãã¿ãŒã³ã¯æå¿«ã ãã$VARã®çŽåŸã«éASCIIæåãç¶ããŠããŠããã€ãŸã ${VAR}ã®åœ¢ã«ãªã£ãŠããªããã®ã ãå
šéšæŸãã°ããã
rg -n --no-heading -g '*.sh' -g '!node_modules' \
'\$[A-Za-z_][A-Za-z0-9_]*[^\x00-\x7F]' ~/dev ~/.claude/scripts ~/bin | rg -v '\$\{'
2段ãã€ãã«ãªã£ãŠããã®ã«ã¯çç±ãããã1段ç®ã®æ£èŠè¡šçŸ\$[A-Za-z_][A-Za-z0-9_]*[^\x00-\x7F]ã¯ã$ã§å§ãŸãè±æ°åã¢ã³ããŒã¹ã³ã¢ãç¶ãããã®çŽåŸã«éASCIIæåãæ¥ããã®ãããã¹ãŠãããããããããã¯$VARã${VAR}ãäž¡æ¹ãããããã2段ç®ã®rg -v '\$\{'ã§ããã§ã«æ³¢æ¬åŒ§ã§å²ãŸãã${VAR}圢ããé€å€ãããä¿®æ£æžã¿ã®ãã®ããã€ãºãšããŠæŸãããä¿®æ£ãå¿
èŠãªãã®ã ããæ®ãã
ä»åãããå®è¡ãããšãã5ãªããžããªã»8ãã¡ã€ã«ã»12ç®æãåºãŠãããäžäººã§ã¹ã¯ãªãããéç£ããŠãããšãçã¯ã³ããã§æšªå±éããããããã®ãªããžããªã«ã¯ããç¡ãããšããéå»ã®å€æã¯è ãã察象ãªã¹ãã¯ããŒãã³ãŒãããªãââæ€åºã¯åžžã«çŸåšã®ã³ãŒãã«å¯ŸããŠè¡ãã
çŽãæ¹ââæ³¢æ¬åŒ§ã1åè¶³ãã ã
# BeforeïŒå±éºãªåœ¢ïŒ
notify "èšäºçæãäžå®å
š: $SLUGïŒå詊è¡ïŒ"
echo "åŠçæžã¿: $IDïŒ${DATE}ïŒ"
# AfterïŒå®å
šãªåœ¢ïŒ
notify "èšäºçæãäžå®å
š: ${SLUG}ïŒå詊è¡ïŒ"
echo "åŠçæžã¿: ${ID}ïŒ${DATE}ïŒ"
å€ããã®ã¯$VARã${VAR}ã«ãããããã ãã ããã°ã¡ãã»ãŒãžã®æ¥æ¬èªæèšã¯1æåãå€ããªãã æ³¢æ¬åŒ§ãããã°ãbashã¯{}ã®å
åŽã ãã倿°åãšããŠè§£éããçŽåŸã®å
šè§æåã«æããããªããªãã
ãã ãæ³šæç¹ããããarticle-daily-stock.shã®äžã«ã¯Pythonã®ã€ã³ã©ã€ã³ã¹ã¯ãªãããè€æ°ãããããããã·ã³ã°ã«ã¯ã©ãŒãã®ãã¢ããã¥ã¡ã³ãã§æžãããŠããã
frontmatter_title_chars() {
python3 - "$1" <<'PY'
import sys
# ...ïŒPythonã³ãŒãïŒ
PY
}
<<'PY'ïŒã·ã³ã°ã«ã¯ã©ãŒãããïŒã®äžã¯ã·ã§ã«ãå±éããªãããããã®äžã®$ã¯Pythonãè§£éãããä¿®æ£å¯Ÿè±¡ã¯ãããŸã§ã·ã§ã«ãå±éããæèã«éãããââãã¢ããã¥ã¡ã³ãã®åŒçšç¬Šã確èªããŠããæãå
¥ããã
ä¿®æ£åŸã¯å¿
ãå®èšŒããŠããéãããæ§åœ¢ãja_JP.UTF-8ã§exit 127ã«ãªãããšã確èªããæ°åœ¢ãåãæèšãåºããŠexit 0ã«ãªãããšã確èªããããçŽããã€ãããã§çµããããªããå®éã«èµ°ãããçµæã蚌æ ã ã
ç§ãè©°ãŸã£ã話
çç¶â : 5æ¥éãDiscordã«äœãå±ããªãã£ã
æåã®ç°å€ã¯å¶ç¶æ°ã¥ããããæè¿Threadsã®ãšã³ã²ãŒãžã¡ã³ããèããªãââããæããŠããã·ã¥ããŒããéãããã5æ¥åããæçš¿ãæ¢ãŸã£ãŠããã
scent-mediaãããžã§ã¯ãã®daily_post.shïŒThreadsèªåæçš¿ã¹ã¯ãªããïŒã®ãã°ãéãããæçš¿å€±æã®ãšã©ãŒè¡ã¯ãããããããã®åŸãã«ããã¯ãã®ãDiscordéç¥éä¿¡ãã®ãã°è¡ããªããéç¥é¢æ°ãåŒã°ãã圢跡ãäžåãªãã£ãã
æåã¯Discordã®webhook URL倿Žãã¬ãŒããªããããçã£ãããããwebhookã¯æã§å©ããšæ®éã«åããæ¬¡ã«ã¹ã¯ãªããèªäœãã¿ãŒããã«ããçŽæ¥èµ°ãããç¬éããšã©ãŒãåºãã
bash: æçš¿IDïŒ2026-08-05ïŒ: unbound variable
$倿°åïŒã®ïŒãå
šè§ã ã£ããThreadsãžã®æçš¿ã倱æãããšããã¹ã¯ãªããã¯Discordãžéç¥ãéãããšããââããããã®éç¥ãçµã¿ç«ãŠãè¡èªäœãexit 127ã§æ»ã¬ãããã倱æããããšããæ
å ±ãã©ãã«ãå±ããªãã£ãã
æçš¿ã倱æãã
â 倱æéç¥ã®åŠçã«å
¥ã
â éç¥æååã®çµã¿ç«ãŠã§ $VARïŒå
šè§ïŒãåºçŸ â exit 127
â éç¥ãéä¿¡ãããªã
â ãã°ãžã®æžã蟌ã¿ãéç¥ããåŸãã«ãã£ãããæ®ããªã
â 5æ¥éèª°ãæ°ã¥ããªã
ãæã§å©ãããšãã ãæ»ã¬ããšããäºå®ããéã«5æ¥éã®é å»¶ãçãã ã 宿å®è¡ïŒCãã±ãŒã«ïŒã¯æ¯æ¥éã£ãŠããããçããªããæã§å©ããŠå€±æããããå©ãæ¹ã®åé¡ããšããŠæµããä»åããŸããŸããã·ã¥ããŒããéããŠæ°ã¥ããããæ°ã¥ããªããã°äœé±éã§ãç¶ããŠããå¯èœæ§ãããã
éç¥ã³ãŒãã®æãããã¯ããã«ãããæ¬äœã®åŠçã倱æãããšãã«ããéãçµè·¯ã«ãªããæåçµè·¯ãäœåºŠãã¹ãããŠããéç¥ã³ãŒãã®å質ã¯äžåä¿èšŒãããªããæåçµè·¯ã ãäœåºŠç¢ºèªããŠãã倱æçµè·¯ã®å質ã¯äžåä¿èšŒãããªãââããã¯locale-dependent-shell-bugs.mdã«ã[[silent-success-antipattern]]ãšããŠèšé²ããæèšã ã
çç¶â¡: daily_generate.sh ãå¥ã®åå ã§å
šæ»
ããŠãã
åãscent-mediaãããžã§ã¯ãã®å¥ã¹ã¯ãªãããdaily_generate.shïŒClaude APIã䜿ã£ãŠã³ã³ãã³ããçæããã»ãïŒã確èªãããããã¡ããåããŠããªãã£ãããã ãåå ã¯ãã±ãŒã«ã§ã¯ãªãã
~/.claude/scripts/daily_generate.sh: line 12: claude: command not found
claudeãã€ããªãèŠã€ãããªããlaunchdã®Plistã¯ã·ã§ã«ãããã¡ã€ã«ãèªãŸãªãããã~/.local/binãPATHã«ååšããªããarticle-daily-stock.shã§ã¯Phase 1ã§æç€ºçã«è§£æ±ºããŠãããã®åé¡ã ã
2ã€ã®ç°ãªããã°ãåãã¿ã€ãã³ã°ã§åããããžã§ã¯ãã§é¡åšåããçç±ã¯åãã ãlaunchdé äžã®ã¹ã¯ãªããã¯ãã¿ãŒããã«ãšå®è¡ç°å¢ãéãããšããäºå®ããæžããæ¬äººãå¿ããŠããã
ãã±ãŒã«å·®ãšPATHå·®ââã©ã¡ããæ ¹ã£ãã¯ãlaunchdã¯ã¿ãŒããã«ã®ã·ã§ã«ç°å¢ãåŒãç¶ããªãããšããäžç¹ã ããã®äºå®ãç¥èãšããŠæã£ãŠããŠããã¹ã¯ãªãããæžãç¬éã«ã¯å¿ããããªããªãæå ã§ãã¹ããããšããã¿ãŒããã«ããèµ°ããããšåããåããââã§ãã以äžè¿œããªããlaunchdããèµ°ããããšãã®ç°å¢ãæèçã«åçŸããªããã°ããã¹ãã¯æåçµè·¯ã®ç¢ºèªã«ãããªããªãã
è©°ãŸãã©ããâ : git管çå€ãšæã蟌ãã§é²ãã
暪æä¿®æ£ã~/.claude/scripts/ã«åœãŠããšãããã®é
äžã¯ãgitã§ç®¡çããŠããªãéå€ãªã¹ã¯ãªãã眮ãå Žãã ãšæã蟌ãã§ãããã ããä¿®æ£åŸã«ãcommitã¯äžèŠããšå€æããŠå
ã«é²ãã ã
ãšãããä¿®æ£åŸã«å¿µã®ããgit statusãæã£ãããchangeãåºãŠããã
On branch main
Changes not staged for commit:
modified: article-daily-stock.sh
modified: token-budget-advisor.sh
~/.claude/scripts/ã¯gitãªããžããªã ã£ããããã2ãã¡ã€ã«ãtracked fileãšããŠç®¡çãããŠããããgit管çå€ã®ã¯ãããšããæã蟌ã¿ã確èªããã«é²ããããšã§ãåŸããææ»ããçºçããã
æèšã¯åçŽã ãäœæ¥åã«git statusãæã€ã»ããæ©ãã åæãè³å
ã§å€å®ãããããã³ãã³ã1çºã§ç¢ºèªããã»ããéããŠç¢ºå®ã ã
ä¿®æ£åŸã¯git add article-daily-stock.sh token-budget-advisor.shã§2ãã¡ã€ã«ã ããã¹ããŒãžã³ã°ããä»åãšç¡é¢ä¿ãªå·®åãå·»ã蟌ãŸãªãããã«ããŠcommitããïŒ2b65662ïŒã5ãªããžããªã®commitããã·ã¥ã¯äºåŸæ€èšŒããŒãã«èšé²ããŠããââlily-line-funnelã6596c00ãautopilotã56288faãbrand-404ãf0d37f5ãmetrics-hubãf9b1a18ã ã
è©°ãŸãã©ããâ¡: 1ç®æã ãã³ãããã§ããªãã£ã
12ç®æãäžæãããšèšããªããã~/.claude/scripts/article-daily-stock.shã®äžã®1ç®æã ããã³ãããã§ããªãç¶æ
ã§æ®ã£ãã
ãã®ç®æã¯ãã¹ã¯ãªããã®ãŸã ã³ããããããŠããªã倿Žãããã¯ã®å éšã«ãã£ããæ°æ©èœãæžãããã§æ¢ãŸã£ãŠããæ°åè¡ã®æªã³ãããå·®åã®äžã«ãä»åã®ä¿®æ£å¯Ÿè±¡ãå«ãŸããŠããã®ã ã
# æªã³ãããå·®åã®å
éšïŒãããªåœ¢ã§ååšããŠããïŒ
# ... æ°æ©èœã®å®è£
éäž ...
log "åŠçã¹ããã: $SLUGïŒéè€ïŒ" # â ãããä¿®æ£å¯Ÿè±¡
# ... ç¶ãæªã³ãããè¡ ...
éžæè¢ã¯2ã€ã ã£ãââ(a)æªã³ããããããã¯ããšcommitããã(b)ãã®1è¡ã ãããŒã«ã«ã§ä¿®æ£ããŠã³ãããããªãââã ã(a)ã¯ãä»åãšç¡é¢ä¿ãªå·®åãå·»ã蟌ããããšã«ãªãcommitã®æå³ãææ§ã«ãªãã(b)ã¯ãworktreeã§ã¯å®å šãªåœ¢ã«ããç¶æ ã§ã³ãããããæ®ããããšã«ãªãã
ããªããžããªå
šäœcleanããšããå®äºæ¡ä»¶ã ããæªéãšæèšããŠ(b)ãéžãã ãããŒã«ã«ã®worktreeã§ã¯å
š12ç®æã${VAR}圢ã«ãªã£ãŠãããlaunchdããã®å®æå®è¡ïŒCãã±ãŒã«ïŒã§ãæåå®è¡ïŒja_JP.UTF-8ïŒã§ãæ»ãªãªãç¶æ
ã§éãããæ®ã1ç®æã¯ãæªã³ããããããã¯ãæŽçããŠcommitããã¿ã€ãã³ã°ã§äžç·ã«éããââããæèšããŠæ¬¡ã«é²ãããå®äºæ¡ä»¶ã®äžéšãæªéããé ããªãããšããåŸããèªåãå©ããã
ãã®ãã°ã远ããŸã§ããlaunchdãšã¿ãŒããã«ã§ãã±ãŒã«ãéãããšããäºå®ã¯ç¥ã£ãŠãããããããã ããéç¥è¡ãå®ããªããã°ãããªããã«ã¯ç¹ãã£ãŠããªãã£ããç¥èãšå®è£ ã¯å¥ç©ã ãã¹ã¯ãªãããæžããšããæåæã®åŠçã«ã¯æ°ã䜿ãã倱ææã®éç¥è¡ã¯ãã©ããåããŠãããšæã£ãŠæµãââãã®æã蟌ã¿ã5æ¥éã®ãµã€ã¬ã³ããªæ»ãæããã
éç¥è¡ãæ»ã¬ãšã倱æããããšããå±ããªãããå±ããªããã¯ãæåããããšåºå¥ã§ããªãã èªååã«ãããŠéç¥ã³ãŒãã¯æ¬äœã®ã³ãŒãããå åºã§ãªããã°ãªããªãããªããªãéç¥ã³ãŒããèœã¡ããšãæ¬äœãèœã¡ãããšããæããŠãããªãããã ã
article-daily-stock.shã®èšèšã«ãçæãšå
¬éã®åé¢ããããããã«ãéç¥ç³»ã«ããéç¥ãã®ãã®ã®å€±æãèŠéããªãããšããèšèšãèŠãããã®äžä»¶ã®åŸãéç¥é¢æ°ã®åŒã³åºãåã«ã¯å¿
ã${VAR}ã®åœ¢ã確èªããããšããçãã€ãããæ³¢æ¬åŒ§1åã®å·®ãã5æ¥éã®æ²é»ãåœæ¥ã®æ€ç¥ããåããã
ã€ãŸãããã€ã³ã
å®éã«èžãã å°é·ãšãèŠèœãšããã¡ãªçœ ãåæããŸããp1ã»p2ã§è§Šããçç¶ã®è£åŽã«ããããªãããã§è©°ãŸãã®ããã®éšåã§ãã
â ã·ã³ã°ã«ã¯ã©ãŒããã¢ããã¥ã¡ã³ããã察象å€ããšæèããŠããªãã£ã
article-daily-stock.shã«ã¯frontmatter_title_chars()ã®ããã«Pythonãã€ã³ã©ã€ã³ã§åŒã¶ç®æãè€æ°ãããŸãã
frontmatter_title_chars() {
python3 - "$1" <<'PY'
import sys
# Pythonã³ãŒãïŒ$ã¯Pythonãè§£éããïŒ
PY
}
<<'PY'ïŒã·ã³ã°ã«ã¯ã©ãŒãããïŒã®å
åŽã¯ã·ã§ã«ãå±éããŸãããæ€åºã³ãã³ããèµ°ããããš$ãå«ãŸããPythonã³ãŒãã«ããããããŸãããããã¯ä¿®æ£äžèŠã§ããæåã«ãããç¥ããããããæ³¢æ¬åŒ§ãè¶³ãã¹ããããšè¿·ããŸãããå€æåºæºã¯ãã·ã³ã°ã«ã¯ã©ãŒãã§éãããã¢ããã¥ã¡ã³ãã®äžãåŠããã®1ç¹ã§ããããã«ã¯ã©ãŒããåŒçšç¬Šãªãã®ãã¢ããã¥ã¡ã³ãïŒ<<PYïŒã¯å±éãããã®ã§å¯Ÿè±¡ã«ãªããŸãã
â¡ >/dev/null 2>&1 ãéç¥å€±æãé ã
ã¹ã¯ãªããã®notify()ã¯ä»¥äžã®ããã«å®çŸ©ãããŠããŸãã
notify() { /usr/bin/osascript -e "display notification \"$1\" with title \"Article daily\"" >/dev/null 2>&1; }
stdoutãšstderrãäž¡æ¹æšãŠãŠãããããosascriptã倱æããŠãäœãæ®ããŸãããéç¥åŒæ°ã«å
šè§æåã®å±éåé¡ãããå Žåããã®è¡èªäœãexit 127ã§æ»ãã§ã2>&1ã§æ¡ãã€ã¶ãããŸããä»åã®5æ¥éæ²é»ã®çŽæ¥åå ã¯ãŸãã«ããã§ãããä¿®æ£åŸã«æ°ã¥ããããšã§ãããéç¥å€±æã ãã¯$LOGã«æ®ã圢ã«ããŠããã¹ãã§ããã
⢠DONE_MARKERã®ã¿ã€ãã³ã°ãééãããšéè€çæãèµ·ãã
article-daily-stock.shã§ã¯DONE_MARKERã®touchãgit pushããåã«è¡ã£ãŠããŸãã
# çææåïŒãã®æç¹ã§åœæ¥doneã確å®ãã
touch "$DONE_MARKER"
# ---- 12. ZennãœãŒã¹ã pushïŒbest-effortïŒ---------
# ...git push...
touch "$DONE_MARKER" # pushã®åŸã«ã念ã®ãã
git pushã倱æããŠãDONE_MARKERãç«ã£ãŠããã®ã§10:35ã®catch-upã¹ãããã¯SKIP_GEN=1ã§auditã®ã¿ã§æããŸããDONE_MARKERãpushããåŸã«çœ®ããšãpush倱æâç¿ã¹ãããã§åãtopicãåçæããšããäºéçæãèµ·ããŸããèšèšæã«èŠèœãšãããããã€ã³ãã§ãã
⣠lockdirã®staleãèæ ®ããªããšèªçžèªçžã«ãªã
mkdirã®ã¢ãããã¯æ§ã䜿ã£ãããã¯æ©æ§ã®äžã«ãstaleå€å®ããããŸãã
if ! /bin/mkdir "$LOCKDIR" 2>/dev/null; then
oldpid=$(cat "$LOCKDIR/pid" 2>/dev/null || true)
if [ -n "${oldpid:-}" ] && kill -0 "$oldpid" 2>/dev/null; then
log "å¥ã€ã³ã¹ã¿ã³ã¹å®è¡äž(pid=$oldpid) â skip"; exit 0
fi
rm -rf "$LOCKDIR"; /bin/mkdir "$LOCKDIR" 2>/dev/null || exit 0
fi
kill -0ã§ããã»ã¹ãaliveãã確èªããããªããã°staleãšã¿ãªããŠrm -rfããŠããåãçŽããŸãããã®å€å®ãå
¥ããªãã£ãåæå®è£
ã§ã¯ãã¹ã¯ãªãããSIGKILLã§åŒ·å¶çµäºãããåŸïŒäºç®è¶
éã§ã¿ã€ã ã¢ãŠãããã±ãŒã¹ãªã©ïŒã«lockdirãæ®ãç¶ããç¿æ¥ããäºåºŠãšåããªããªããšããåé¡ãåºãŸããã
†set -uo pipefail ã®pipefailéšåãäºæ³å€ã®å Žæã§æ»ã¬
set -uo pipefailã®pipefailã¯ããã€ãã®éäžã§ã©ãã1ã€ãé0ãè¿ãããå
šäœãé0ã«ãªãããšããåäœã§ããjq ... | grep -q ...ã®ãããªçµã¿åããã§ãgrepããããããªããã§exit 1ãè¿ãæ£åžžã±ãŒã¹ã§ãã¹ã¯ãªãããæ»ã¬ããšããããŸãã
# å±éºãªåœ¢
jq -r '.[].slug' "$QUEUE" | grep -qx "$NEW_SLUG"
# å®å
šãªåœ¢
used_slugs | grep -qx "$NEW_SLUG"
# â used_slugs()å
ã§pipeã®ãšã©ãŒã || true ã§åžåæžã¿
article-daily-stock.shã®used_slugs()ããã®ãã¿ãŒã³ã«å¯ŸåŠããŠããŸããgrep -qã®ãèŠã€ãããªããã¯ãšã©ãŒã§ã¯ãªãæ£åžžç³»ãªã®ã«ãpipefailã ãšéãæå³ã«ãªããbashç¹æã®èœãšã穎ã§ãã
⥠claudeãã€ããªã®æ€åºãåŸåãã«ãã
æåã®å®è£
ã§ã¯claudeã³ãã³ããèŠã€ãããªãå Žåã®åŠçãåŸåãã«ããŠå
ã«é²ãã§ããŸããŸãããlaunchdããèµ·åããå Žåã¯~/.local/binãPATHã«ãªããããå®éã«ã¯æ¯æãclaude: command not foundãã§ãµã€ã¬ã³ãã«å
šæ»
ããŠããŸãããçŸåšã®3段ãã©ãŒã«ããã¯ã¯ãã®çµéšããçãŸããŠããŸãã
CLAUDE="${CLAUDE_BIN:-$(command -v claude 2>/dev/null)}"
[ -z "$CLAUDE" ] && [ -x "$HOME/.local/bin/claude" ] && CLAUDE="$HOME/.local/bin/claude"
[ -z "$CLAUDE" ] && CLAUDE=$(ls -t "$HOME"/.nvm/versions/node/*/bin/claude 2>/dev/null | head -1)
[ -x "$CLAUDE" ] || { log "ABORT: claude binary not found"; notify "claude binaryãç¡ã"; exit 0; }
ãã®æ€åºãå ¥ããåŸãåããŠãclaudeæªçºèŠãã®ABORTãã°ãåºãŠåé¡ãææ¡ã§ããŸããããåããŠããã¯ããã¯åããŠããªãã
⊠ALLOWED_OWNERãã§ãã¯ãå¿ããŠforkã«pushããã
git pushã®çŽåã«OWNERãã§ãã¯ãå
¥ããŠããŸãã
OWNER=$(printf '%s' "$URL" | sed -nE 's#.*github\.com[:/]+([^/]+)/.*#\1#p')
if [ "$OWNER" = "$ALLOWED_OWNER" ]; then
# pushãã
fi
ãããå
¥ããŠããªãã£ãææã«ãéçºäžã«originãå¥ãªããžããªã«åããŠãããŸãŸèªåå®è¡ãèµ°ããé¢ä¿ãªããªããžããªã«ã³ããããç©ãŸããŸãããALLOWED_OWNER=bokuwalilyãåºå®ããŠããããšã§ã誀æäœã§originãå€ããŠãææªã®äºæ
ãé²ããŸãã
â§ MIN_ARTICLE_BYTES=1200ãšããå€å®å€ã®æ ¹æ ãç¥ããã«çã£ã
article_ok()ã®æ¬æãã§ãã¯ã§stat -f%zã1200ãã€ãæªæºãªãç Žæ£ããŠããŸããæåããªã1200ãã€ããªã®ãããšçããå€ãå€ããããšããŸãããââããã¯ãã€ãæ°ã§ããUTF-8ã§ã¯æ¥æ¬èª1æåã3ãã€ããªã®ã§ã1200ãã€ãïŒçŽ400æåããŸãšããªæè¡èšäºã®åé ã ãã§ãè¶
ããã¯ãã®å€ãšããŠæ±ºããäžéã§ããstat -f%zã¯macOSåºæã®ãªãã·ã§ã³ïŒLinuxçã¯stat -c%sïŒãªã®ã§ãLinuxã«ç§»æ€ããå Žåã¯æžãæããå¿
èŠã§ãã
ãã¹ããã©ã¯ãã£ã¹
å®éã«ãã®ãã°ãèžã¿ã5ãªããžããªã»8ãã¡ã€ã«ã»12ç®æãäžæããçµéšããå°ããå®è·µçãªã«ãŒã«ã§ãã
1. 倿°å±éã®çŽåŸã«å
šè§æåã眮ããªãå¿
ã${VAR}圢åŒã«ãã
# NG
notify "çæå€±æ: $SLUGïŒå詊è¡ïŒ"
# OK
notify "çæå€±æ: ${SLUG}ïŒå詊è¡ïŒ"
æ³¢æ¬åŒ§1åã®å·®ã§ãããã°ã¡ãã»ãŒãžã®æ¥æ¬èªæèšã¯1æåãå€ããªããŠããã
2. æšªææ€åºã³ãã³ããæå ã«æã£ãŠãã
rg -n --no-heading -g '*.sh' -g '!node_modules' \
'\$[A-Za-z_][A-Za-z0-9_]*[^\x00-\x7F]' ~/dev ~/.claude/scripts ~/bin | rg -v '\$\{'
ã1ç®æçŽãããã§çµããããªããåãçã¯ã³ããã§æšªå±éããããä»åãåããã¿ãŒã³ã5ãªããžããªã«æ£ãã°ã£ãŠããŸãããæ€åºå¯Ÿè±¡ãªã¹ããããŒãã³ãŒãããªãââåžžã«çŸåšã®ã³ãŒãã«å¯ŸããŠèµ°ãããŸãã
3. launchdã®å®è¡ç°å¢ãã¿ãŒããã«ã§åçŸããŠãã¹ããã
# launchdãšåãç°å¢ãæå
ã§åçŸ
env -i HOME="$HOME" PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin" \
CAFFEINATED=1 /bin/bash ~/path/to/script.sh dry
env -iã§ã»ãŒç©ºã®ç°å¢ããèµ·åããããšã§ãlaunchdé
äžã§ã®æåãæå
ã§ç¢ºèªã§ããŸãããã¿ãŒããã«ã§éããã¯ãlaunchdã§éããã§ã¯ãªãã
4. launchd Plistã«LANGãæç€ºããŠç°å¢ãæãã
<key>EnvironmentVariables</key>
<dict>
<key>LANG</key>
<string>ja_JP.UTF-8</string>
</dict>
Plistã«LANGãæç€ºãããšã宿å®è¡ã§ãja_JP.UTF-8ã§åããŸãããã¿ãŒããã«ãšåãç°å¢ã§åããããšã§ããã±ãŒã«å·®ã«èµ·å ãããã°ãæå
ãã¹ãã§æ€åºã§ããããã«ãªããŸããä»åã¯ã³ãŒããä¿®æ£ããéžæãããŸããããå®è¡ç°å¢ãæããéžæè¢ããããŸãã
5. PATHè£å®ã¯set -uããåã«ãã¹ã¯ãªããã®æåã®ãã§ãŒãºã§è¡ã
NODE_BIN=$(ls -d "$HOME"/.nvm/versions/node/*/bin 2>/dev/null | sort -V | tail -1)
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
[ -n "$NODE_BIN" ] && export PATH="${NODE_BIN}:$PATH"
launchdã®Plistã¯ã·ã§ã«ãããã¡ã€ã«ãèªãŸãªããnvmã»Homebrewã»~/.local/binã䜿ãããŒã«ã¯å
šéšãèŠããŠããªããã®ããšããŠæ±ããæç€ºçã«è¶³ãå¿
èŠããããŸãã
6. claudeãã€ããªã¯3段ãã©ãŒã«ããã¯ã§è§£æ±ºããèŠã€ãããªããã°ABORTãã
command -vâ~/.local/binânvmã®bin以äžããšããé ã§æ¢ããã©ãã«ããªããã°exit 0ïŒæ¬¡ã¹ãããå詊è¡ïŒãexit 1ã«ãããšéç¥ãŸã§å¹ãé£ã¶ããšãããã®ã§exit 0ã«ããŠlogã ãæ®ãããšããèšèšã«ããŠããŸãã
7. éç¥ã³ãŒããæ¬äœããå åºã«æžã
éç¥ãèœã¡ããšã倱æããããšããå±ããªãââãå±ããªããã¯ãæåããããšåºå¥ã§ããŸãããarticle-daily-stock.shã§ãããé²ãããã«æ¡ã£ãèšèšã¯2ã€ã§ãã
- éç¥åŒæ°ã«å
šè§æåãå«ãŸããå Žåã¯å¿
ã
${VAR}åœ¢åŒ - éç¥ãããå
ã«
$LOGãžæžãïŒlogãžã®æžã蟌ã¿ãæ®ãã°éç¥å€±æã¯æ€ç¥å¯èœïŒ
log "ABORT: titleé·ãã(${TITLE_CHARS}å) â ç Žæ£"
notify "titleé·ãã: ${SLUG}ïŒå詊è¡ïŒ" # éç¥ã¯åŸ
8. set -uo pipefailã¯å
¥ãããã|| trueã®äœ¿ãæãç¥ã
pipefailã®ããã§ãgrepã§ãããããªãã£ãããšããæ£åžžç³»ãexit 1æ±ãã«ãªãããšããããŸãã|| trueã¯ãã®ãããªã倱æã§ã¯ãªãé0ããåžåããããã«äœ¿ããŸãããå
šéš|| trueãã¯è«å€ââset -uã®äŸ¡å€ãæ¶ããã䜿ãã®ã¯ãé0ãæ£åžžããšç¢ºå®ããŠããç®æã ãã§ãã
9. DONE_MARKERã¯git pushããåã«touchãã
touch "$DONE_MARKER" # â ããã§ããŒã«ãŒãç«ãŠã
# 以éã®pushã¯best-effort
git push ...
touch "$DONE_MARKER" # 念ã®ããäºétouchïŒåªçïŒ
pushã倱æããŠããçææåãã¯æºãããªãããšããèšèšã®èŠã§ããDONE_MARKERãç«ã£ãŠããã°æ¬¡ã¹ãããã¯çæãã¹ãããããã®ã§ãpush倱æâéè€çæã¯èµ·ããŸããã
10. audit_repair()ããçæãã¹ãããããæ¥ãã«ãèµ°ããã
audit_repair
if [ "$MODE" = "audit" ] || [ "$SKIP_GEN" = "1" ]; then
exit 0 # çæã¯ã¹ããããauditã¯æ¯æ¥èµ°ã
fi
æ¬æ¥ãã§ã«çææžã¿ã®æ¥ã§ããæšæ¥ä»¥åã®ã¹ããã¯ã®å質ã¯å£åãããããµã ãã®å¹
ãã§ãã¯ïŒsips -g pixelWidthã§2000px以äžç¢ºèªïŒã𿬿ã®ã¹ã¿ãæ€åºã¯æ¯ã¹ãããèµ°ãããåé¡ãããã°QUEUEã«åæå
¥ããŸãã
11. gitã§ç®¡çããŠãããã©ãããæã蟌ã¿ã§å€æããªã
git status # äœæ¥éå§åã«1çºæã€ã ã
ä»åã~/.claude/scripts/ããgit管çå€ã®éå€ãªå Žæããšããæã蟌ã¿ã§é²ããŠãåŸããgit statusãæã£ããmodifiedã2ä»¶åºãŠããŸãããæã蟌ã¿ã確èªãããããã³ãã³ã1çºã§äºå®ã確èªããæ¹ãæ©ãã§ãã
12. ä¿®æ£åŸã¯å¿ ãå®èšŒããŠããéãã
# æ§åœ¢ãexit 127ã§æ»ã¬ããšã確èª
LC_ALL=ja_JP.UTF-8 bash -c 'set -u; SLUG=test; echo "çæå€±æ: $SLUGïŒå詊è¡ïŒ"'
# => bash: SLUGïŒå詊è¡ïŒ: unbound variable
# æ°åœ¢ãåãæèšãæ£åžžã«åºãããšã確èª
LC_ALL=ja_JP.UTF-8 bash -c 'set -u; SLUG=test; echo "çæå€±æ: ${SLUG}ïŒå詊è¡ïŒ"'
# => çæå€±æ: testïŒå詊è¡ïŒ
ãçŽããã€ãããã§çµããããªããlocale-dependent-shell-bugs.mdã«ããã®å®èšŒã³ãã³ããæ®ããŠãããŸããå·®åãäžè¡ã§ããæ§åœ¢ãšæ°åœ¢ã®äž¡æ¹ã®æåãç®ã§èŠãŠéããç¿æ
£ãããçŽããã®ã«ãŸã èœã¡ããã®åŸåŸ©ãé²ããŸãã
13. ãã¿ç«æ¡ã«ãå®åšãã¡ã€ã«ã®æ ¹æ ãèŠæ±ãã
article-daily-stock.shã®ãã¿èªåç«æ¡ããã³ããã«ã¯ãæé çŠæ¢ïŒå¿
ãå®åšãããã¡ã€ã«ãã¹ã¯ãªãããæ ¹æ ã«ããããsourcesã«å®åšãã¹ã2ã4åæããããšããå¶çŽãå
¥ããåºåã®JSONã§ãããæ€èšŒããŠããŸããèªååèšäºããæ¶ç©ºã®å®è£
ãã«ãªããªãããã®èšèšã§ããèªåãå®éã«åãããŠããã³ãŒãããåãå£ãéžã¶ããšã§ãèšäºã®å¯åºŠãæ ¹æ¬çã«å€ãããŸãã
ãŸãšã
ãã®ãã°ã5æ¥éèŠã€ãããªãã£ãçç±ã¯ããæã§å©ãããšãã ãæ»ã¬ããšããéåžžã®é転ãã¿ãŒã³ãšãã倱ææã®ãã¹ã«ããååšããªããã°ããšãã2ã€ã®æ§è³ªãéãªã£ãããã§ãã
- 宿å®è¡ã¯Cãã±ãŒã«ã§æ¯æ¥éã â 誰ãçããªã
- æã§å©ããš
ja_JP.UTF-8ã§æ»ã¬ â ãå©ãæ¹ã®åé¡ãã§æµããã - æ»ã¬å Žæã倱æéç¥ã®è¡ â 倱æããããšèªäœãå±ããªã
$SLUGïŒå詊è¡ïŒãšãã倿°å±éã®çŽåŸã«å
šè§ïŒãæ¥ãæžãæ¹ã¯ãæ¥æ¬èªã§äžå¯§ã«ãã°ãæžãã°æžãã»ã©èžã確çãäžãããŸããè±èªã ãã§æžããã¹ã¯ãªããã¯ãã®åé¡ã«ééããŸãããç®èãªæ§è³ªã§ãã
ä¿®æ£èªäœã¯rg1è¡ã§12ç®æãæ€åºããæ³¢æ¬åŒ§ã1åè¶³ãã ãã§å®äºããŸããïŒ2b65662ã»6596c00ã»56288faã»f0d37f5ã»f9b1a18ã®5ã³ãããïŒããããä»åæ¬åœã«åŠãã ã®ã¯ãçŽãæ¹ãã§ã¯ãªããéç¥ã³ãŒãã¯æ¬äœã³ãŒãããå
åºã§ãªããã°ãªããªãããšããååã§ãã
éç¥ãèœã¡ããšãèœã¡ãããšèªäœãå±ããªãã
èªååã®ä»çµã¿ã¯äœã£ãç¬éããè
ãå§ãããããè
ã£ãŠããããšãæããŠãããªãââã ããaudit_repair()ãæ¯ã¹ãããèµ°ããDONE_MARKERãgit pushããå
ã«ç«ã¡ãarticle_ok()ããã¹ã¿ãæ€åºãã®çªäººãšããŠæ§ããŠããŸããæ30æ¬ã®èšäºãèªåçæããç¶ããåå°ã¯ã1200ãã€ãæªæºã匟ã1è¡ã®é¢æ°ãšãæ³¢æ¬åŒ§1åã®å·®ã§å®ãããŠããŸãã
ä»çµã¿ã¯ãæ£åžžã«åããŠããéãã¯éæã§ããå£ãããšãã«åããŠèŠããããã®ç¬éã«èŠãããã©ããããæå120äžã®åå°ãå®å®ãããã©ãããåããŠããŸãã
ä»çµã¿ã®å
šäœåã»æ120äžã®å
èš³ã»30æ¥æé ã¯æænoteã«ãŸãšããŠããŸãã
ð Claude CodeèªåŸç°å¢ã§ãå®éã©ã皌ãã â ä»çµã¿ã»å®äŸã»å§ãæ¹ã»ãµããŒã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- å¶äœç©ã»èšäºã¯ bokuwalily.com ã«ãŸãšããŠããŸãð¥ïž
- AIã§ãå¯ãŠãŠãåãä»çµã¿ããäœã£ãŠæ120äžã«ãã話㯠noteã®ææèšäº ã«ð°
- OSS: github.com/bokuwalily ð
- ææ°æ å ±ã»ãåãåãã㯠X @bokuwalily ãžð
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ