æ¯æ3æ¬ã®ã¢ãã£ãªèšäºãå®å šèªåã§å ¬éããä»çµã¿ ïŒå š2åã®ç¬¬1åïŒïŒåç·š â ä»çµã¿ã®å šäœåãšãæåã®3æ¥é1æ¬ãå ¬éã§ããªãã£ã話
æ10äžã®å€§åŠçãæãæã¡ã§æ60äžãŸã§çšŒããäŒç€Ÿéœåã®è§£éã§äžå€ã«ããŠ0ã«ãªããå幎ã§Claude Codeã®èªåŸç°å¢ãæ§ç¯ããŠä»ã¯æå120äžãè¶ ããŠããŸãããã®äžæ žã«ããã®ããæ¯æ3æ¬ã®ã¢ãã£ãªãšã€ãèšäºã人éãäœãè§Šããã«å ¬éãç¶ããä»çµã¿ã§ãã
ãªããã®ä»çµã¿ãå¹ãã®ã
ã¢ãã£ãªãšã€ãã§çšŒãç¶ããŠãã人ãšè±èœãã人ã®å·®ã¯ãæç« åã§ãååéžå®ã®å èŠã§ããããŸãããç¶ç¶ã§ãããã©ããã ãã§ãã
楜倩ã¢ãã£ãªãšã€ãã§åçãåºãããèšäºã«ã¯å ±éãã¿ãŒã³ããããŸããå䟡5äžå以äžã®å®¶é»ã»ã¬ãžã§ããã§ãã¬ãã¥ãŒæ°ãå€ããåšåº«ããã補åã®ã¹ããã¯æ¯èŒèšäºã§ããããããæé€æ©ãããŒã¿ãã«é»æºããã©ã åŒæŽæ¿¯æ©ãå šèªåã³ãŒããŒã¡ãŒã«ãŒãæ€çŽ¢æå³ããè²·ãåã«æ¯èŒãããããªã®ã§ååãªã³ã¯ã®ã¯ãªãã¯çãé«ããã¢ãã£ãªã®æ§é ãšçžæ§ãè¯ãã
åé¡ã¯ã³ã¹ãã§ããé«å䟡家é»ã®ã¹ããã¯ãWebã§èª¿ã¹ãæ¯èŒè¡šãäœãããæ£çŽã€ãã€ããªç¹ããŸã§æžããçšåºŠã®èšäºã1æ¬ä»äžãããš30ã40åããããŸãã3æ¬ãªã2æéè¿ããããã365æ¥ç¹°ãè¿ãæå¿åãæã€äººã¯ã»ãŒããŸãããç§ãããŸããã
ããã§å¿ èŠãªã®ã¯ãé 匵ããããšã§ã¯ãªããé 匵ããªããŠãåãç¶ããç°å¢ã§ãã
äžåºŠä»çµã¿ãäœãã°ã©ã³ãã³ã°ã³ã¹ãã¯APIã³ãŒã«ã ãã«ãªããŸããç§ãäœã£ã affiliate-factory ã¯ã·ã§ã«ã¹ã¯ãªãã4æ¬ããæãåçŽãªæ§é ã§ããmacOSã® launchdïŒcronã®åŸç¶ïŒãæ¯æã»æŒã»å€ã®3åèµ·åãã人éãäœãããªããŠãæ¯æ¥3æ¬ã®ã¢ãã£ãªãšã€ãèšäºãã¯ãŠãªããã°ã«å
¬éãããŸãã
ããã²ãšã€ãèšèšäžã®æ žå¿ããããŸããåªçæ§ã§ãã
åçŽãªã¹ã¯ãªããã¯ä»æ¥ãã§ã«äœæ¬å ¬éããããæ°ã«ããŸãããæã®ãããã倱æããã°ããã®æ¥ã¯0æ¬ã®ãŸãŸã§ãããã®ä»çµã¿ã¯ãŸãã仿¥ãã§ã«å ¬éã§ããæ¬æ°ããšãDesktopäžã®ãã©ããæ®æ°ããæ°ããç®æš3æ¬ã«å¯ŸããŠäžè¶³ããŠããæ¬æ°ã ããçæããŸãã
# daily.sh
PUB_TODAY=$(find "$ARCHIVE" -maxdepth 1 -name "${TODAY}_*.md" 2>/dev/null | wc -l | tr -d ' ')
DRAFTS=$(find "$OUT" -maxdepth 1 -name '*.md' 2>/dev/null | wc -l | tr -d ' ')
NEED=$((TARGET - PUB_TODAY - DRAFTS))
[ "$NEED" -lt 0 ] && NEED=0
ååäžã®ããããAPIå¶éã§å šæ» ããŠããæŒã®ããããã仿¥ããš3æ¬å¿ èŠããšèšç®ããŠè£å ããŸãã倿¹ã®ãããã1æ¬æ®ããåããŸããäœåºŠå®è¡ããŠãããã®æ¥ã®å ¬éæ°ã¯æçµçã«3æ¬ã«åæããããã®èšèšãçè§£ããŠããèªããšã4æ¬ã®ã¹ã¯ãªããã®åœ¹å²ããŸã£ããéã£ãŠèŠããŸãã
èªè ã®å€ãã¯ãæ¯æ¥èšäºãæžããªããã°ãããªãã®ãããã©ãããšããç¶æ³ã«ãããšæããŸããç§ãããã§ãããã§ãæ£ç¢ºã«èšããšãããã©ãã®ã¯ãæ¯æ¥èšäºãæžãæææ±ºå®ãããããšãã§ããä»çµã¿ã決å®ãè©ä»£ãããããšãã人éã¯ãã å ¬éæžã¿ã®èšäºãèŠãã ãã«ãªããŸãã
å šäœã®æµã
ã·ã¹ãã å šäœã®æ§é ãã¢ã¹ããŒå³ã§ç€ºããŸãã
[launchd] æ¯æã»æŒã»å€ã®3å
â
âŒ
[daily.sh] â åžä»€å¡ãåªçã«ã仿¥ããšäœæ¬å¿
èŠãããèšç®
â
ââ NEEDæ¬åã«ãŒã âââââââââââââââââââââââââââââââââââââââââââ
â â
â [generate.sh] â
â â claude -p + WebSearch ã§è£œåãéžã³èšäºãçæ â
â â timeout 600s / æå€§3ãªãã©ã€ â
â ââââ ~/Desktop/ã¢ãã£ãªèšäº/YYYY-MM-DD_HHMMSS.md âââ
â
ââ [post-to-hatena.sh --publish --all]
â â Desktop/*.md ã blogsync ã§ã¯ãŠãªããã°ãžå
šä»¶å
Ž
â ââââ published/ ãžã¢ãŒã«ã€ãïŒDesktopãã¥ãŒããé€å»ïŒ
â
ââ [audit-heal.sh]
â å£ãèšäºã®æé€ãã«ãã¬ããžè¡šã®åºåãç°åžžæã¯macOSéç¥
ââââ logs/audit-YYYY-MM-DD.log
daily.sh â åªçãªåžä»€å¡
daily.sh ã®åœ¹å²ã¯ã·ã³ãã«ã§ãã仿¥ã®æ®éãèšç®ããå¿
èŠãªåã ã generate.sh ãåŒã³ãå
¬éåŠçãšç£æ»ãé çªã«å®è¡ãããããã ãã§ãã
# daily.shïŒæç²ïŒ
TARGET=3
TODAY="$(date +%Y-%m-%d)"
PUB_TODAY=$(find "$ARCHIVE" -maxdepth 1 -name "${TODAY}_*.md" 2>/dev/null | wc -l | tr -d ' ')
DRAFTS=$(find "$OUT" -maxdepth 1 -name '*.md' 2>/dev/null | wc -l | tr -d ' ')
NEED=$((TARGET - PUB_TODAY - DRAFTS))
[ "$NEED" -lt 0 ] && NEED=0
if [ "$NEED" -gt 0 ]; then
for i in $(seq 1 "$NEED"); do
bash "$DIR/generate.sh" "$i" || echo "[daily] â çæ1æ¬å€±æïŒåŸç¶ã®åå®è¡ã§è£å
ãããŸãïŒã"
done
fi
bash "$DIR/post-to-hatena.sh" --publish --all
bash "$DIR/audit-heal.sh"
PUB_TODAY 㯠published/ é
äžã®æ¬æ¥ãã¬ãã£ãã¯ã¹ã®ãã¡ã€ã«æ°ãæ°ããŸããDRAFTS ã¯DesktopçŽäžã«æ®ã£ãŠãããã©ããæ°ã§ããNEED ã¯ãã®å·®åããã€ãã¹ã«ãªã£ãã0ã«ã¯ã©ã³ãããŸãã
éèŠãªã®ã¯ generate.sh ã1æ¬å€±æããŠãåŠçãæ¢ãŸããªãããšã§ãã|| echo ã§ãšã©ãŒã飲ã¿èŸŒã¿ãåŸç¶ã®ããããæ®ããè£å
ããŸããset -uo pipefail ãåé ã§å®£èšãã€ã€ãåå¥ã®çæå€±æã¯ã«ãŒãå
ã§åžåãããå
šäœã¯æ¢ãããã§ãå®è¡çµæã®è¿œè·¡ã¯å€±ããªãèšèšã§ãã
generate.sh â claude -p ã§èšäºãéç£ãã
generate.sh ããã®ã·ã¹ãã ã®å¿èéšã§ããWebSearchã䜿ããªãããæ¥œå€©ã§å£²ããŠããé«å䟡家é»ãèªåã§éžã³ãã¹ããã¯ã調ã¹ãèšäºãæžãããããClaudeãäžäººã§ãããŸãã
ããã³ããã®æ§é
ããã³ãã㯠generate.sh å
ã®ãã¢ããã¥ã¡ã³ãã§å®çŸ©ãããŠããŸãã以äžã®æé ãClaudeã«æç€ºããŸãã
- WebSearchã§å䟡5äžå以äžã®é«ã¬ãã¥ãŒå®¶é»/ã¬ãžã§ããã1ã€éžã¶ïŒããããæé€æ©ã»ããŒã¿ãã«é»æºã»ãã©ã åŒæŽæ¿¯æ©ã»ãããžã§ã¯ã¿ãŒãªã©ïŒ
- WebSearchã§ãã®ã¹ããã¯ãšå®å¢äŸ¡æ Œã確èªããïŒäžæé ç®ã¯ãå ¬ç§°å€ã»èŠç¢ºèªããšæžãããæé ãé²ãïŒ
- æå®ãã©ãŒãããã§èšäºãçæããïŒH1ã¿ã€ãã«ãå 責衚瀺ãç®æ¬¡ãçµè«âã¹ããã¯è¡šâè©äŸ¡ç¹â匱ç¹âæ¯èŒâQ&AâãŸãšãïŒ
ããã³ããåé ã® EXCL 倿°ã«ã¯æ¢åºååã®ãªã¹ããå
¥ããŸããposted-products.log ããèªã¿èŸŒãã§ããããã®è£œåã¯ä»åéžã°ãªãããšClaudeã«æž¡ãããšã§ãåã補åãç¹°ãè¿ãç»å ŽããŸããã
claudeã³ãã³ãã®åŒã³åºã
# generate.shïŒæç²ïŒ
GEN_TIMEOUT="${AFFILIATE_FACTORY_GEN_TIMEOUT:-600}"
for attempt in 1 2 3; do
RESP=$(timeout "$GEN_TIMEOUT" "$CLAUDE" -p "$PROMPT" \
--allowedTools WebSearch \
--model sonnet \
--permission-mode auto \
</dev/null 2>/dev/null)
if resp_is_valid "$RESP"; then break; fi
echo "[generate] çæå€±æ(詊è¡${attempt}/3)ãå詊è¡ããŸãâŠ" >&2
RESP=""
done
3ã€ã®ãã©ã°ã«ããããçç±ããããŸãã
</dev/null â launchdããèµ·åãããããã»ã¹ã«ã¯ttyããããŸãããããããªããš claude -p ãstdinãåŸ
ã¡ç¶ããŠæ¢ãŸããŸããã¿ãŒããã«ããæåå®è¡ãããšãã¯æ°ã¥ããªãçœ ã§ãlaunchdäžã ãã§åçŸããŸãã
--permission-mode auto â WebSearchã䜿ããšããéåžžã¯ClaudeããWebSearchã䜿ã£ãŠãããããšç¢ºèªãæ±ããŸããttyããªãç°å¢ã§ãããåºããšãããã³ãããè¿ã£ãŠããªããŸãŸãã³ã°ããŸããauto ãæå®ããããšã§ã--allowedTools ã§èš±å¯ããããŒã«ã¯ç¢ºèªãªãã«å®è¡ãããŸãã
timeout 600 â ãããæ¬é¡ã§ããWebSearchãè€æ°åå®è¡ããªããèšäºãçæãããããŒã¯ã宿ž¬ã§çŽ260ç§ããããŸããæåã¯300ç§ã«èšå®ããŠããŸããããããŠæåã®3æ¥éã1æ¬ãå
¬éã§ããŸããã§ããã
ãã®åå ãšæ ¹æ²»ã«ã€ããŠã¯åŸç·šã§è©³ããæžããŸãã
楜倩ã¢ãã£ãªãšã€ããªã³ã¯ã®çæ
generate.sh ã®ããã²ãšã€ã®éèŠãªä»äºããæ¥œå€©ã®ã¢ãã£ãªãšã€ããªã³ã¯ãæ£ããåã蟌ãããšã§ãã
Claudeãèšäºäžã«æžããæ¥œå€©ã§æ¢ãããªã³ã¯ã¯ããã®ãŸãŸã§ã¯ã¢ãã£ãªãšã€ãèšæž¬ãä¹ããŸããããã®ãã postprocess_body 颿°ãPythonã§æ¬æãåŸåŠçããClaudeãæžãããã¹ãŠã®æ¥œå€©ãªã³ã¯ãå·®ãæ¿ããŸãã
# generate.shïŒæ¥œå€©ãªã³ã¯çœ®æïŒ
# 2) claudeãæ¬æã«æžããå®ãªã³ã¯ [楜倩ã§ãâŠããæ¢ã](ä»»æURL) ããæ£ããã¢ãã£ãªãªã³ã¯ã«äžžããšå·®ãæ¿ããã
# ïŒããããããªããš claude ãæžããéã¢ãã£ãªã®æ€çŽ¢URLããã®ãŸãŸæ®ãïŒå ±é
¬ãŒãã«ãªãïŒ
rakuten_md_link_re = re.compile(r"\[楜倩ã§ã[^ã]*ããæ¢ã\]\([^)]*\)")
body = rakuten_md_link_re.sub(lambda m: link, body)
ãªã³ã¯èªäœã¯æ¥œå€©ã®Ichiba Item Search APIã§ååŸããŸãããã ãæ¥œå€©APIã«ã¯ãkeyword is not validããšã©ãŒãããã"Narwal Freo Z Ultra"ã®ãããªåååããã®ãŸãŸæž¡ããšåŒŸãããããšããããŸãããã®å¯Ÿçãšã㊠resolve 颿°ãæ«å°Ÿã®èªã1ã€ãã€åããªããå詊è¡ããŸãã
# generate.shïŒæ¥œå€©APIèªåãå詊è¡ããžãã¯ïŒ
def resolve():
words = product.split()
brand = words[0].lower() if words else ""
tried = set()
for n in range(len(words), 0, -1):
keyword = " ".join(words[:n]).strip()
if not keyword or keyword in tried:
continue
tried.add(keyword)
result = fetch(keyword)
if result:
if not brand or brand in (result["name"] + " " + result["url"]).lower():
return result["url"]
# ãã©ã³ãäžäžèŽ=å¥ååã«åãããæã¡åãã
return None
time.sleep(1.0)
return None
"Narwal Freo Z Ultra" â "Narwal Freo Z" â "Narwal Freo" ã®é ã§è©Šãããããããåååã«ãã©ã³ãåïŒ"narwal"ïŒãå«ãŸããããæ€èšŒããŸããå¥ã®ååã«åãããšå€æããããã¢ãã£ãªèšæž¬ä»ãæ€çŽ¢URLïŒhgcãªã³ã¯ïŒã«ãã©ãŒã«ããã¯ããŸããåå¥ååãªã³ã¯ãåããªããŠããå ±é ¬ã®ä¹ãæ€çŽ¢ãªã³ã¯ãå¿ ãå ¥ããèšèšã§ãã
post-to-hatena.sh â ããã°ãžèªåå ¬é
èšäºãã©ãããã¯ãŠãªããã°ã«æçš¿ããã®ã post-to-hatena.sh ã®åœ¹å²ã§ããããã¯ãšã³ãã«ã¯ blogsyncïŒGo補ã®ã¯ãŠãªããã°CLIïŒã䜿ã£ãŠããŸãã
# post-to-hatena.shïŒpost_one颿°ïŒ
post_one() {
local file="$1"
local title body
title="$(sed -n 's/^# //p' "$file" | head -1)"
[ -z "$title" ] && title="$(basename "$file" .md)"
body="$(awk 'NR==1 && /^# /{next} {print}' "$file")"
echo "[hatena] æçš¿: ${DRAFT_FLAG:-å
Ž} | $title"
printf '%s\n' "$body" | "$BLOGSYNC" post $DRAFT_FLAG --title "$title" "$BLOG"
}
Markdownã®1è¡ç®ïŒ# ã¿ã€ãã«ïŒããšã³ããªã®ã¿ã€ãã«ãšããŠåãåºããæ¬æããã¯H1ãé€ããŠæçš¿ããŸããH1ãæ¬æã«æ®ããšã¯ãŠãªåŽã§äºé衚瀺ã«ãªãããã§ãã
--publish --all ãã©ã°ã§å®è¡ãããšãDesktopäžã®ãã©ãããå
šä»¶äžçºå
¬éããå
¬éæžã¿ãã¡ã€ã«ã published/ ãã£ã¬ã¯ããªã«ç§»åããŠDesktopãã¥ãŒããé€å»ããŸããç¿æ¥ã®ããããã仿¥ã®ãã©ããæ®æ°ããæ°ãããšããæšæ¥åãæ··å
¥ããªãããã®èšèšã§ãã
æçš¿æžã¿ã®ãã¹ã¯ posted-hatena.log ã«èšé²ããã--all å®è¡æã®ã¹ãããå€å®ã«äœ¿ãããŸãããã°ã«èŒã£ãŠãããã¡ã€ã«ã¯æçš¿å¯Ÿè±¡ããå€ãããããåäžèšäºã®äºéæçš¿ãèµ·ããŸããã
ãŸãçæå€±æã®æ®éªžïŒæ¬æã«ãäžæãªååãããRequest timed outããå«ãŸãããã¡ã€ã«ïŒã¯æçš¿åã«æ€åºããŠã¹ãããããŸãã
if /usr/bin/grep -qE 'äžæãªåå|Request timed out' "$f"; then
echo "[hatena] ã¹ããã(çæå€±æã®æ®éªž): $f" >&2; continue
fi
Claudeã3å詊è¡ããŠããŸãšããªèšäºãäœããªãã£ãå Žåããã®å€±æãã¡ã€ã«ãæçš¿ããŠããŸããªãããã®å®å šåŒã§ãã
audit-heal.sh â èªå·±ä¿®åŸ©ãšå質ä¿èšŒ
æåŸã®ã¹ãããã audit-heal.sh ã§ããæ¯æ¥ã®å
¬éãçµãã£ãåŸã«èµ°ãã3ã€ã®ããšãããŸãã
1) å£ãèšäºã®æé€
post-to-hatena.sh ãã¹ãããããçæå€±æã®æ®éªžããã¥ãŒããåé€ããŸãã
for f in "$OUT"/*.md; do
[ -e "$f" ] || continue
if /usr/bin/grep -qE 'äžæãªåå|Request timed out' "$f"; then
log " [æé€] å£ãèšäºãåé€: $(basename "$f")"
rm -f "$f"
fi
done
2) ã«ãã¬ããžè¡šã®åºå
æ¬æ¥å ¬éããå šèšäºã«ã€ããŠãã¢ãã£ãªãªã³ã¯ãæ£ããå ¥ã£ãŠããããæ€èšŒããŸãã
for f in "$ARCHIVE/${TODAY}"_*.md; do
published=$((published+1))
title="$(sed -n 's/^# //p' "$f" | head -1 | cut -c1-30)"
if /usr/bin/grep -q 'hb.afl.rakuten.co.jp' "$f"; then
link="âã¢ãã£ãª"
else
link="âéã¢ãã£ãª"; bad_link=$((bad_link+1)); problems=$((problems+1))
fi
log " ${link} | ${title}"
done
hb.afl.rakuten.co.jp ãæ¬æã«å«ãŸããŠãããã§å€å®ããŸããå«ãŸããŠããªããã°ãâéã¢ãã£ãªããšããŠåé¡ã«ãŠã³ãã«å ç®ããŸããã¬ãã¥ãŒèšäºãã¢ãã£ãªãªã³ã¯ãªãã§å
¬éãããŠãèªè
ã«ã¯æ°ã¥ãããŸããããå ±é
¬ããŒãã«ãªããŸãããããèªååã®ç²ç¹ã®ã²ãšã€ã§ãã
3) macOSéç¥
ç®æšæ¬æ°ã«éããªãã£ãå ŽåããŸãã¯éã¢ãã£ãªãªã³ã¯ã®èšäºããã£ãå ŽåãmacOSã®éç¥ã»ã³ã¿ãŒã«ç°åžžãé£ã°ããŸãã
notify() {
command -v osascript >/dev/null 2>&1 && \
osascript -e "display notification \"$1\" with title \"ã¢ãã£ãªèªåæçš¿\"" >/dev/null 2>&1 || true
}
éç¥ãæ¥ãã logs/audit-YYYY-MM-DD.log ã確èªããŸããã©ã®èšäºãåé¡ã ã£ãããæ¬æ°ãäœæ¬ã ã£ããããã°ã«æ®ã£ãŠããŸãã
å šäœã®ç£æ»ãµããªã¯ä»¥äžã®ãã©ãŒãããã§åºåãããŸãã
==== ã¢ãã£ãªç£æ» 2026-06-22 07:15 ====
--- æ¬æ¥å
¬éåã®ã«ãã¬ããž ---
âã¢ãã£ãª | Roborock S8 MaxV Ultra ã¬ãã¥ãŒ...
âã¢ãã£ãª | Anker SOLIX C800 ããŒã¿ãã«é»æº...
âã¢ãã£ãª | Narwal Freo Z Ultra 宿©ã¹ããã¯...
--- ãµã㪠---
æ¬æ¥å
¬é: 3æ¬ / ç®æš: 3æ¬ ïœ æªå
¬éãã¥ãŒæ®: 0æ¬ ïœ éã¢ãã£ãª: 0æ¬
â
ç£æ»OK: 3æ¬ãã¹ãŠã¢ãã£ãªãªã³ã¯ä»ãã§å
Ž
ãããæ¯æ7æå°ã« logs/ ã«èç©ãããŠããããã®ãã°ãæºãŸãã»ã©ãä»çµã¿ãåããŠããããšãžã®ä¿¡é Œãç©ã¿äžãããŸãã
åŸç·šã§ã¯ãæåã®3æ¥éãã® audit-heal.sh ããå
¬é: 0æ¬ / ç®æš: 3æ¬ããèšé²ãç¶ããåå ãšã1è¡ã®æ°åãå€ããŠæ ¹æ²»ãã話ãå®ã³ãŒãã§æžããŸãã
å®è£ ã®è©³çް
resp_is_valid ãå®ã3ã€ã®æ¡ä»¶
generate.sh ã®äžæ žã«ãã resp_is_valid 颿°ã¯4è¡ã®çãå®è£
ã§ããããããæãããšå£ãèšäºãDesktopã«ç©ã¿äžãããŸãã
resp_is_valid() {
local r="$1"
[ -z "$r" ] && return 1
printf '%s' "$r" | grep -q '^PRODUCT:' || return 1
printf '%s' "$r" | grep -qiE 'request timed out|error:|rate limit|usage limit' && return 1
[ "$(printf '%s' "$r" | wc -c | tr -d ' ')" -lt 400 ] && return 1
return 0
}
æ¡ä»¶ã¯3ã€ã§ãã
â PRODUCT: è¡ãååšãã â åŸæ®µã§sedãåååãåãåºãããã®ç®å°ã§ããããã³ãããã1è¡ç®ã«å¿
ã PRODUCT: <ååå> ãšã ãæžãããšæç€ºããŠããŠããClaudeãå眮ããä»ããããAPIå¶éã§ã¡ãã»ãŒãžã ãè¿ã£ãŠããããšããããŸããPRODUCT: ãç¡ãå¿çãèšäºãšããŠæžãåºããšãåååã "äžæãªåå" ã®ãŸãŸ posted-products.log ã«è¿œèšãããç¿æ¥ã®éè€åé¿ãªã¹ããæ±æããŸãããäžæãªååã®ã¬ãã¥ãŒããšããã¿ã€ãã«ã®ã¯ãŠãªèšäºã¯æŽèœã«ãªããŸããã
â¡ãšã©ãŒæãå«ãŸããŠããªã â timeout ã§killãããClaude㯠"Request timed out" ãå«ãçãå¿çãè¿ãããšããããŸããAPIå¶éãªã "rate limit" "usage limit" ãå«ãŸããŸãããããèšäºãšããŠæžãåºããšãpost-to-hatena.sh ã grep -qE 'äžæãªåå|Request timed out' ã®ã¬ãŒãã§ã¹ãããããaudit-heal.sh ãæé€ãããŸã§Desktopãã¥ãŒã«æ®ããŸããã¬ãŒãã¯å€éã«ãªã£ãŠããŸãããresp_is_valid ã§åŒŸããŠããã®ãæ ¹æ¬å¯Ÿçã§ãã
â¢400æå以äžãã â WebSearchã空æ¯ãããŠã該åœè£œåãèŠã€ãããŸããã§ãããçãªæ°è¡ã ããè¿ãããšããããŸããèšäº1æ¬åã®Markdownã¯æäœã§ã1,500ã3,000æåããããã400æåæªæºã¯æç¢ºãªå€±æã§ãããã®éŸå€ã¯çµéšåã§ãçãããŠèšäºã«ãªããªãå¿çããã¹ãŠãã£ããã§ããŸãã
ãã®3æ¡ä»¶ãæºãããå Žåã ã break ããŠæ¬¡ã®ã¹ããããžé²ã¿ã1ã€ã§ãå€ããã RESP="" ã«ã㊠attempt ã«ãŠã³ã¿ãŒãé²ããŸãã3å詊è¡ããŠãã¹ãŠå€±æãããšããgenerate.sh ã¯å£ãèšäºãæžããã« exit 1 ããŸãã
PRODUCT: è¡ãšããå
¥åºåã®å¥çŽ
ããã³ããã®æéèŠæç€ºã¯ã1è¡ç®ã«å¿
ã PRODUCT: <æ£åŒååå> ãšã ãæžãããšããçžãã§ãããªãèšäºã®åé ã«ãã®è¡ãå¿
èŠãªã®ãã
PRODUCT=$(printf '%s\n' "$RESP" | sed -n 's/^PRODUCT: *//p' | head -1)
BODY=$(printf '%s\n' "$RESP" | awk 'p{print} /^PRODUCT:/{p=1}')
[ -z "$BODY" ] && BODY="$RESP"
[ -z "$PRODUCT" ] && PRODUCT="äžæãªåå"
PRODUCT: è¡ã¯ãClaudeã®å¿çãããåååããšãæ¬æãã確å®ã«åé¢ããããã®ã»ãã¬ãŒã¿ãŒã§ããã1è¡ç®ãH1ã¿ã€ãã«ã2è¡ç®ããæ¬æããšããæ§é ã§è¿ãããŠããå眮ãæãæ··å
¥ããããšããããŸããPRODUCT: ãšããæç€ºçãªããŒã«ãŒãèšããããšã§ãawk ã PRODUCT: è¡ã«åºäŒã£ããã©ã°ïŒp=1ïŒãç«ãŠããã以éã®è¡ã ããæ¬æãšããŠåãåºããŸãã
åãåºãã PRODUCT ã¯3ã€ã®ä»äºãããŸããæ¥œå€©APIãžã®ããŒã¯ãŒããšããŠæž¡ããposted-products.log ã«è¿œèšããŠç¿æ¥ã®éè€åé¿ãªã¹ãã«å ããããããŠã¢ãã£ãªãšã€ããªã³ã¯ãçæããããã®åååãšããŠäœ¿ãããã®1è¡ãããããäžæµåŠçã®èµ·ç¹ã«ãªã£ãŠããŸãã
postprocess_body ãè¡ã3çš®ã®çœ®æ
generate.sh ã®åŸåŠç颿° postprocess_body ã«ã¯30è¡åŒ·ã®Pythonãå
å
ãããŠããŸãããããæãããªãããŒãªç®æã§ãèŠãç®ããè€éãªããšãããŠããŸãã
ãŸãMarkdownã®æ§é ã匷å¶çã«æ£èŠåããŸãã
lines = body.splitlines()
title_idx = next((i for i, line in enumerate(lines) if line.startswith("# ")), None)
title = lines.pop(title_idx) if title_idx is not None else f"# {product} ã¬ãã¥ãŒ"
lines = [line for line in lines if line.strip() not in {disclaimer, contents}]
Claudeãæ¬æäžã®äœè¡ç®ã«H1ãæžããŠããpop ã§ãã£ããåãåºããŸããå
責æïŒ> â»æ¬èšäºã¯ã¢ãã£ãªãšã€ãâŠïŒãšç®æ¬¡ã¿ã°ïŒ[:contents]ïŒãäºéã«å
¥ã£ãŠããã°é€å»ããŸããæåŸã« out = [title, disclaimer, contents] ã§å
é 3è¡ã確å®ãããŸããã©ããªã¬ã€ã¢ãŠãã§æ¬æãè¿ã£ãŠããŠããåºåã®åé ã¯åžžã«ãH1ã¿ã€ãã«âå
責âç®æ¬¡ã¿ã°ãã«æããŸãã
次ã®3çš®ã®çœ®æãæ¬é¡ã§ãã
眮æâ ïŒãã¬ãŒã¹ãã«ããŒã®å·®ãæ¿ã
placeholder_re = re.compile(r"ïŒ?âŒ?楜倩ã§ã[^ã]+ããæ€çŽ¢ããŠãªã³ã¯(?:ãäœæããããã«è²Œã|ã貌ã)ïŒ?")
body = placeholder_re.sub(link, body)
Claudeããªã³ã¯ããåŸã§äººéãå
¥ããã¹ããã¬ãŒã¹ãã«ããŒããšããŠæžãããšããããŸããïŒâŒæ¥œå€©ã§ãRoborock S8ããæ€çŽ¢ããŠãªã³ã¯ã貌ãïŒ ãšãã圢åŒã§ãããããæŸã£ãŠæ£ããã¢ãã£ãªãªã³ã¯ã«å·®ãæ¿ããŸãã
眮æâ¡ïŒæ¥œå€©MDãªã³ã¯ã®å·®ãæ¿ãïŒæéèŠïŒ
rakuten_md_link_re = re.compile(r"\[楜倩ã§ã[^ã]*ããæ¢ã\]\([^)]*\)")
body = rakuten_md_link_re.sub(lambda m: link, body)
ããã³ããã§ã[楜倩ã§ã<ååå>ããæ¢ã](<URL>) ã®åœ¢åŒã§æžãããšæç€ºãããšãClaudeã¯ãã®åœ¢åŒã«åŸããŸãããURLã¯æ¥œå€©ã®éåžžæ€çŽ¢ããŒãžURLïŒhttps://search.rakuten.co.jp/search/mall/...ïŒã«ãªããŸãããã®ãŸãŸã§ã¯èšæž¬ãä¹ããªãéã¢ãã£ãªãªã³ã¯ã§ãããã®æ£èŠè¡šçŸãã[楜倩ã§ãâŠããæ¢ã]ãã§å§ãŸãMarkdownãªã³ã¯ããã¹ãŠæ£ããã¢ãã£ãªURLã«äžæžãããŸãããããæãéèŠãªçœ®æã§ãaudit-heal.sh ã® hb.afl.rakuten.co.jp ãã§ãã¯ãéãã®ã¯ãããæ£ããæ©èœããŠããããã§ãã
眮æâ¢ïŒæ¥œå€©ãã¡ã€ã³ãªã³ã¯ã®å¿µæŒãå·®ãæ¿ã
rakuten_any_re = re.compile(r"\[[^\]]+\]\((?:https?:)?//[^)]*rakuten\.co\.jp[^)]*\)")
body = rakuten_any_re.sub(lambda m: link, body)
æ¯èŒè¡šã®äžã®ãªã³ã¯ããŸãšãã»ã¯ã·ã§ã³ã§Claudeãå¥ã®åœ¢åŒã§æ¥œå€©URLãæžããå Žåã®æçµé²è¡ç·ã§ããrakuten.co.jp ãå«ãMarkdownãªã³ã¯ã¯ãã¹ãŠã¢ãã£ãªãªã³ã¯ã«çµ±äžããŸãã
2æ¬ã®ãã°ã§é²ãéè€ã®çœ
ãã®ã·ã¹ãã ã«ã¯éè€é²æ¢ãã°ã2æ¬ãããŸãã
posted-products.log â çæããåååã远èšãããã¡ã€ã«ã§ãã
EXCL=$(paste -sd 'ã' "$LOG" 2>/dev/null)
[ -z "$EXCL" ] && EXCL="ïŒãŸã ç¡ãïŒ"
paste -sd 'ã' ã§å
šè¡ãèªç¹åºåãã®1è¡ã«çµåããããã³ããã®é€å€ã»ã¯ã·ã§ã³ã«åã蟌ã¿ãŸãããRoborock S8 MaxV Ultraããå
¥ã£ãŠããã°ãClaudeã¯åã補åãç¿æ¥ãŸãéžã¶ããšã¯ãããŸãããæ°ã¶æè²¯ãŸãã°ãåãèšäºãåºããåé¡ã¯äºå®äžãªããªããŸãã
posted-hatena.log â ã¯ãŠãªãžã®æçš¿æžã¿ãã¡ã€ã«ãã¹ã远èšãããã¡ã€ã«ã§ãã
if /usr/bin/grep -qxF "$f" "$POSTED_LOG"; then continue; fi
-xïŒè¡å
šäœãããïŒãš -FïŒåºå®æååïŒãçµã¿åãããŠããŸããdaily.sh ã1æ¥3åèµ°ããpost-to-hatena.sh --all ãæ¯ååŒã°ããŸãããã®ãã°ããªããã°åäžãã¡ã€ã«ã3åæçš¿ãããŸãã-F ã¯ãã¡ã€ã«ãã¹äžã®ããããã¹ã©ãã·ã¥ãæ£èŠè¡šçŸãšããŠè§£éããªãããã®æå®ã§ãã
ç§ãè©°ãŸã£ã話
3æ¥éã§1æ¬ãå ¬éã§ããªãã£ãïŒtimeout 300 察 259
æåã®3æ¥éãaudit-heal.sh ã®ãã°ã¯ãããªã£ãŠããŸããã
==== ã¢ãã£ãªç£æ» 2026-06-03 07:15 ====
--- æ¬æ¥å
¬éåã®ã«ãã¬ããž ---
--- ãµã㪠---
æ¬æ¥å
¬é: 0æ¬ / ç®æš: 3æ¬ ïœ æªå
¬éãã¥ãŒæ®: 0æ¬ ïœ éã¢ãã£ãª: 0æ¬
â å
¬éãç®æšæªéïŒçæ or å
¬éã倱æããå¯èœæ§ïŒ
â ç£æ»NG: èŠç¢ºèª (1ä»¶)
3æ¥é£ç¶ã§0æ¬ãNEED ãèšç®ã㊠generate.sh ãåŒã³åºããŠããã¯ããªã®ã«ãDesktopã«ãã©ããã1ã€ãçæãããŠããŸããã
æåã¯ãããã³ãããæªãããšæããŸãããååã«ããŽãªãå¢ãããåºåãã©ãŒãããã®æç€ºã詳现ã«ããŸãããäœãå€ãããŸããã§ãããæ¬¡ã«ãããŒã«èš±å¯ã®åé¡ãããšçãã--allowedTools ã®èšå®ãèŠçŽããŸãããå€åãªãã
3æ¥ç®ã®å€ãdaily.sh ãã¿ãŒããã«ããçŽæ¥å®è¡ããŠã¿ãŸããã30ååŸãèšäºã1æ¬ã§ãããã£ãŠããŸãããlaunchdçµç±ã§ã®ã¿å€±æããŠããŸããã
launchdçµç±ãšæåå®è¡ã®å·®ã調ã¹ãŠãããšãgenerate.sh ã®äžã«ä¿®æ£åŸã®ã³ã¡ã³ããšããŠæ®ã£ãŠããèšè¿°ãç®ã«å
¥ããŸãã
# ãã«èšäºçæ(WebSearchè€æ°å蟌ã¿)ã¯å®æž¬ã§çŽ260sãããã300sã ãšlaunchdäžã§å
ãã«è¶
ããŠ
# timeoutã«killãããå
šè©Šè¡ã空å¿çâ0æ¬å
¬éã«ãªã£ãŠããã宿ž¬ã®2å匷ã確ä¿ããã
GEN_TIMEOUT="${AFFILIATE_FACTORY_GEN_TIMEOUT:-600}"
宿ž¬259ç§ â timeout 300ç§ãäœè£ã¯41ç§ãããããŸããã§ããã
WebSearchã3ã4ååŒã³ãªããèšäºãçæãããããŒã¯ãã¿ãŒããã«ã§ã®èšæž¬ã§çŽ259ç§ããããŸãã300ç§ãªãäœè£ãããããã«èŠããŸãããããlaunchdäžã®ããã»ã¹ã¯èµ·åãªãŒããŒãããããããã«å€§ãããæ7æå°ã®WebSearchã¬ã¹ãã³ã¹ãè¥å¹²é
ãæé垯ã«éãªããšã259ç§ã305ç§ã«ãªãããšããããtimeout ãkillãããš RESP="" ã®ãŸãŸæ¬¡ã®è©Šè¡ãžé²ã¿ã3åãã¹ãŠç©ºå¿çã§ exit 1ãããã3æ¥ç¶ããŠããŸããã
ä¿®æ£ã¯ GEN_TIMEOUT ã®å€ã 300 ãã 600 ã«å€ããã ãã§ããå€ããç¿æã®ãã°ã¯ãããªããŸããã
==== ã¢ãã£ãªç£æ» 2026-06-06 07:31 ====
âã¢ãã£ãª | Roborock S8 MaxV Ultra ã¬ãã¥ãŒ...
âã¢ãã£ãª | Anker SOLIX C800 ããŒã¿ãã«é»æº...
âã¢ãã£ãª | Narwal Freo Z Ultra 宿©ã¹ããã¯...
æ¬æ¥å
¬é: 3æ¬ / ç®æš: 3æ¬ ïœ æªå
¬éãã¥ãŒæ®: 0æ¬ ïœ éã¢ãã£ãª: 0æ¬
â
ç£æ»OK: 3æ¬ãã¹ãŠã¢ãã£ãªãªã³ã¯ä»ãã§å
Ž
æåã§åãããšã確èªããã ãã§ã¯äžååã§ãlaunchdçµç±ã§åãçµæã«ãªããŸã§ç¢ºèªããããšãèªååã®å®äºæ¡ä»¶ã§ãããã®æèšãã AFFILIATE_FACTORY_GEN_TIMEOUT ãç°å¢å€æ°ãšããŠå€åºãããåã³ã³ãã€ã«ãªãã«èª¿æŽã§ããèšèšã«ããŸããã
ãŸãããªã3æ¥éæ°ã¥ããªãã£ãããã®åçããããŸããgenerate.sh ã exit 1 ãè¿ãããšããdaily.sh 㯠|| echo ã§ãšã©ãŒãåžåããŠåŠçãç¶ããŸãã
bash "$DIR/generate.sh" "$i" || echo "[daily] â çæ1æ¬å€±æïŒåŸç¶ã®åå®è¡ã§è£å
ãããŸãïŒã"
ãã®èšèšã¯ãæã倱æããŠãæŒãè£å
ããããšããåªçæ§ã®ããã®æå³çãªéžæã§ããããããåæã«ããæ¯å倱æããŠããŠãåŠçãæ¢ãŸãããmacOSéç¥ãäžãããªãããšããç²ç¹ãçãã§ããŸãããaudit-heal.sh ãå
¬é0æ¬ã§éç¥ãé£ã°ãã®ã¯åŠçã®æåŸãªã®ã§ãlaunchdçµç±ã§ã®éç¥ãå±ããŠããŸããã§ããïŒåŸã§ç¢ºèªãããšéç¥ã»ã³ã¿ãŒã«æºãŸã£ãŠããŸããïŒã
楜倩APIã keyword is not valid ãè¿ãç¶ãã
ã·ã¹ãã ãå®å®çšŒåãå§ããŠ1é±éåŸãNarwal Freo Z Ultraã®ããããæé€æ©ã®èšäºãçæããããšãã[generate] åååå¥ãªã³ã¯ãååŸã§ããæ€çŽ¢ãªã³ã¯ã«ãã©ãŒã«ããã¯: Narwal Freo Z Ultra ãšãããã°ãåºç¶ããŸããã
楜倩Ichibaã®APIã« keyword: "Narwal Freo Z Ultra" ããã®ãŸãŸæž¡ããšãHTTPã¹ããŒã¿ã¹400ãè¿ããŸãããšã©ãŒããã£ã« keyword is not valid ãšããæååãå
¥ã£ãŠããŸãã
except HTTPError as exc:
body = exc.read().decode("utf-8", "replace")
if exc.code == 400 and "keyword is not valid" in body:
return None # â resolve()ã®èªåãã«ãŒããž
return None ãèªåãå詊è¡ã®ããªã¬ãŒã§ãã"Narwal Freo Z Ultra" ã® "Z" ãšãã1æåããŒã¯ã³ãåé¡ã§ããã"Narwal Freo Z" ã§ã匟ãããŸãã"Narwal Freo" ã§ããããéããŸãããããã§æ¬¡ã®åé¡ãèµ·ããŸãã
ãNarwal Freoãã§ã¬ãã¥ãŒæ°éé ã«æ€çŽ¢ãããšãçŸè¡ã®æäººæ°ã¢ãã«ããããããŸããããããNarwal Freo Ultraãã ã£ãå Žåããã©ã³ãåãnarwalããåååã«å«ãŸããŠãããããã©ã³ãã¬ãŒããééããŸããå ã®ã¿ãŒã²ããã¯"Narwal Freo Z Ultra"ãªã®ã«ãå¥ã¢ãã«ã®ãªã³ã¯ã匵ãããŠããŸãã
if not brand or brand in (result["name"] + " " + result["url"]).lower():
return result["url"]
# ãã©ã³ãäžäžèŽã®å€å®ïŒãã©ã³ãåã¯åã£ãŠãããåååãå¥ç©ã®å ŽåïŒ
print(f"[generate] åè£ããã©ã³ãäžäžèŽ({keyword}â{result['name'][:30]})ã", file=sys.stderr)
return None
çŸåšã®å®è£ ã¯ãã©ã³ãåïŒå é ã®åèªïŒã§ã®ãã£ã«ã¿ã«çãŸã£ãŠããŸãããnarwalããšããæååãçµæã«å«ãŸããŠãããã確èªãããããåããã©ã³ãã®å¥ã¢ãã«ã¯ããæããŸããhgcãã©ãŒã«ããã¯URLã¯ã¢ãã£ãªèšæž¬ãä¹ãããå ±é ¬ãŒãã«ã¯ãªããŸããããåå¥ååãªã³ã¯ã®ç²ŸåºŠã¯èª²é¡ã§ãã
ãã®åé¡ããåŸã仿§ã®ç¥èã1ã€ãããŸããæ¥œå€©APIã¯åäžæåã®ããŒã¯ã³ãå«ãããŒã¯ãŒãã keyword is not valid ã§åŒŸããŸããåçªã«å€ããZããSããXããiããªã©ãå
¥ãåååã¯å¿
ãåŒã£ããããŸããgenerate.sh ã®èªåãã«ãŒãã¯å¿
é ã§ãã
launchdäžã ãã§èµ·ããstdinãã³ã°
æåã® launchd èšå®ã§ã¯ãããããèµ°ãåºããŠããäœååŸ
ã£ãŠã generate.sh ãçµãããŸããã§ãããããã»ã¹ããªãŒã«ã¯ claudeããã»ã¹ãååšããŠããã®ã«ãäœãèµ·ããŠããªãã
ps aux | grep claude
# â /Users/xxx/.local/bin/claude -p "..." --allowedTools WebSearch --model sonnet
claudeããã»ã¹ã確ãã«ååšããŠããŸãããããåããŠããŸããã
ttyããªãlaunchdç°å¢ã§ãclaudeã³ãã³ããstdinããå
¥åãåŸ
ã£ãŠããã®ã§ããã¿ãŒããã«ã§ã¯ /dev/tty ãããŠãŒã¶ãŒã®å
¥åãåãããããããclaude -p ã¯ã察話å
¥åäžèŠããšå€æããŠåŠçãé²ããŸããlaunchdäžã§ã¯ /dev/tty ããªããstdinããäœããæ¥ããŸã§åŸ
ã€ã¢ãŒãã«å
¥ã£ãŠããŸããã
RESP=$(timeout "$GEN_TIMEOUT" "$CLAUDE" -p "$PROMPT" \
--allowedTools WebSearch \
--model sonnet \
--permission-mode auto \
</dev/null 2>/dev/null)
</dev/null ã§stdinãå³åº§ã«EOFã«ããããã®1è¡è¿œå ã§æ¢ãŸããªããªããŸããã
åæã« --permission-mode auto ãå¿
èŠã§ãããWebSearchã䜿ããšããClaudeããWebSearchã䜿ã£ãŠããã§ããããšç¢ºèªããã³ãããåºããŸããttyãããç°å¢ã§ã¯äººéããyesããšå
¥åã§ããŸããã</dev/null ã§stdinãéããŠããç¶æ
ã§ã¯ãã®ããã³ãããžã®å¿çãè¿ãããå¥ã®å Žæã§ãã³ã°ããŸããauto ãæå®ããããšã§ã--allowedTools ã§æç€ºããWebSearchã¯ç¢ºèªãªãã«å®è¡ãããŸãã
ãã®2ã€ã®ãã©ã°ã¯ã»ããã§æ©èœããŸãã </dev/null ã ãã§ã¯èš±å¯ããã³ããã§ãã³ã°ãã--permission-mode auto ã ãã§ã¯stdinã§ãã³ã°ããŸããã©ã¡ããçæ¹ã ãã§ã¯è§£æ±ºããŸããããã®2ã€ãæã£ãŠåããŠãlaunchdäžã§ã¿ãŒããã«ãšåãåäœã«ãªããŸãã
次åã¯ããã®ä»çµã¿ã皌åãç¶ããããã«å®è£ ããèªå·±ä¿®åŸ©canaryïŒæ£åžžç³»ãã¹ãã»æå³ç倱æãã¹ãïŒãšã3ã¶æåã®ãã°ããèªã¿åããå®éã®çšŒåçã»åçããŒã¿ãå ¬éããŸãã
ã€ãŸãããã€ã³ã
åç« ã§ã¯timeout 300ç§ã宿ž¬259ç§ã«è² ããä»¶ã»launchdäžã®stdinãã³ã°ã»æ¥œå€©API keyword is not valid ã®3倧é害ã解説ããŸããã以äžã¯ãã以å€ã§å®éã«èžãã 现ããçœ ã®äžèЧã§ããä»çµã¿ã®ããªããããªã£ãŠãããããèŠããŠããã®ã§ãèªåã§äœããšãã®èšèšåèã«ããªããŸãã
macOSã® wc -l ã¯å
é ã«ã¹ããŒã¹ãåºåãã
daily.sh ã®åªçèšç®ãå£ããåå ã®äžã€ã§ãã
PUB_TODAY=$(find "$ARCHIVE" -maxdepth 1 -name "${TODAY}_*.md" 2>/dev/null | wc -l | tr -d ' ')
macOSã® wc -l 㯠" 3" ã®ããã«å
é ã«ã¹ããŒã¹ä»ãã§åºåããŸããtr -d ' ' ããªããš NEED=$((TARGET - " 3" - DRAFTS)) ãšãªããbashã®ç®è¡å±éããšã©ãŒãè¿ããŸããGNU Linuxã® wc ã¯ã¹ããŒã¹ãªããªã®ã§æå
ã®Linuxç°å¢ã§ã¯äžåæ°ã¥ãããmacOSã®launchdç°å¢ã§åããŠçç¶ãåºãŸãã1è¡è¿œå ã§å®å
šã«è§£æ¶ããŸãã
shopt -s nullglob ããªããšglobããªãã©ã«æååãšããŠæ®ã
audit-heal.sh ã®æ¬æ¥å
¬éåã«ãŒãã§ãåœæ¥ãã¡ã€ã«ã1æ¬ããªãå ŽåïŒ
shopt -s nullglob
for f in "$ARCHIVE/${TODAY}"_*.md; do
published=$((published+1))
done
shopt -u nullglob
shopt -s nullglob ããªããšglobããããããªããšãããã¿ãŒã³æåå "$ARCHIVE/2026-06-06_*.md" ãã®ãã®ãã«ãŒãã«å
¥ããŸãã[ -e "$f" ] ã§ååšç¢ºèªã¯ã§ããŸãããpublished ã«ãŠã³ã¿ãŒãæå³ãã1å ç®ãããå®è£
ã«ãªãããŸãã䜿ãçµãã£ãã shopt -u nullglob ã§å¿
ãã¹ã³ãŒããæ»ããå
šäœã«å¹ããããšå¥ã®globãéãã«ç©ºã«ãªããŸãã
grep -qxF ã® -x ãš -F ã¯ã»ãã
if /usr/bin/grep -qxF "$f" "$POSTED_LOG"; then continue; fi
-xïŒè¡å
šäœãããïŒããªããš /Desktop/ã¢ãã£ãªèšäº/2026-06-06_071532.md ã 071532.md ã®éšåæååãšããŠèª€ãããããŸãã-FïŒåºå®æååïŒããªããšãã¹äžã® . ãæ£èŠè¡šçŸã®ãä»»ææåããšããŠè§£éãããŸããã©ã¡ããæ¬ ããŠã倧åã®ã±ãŒã¹ã§ã¯åãããåäœãã¹ãã§ã¯æ°ã¥ãã«ãããç¹å®ã®ãã¡ã€ã«åã®ãšãã ãäºéæçš¿ãèµ·ãã鿬 é害ã«ãªããŸãã
launchdã® PATH ã¯ã¿ãŒããã«ãšå¥ç©
CLAUDE="${CLAUDE:-~/.local/bin/claude}"
BLOGSYNC="${BLOGSYNC:-$HOME/.local/bin/blogsync}"
launchdèµ·åããã»ã¹ã® PATH 㯠/usr/bin:/bin:/usr/sbin:/sbin çšåºŠã§ããnvmé
äžïŒ~/.nvm/versions/node/vXX/bin/ïŒã ~/.local/bin/ ã¯å«ãŸããŸãããã¿ãŒããã«ã§ which claude ãéã£ãŠããŠããlaunchdçµç±ã§ã¯ command not found ã§çæãã§ãŒãºãå
šæ»
ããŸããããã©ã«ãå€ã絶察ãã¹ã§æžããç°å¢å·®ç°ã¯ .env ã EnvironmentVariables ã§äžæžãããèšèšãæã確å®ã§ãã
--publish ãå€ããšDesktopã«ãã©ãããç©ã¿äžãã£ãŠ NEED ãåžžã«0ã«ãªã
# post-to-hatena.sh
[ -z "$DRAFT_FLAG" ] && mv "$f" "$ARCHIVE/" && echo "[hatena] ã¢ãŒã«ã€ããžç§»å: $(basename "$f")"
ã¢ãŒã«ã€ããžã® mv 㯠--publish æã ãã§ãã--draftïŒããã©ã«ãïŒã§ã¯Desktopã«ãã¡ã€ã«ãæ®ãç¶ããŸããèšå®ãã¹ã§ --publish ãå€ããå ŽåãDRAFTS ã«ãŠã³ããç¿æ¥ä»¥éãç©ã¿äžãããNEED ãåžžã«0ãšèšç®ãããŸããæ¯æ¥ãçæ0æ¬å¿
èŠããšå€æãããŠäœãèµ·ãããaudit-heal.sh ã ãããç®æšæªéãã®éç¥ãé£ã°ãç¶ããŸããçç¶ãtimeout倱æãšåºå¥ãã€ããªãç¹ãåä»ã§ãã
minPrice: "30000" ããªããšäº€æããŒãããªã³ã¯å
ã«ãªã
"minPrice": "30000",
"NGKeyword": "äžå€ èš³ãã çŸå",
minPrice ãªãã§ãRoborock S8 MaxV Ultraããæ€çŽ¢ãããšã亀æçšãã¹ãããã¯ãå°çšã¯ãªãŒããŒæ¶²ãé«ã¬ãã¥ãŒé ã®äžäœã«ããããšããããŸãã3äžåã®äžéã§æ¶èåã»ã¢ã¯ã»ãµãªã®èª€ããããæå¶ããŠããŸãããå䟡5äžä»¥äžããšããããã³ããæç€ºãšã®å·®ã¯ã楜倩ã§ã®å®å£²äŸ¡æ Œãå®äŸ¡ããäžããã±ãŒã¹ãžã®äœè£ã§ããminPrice: "50000" ã«ãããšåšåº«åããæ§ã¢ãã«ã0ä»¶ãããã«ãªãããããhgcãã©ãŒã«ããã¯é »åºŠãäžãããŸãã
2>/dev/null ã§claudeã®ãšã©ãŒã¡ãã»ãŒãžãå®å
šã«æ¶ãã
RESP=$(timeout "$GEN_TIMEOUT" "$CLAUDE" -p "$PROMPT" ... 2>/dev/null)
launchdç°å¢ã§stderrã®ãã€ãºãæå¶ããããã«ä»ããŠããŸãããå¯äœçšãšããŠtimeout killã»èªèšŒãšã©ãŒã»ã¢ãã«ãšã©ãŒã®ã¡ãã»ãŒãžããã¹ãŠæšãŠãããŸãããããã°æã¯äžæçã« 2>/tmp/claude-err.log ã«å€ãããšå®äœã確èªã§ããŸããæ¬çªã«æ»ãéã¯å¿
ã 2>/dev/null ã«æ»ãããšãresp_is_valid ã§ã®å€å®ãå¯äžã®ãšã©ãŒæ€ç¥ææ®µã«ãªã£ãŠããããããšã©ãŒå
容ã¯å¿çæååã®äžèº«ã§å€æããŸãã
PRODUCT: ã»ãã¬ãŒã¿ããªããã¹ãå¿çã¯åååãæ±æãã
PRODUCT=$(printf '%s\n' "$RESP" | sed -n 's/^PRODUCT: *//p' | head -1)
[ -z "$PRODUCT" ] && PRODUCT="äžæãªåå"
[ "$PRODUCT" != "äžæãªåå" ] && echo "$PRODUCT" >> "$LOG"
AFFILIATE_FACTORY_TEST_RESPONSE ã§ãã¹ãå¿çãçŽæ¥æµãå ŽåãPRODUCT: è¡ãä»ãå¿ãããšåååã "äžæãªåå" ã«ãªããŸããposted-products.log ãžã®è¿œèšã¯ != "äžæãªåå" ãã§ãã¯ã§ã¹ããããããŸãããæ¥œå€©APIã« "äžæãªåå" ãšããããŒã¯ãŒããæž¡ã keyword is not valid â hgcãã©ãŒã«ããã¯ã®æµãã«ãªããŸããæå³äžæãªåååãæã€hgcãªã³ã¯ä»ãèšäºãDesktopã«æ®ããŸãããã¹ãçšã¬ã¹ãã³ã¹ã®1è¡ç®ã¯å¿
ã PRODUCT: ãã¹ãååå ã«ããŸãã
ãã¡ã€ã«åã®ç§ã¿ã€ã ã¹ã¿ã³ãããªããšé«éãã¹ãæã«è¡çªãã
FNAME="$OUT/$(date +%Y-%m-%d_%H%M%S).md"
æ¬çªã¯1èšäºãããçŽ260ç§ãããã®ã§ååäœã§è¡çªããŸããããAFFILIATE_FACTORY_TEST_RESPONSE ã䜿ããšNEED=3ã®å Žåã«3æ¬ãæ°ç§ã§çæãããŸãã%H%MïŒååäœïŒã ãšåäžåå
ã®2æ¬ç®ã1æ¬ç®ãäžæžãããŸãã%H%M%SïŒç§åäœïŒã«ããããšã§ãé«éãã¹ãã§ãè¡çªããŸããã
楜倩APIã®ãã©ã³ãã¬ãŒãã¯åãã©ã³ãå¥ã¢ãã«ãããæãã
if not brand or brand in (result["name"] + " " + result["url"]).lower():
return result["url"]
brand ã¯åååã®å
é 1èªïŒäŸ: "narwal"ïŒã§ãããNarwal Freo Z Ultraãã§èªãåã£ãŠãNarwal Freoãã§ããããããšããæãã¬ãã¥ãŒæ°ã®å€ããNarwal Freo Ultraããè¿ãããšããããŸãããnarwalããšããæååã¯ã©ã¡ãã®åååã«ãå«ãŸãããããã©ã³ãã¬ãŒããééããŸããå
ã®ã¿ãŒã²ãããšã¯å¥ã¢ãã«ã®ãªã³ã¯ãåã蟌ãŸããŸãããåãã©ã³ãã®ã¢ãã£ãªãªã³ã¯ãªã®ã§å ±é
¬ã¯ä¹ããŸããå®å
šã«é²ãã«ã¯ã¢ãã«çªå·äžèŽç¢ºèªãå¿
èŠã§ãçŸç¶ã¯hgcãã©ãŒã«ããã¯ãæçµå®å
šåŒãšããŠéçšããŠããŸãã
set -uo pipefail ãš || echo ã®äœ¿ãåããééãããšå
šäœãæ¢ãŸã
# daily.sh
set -uo pipefail
# ...
bash "$DIR/generate.sh" "$i" || echo "[daily] â çæ1æ¬å€±æïŒåŸç¶ã®åå®è¡ã§è£å
ãããŸãïŒã"
daily.sh åé ã® set -uo pipefail ã¯ã¹ã¯ãªããå
šäœãfail-fastã«ããŸããäžæ¹ã§ generate.sh ã®åŒã³åºã㯠|| echo ã§ãšã©ãŒãåžåããŠããŸãããã®äœ¿ãåããæå³çãªèšèšã§ããå
šäœã®ãããŒïŒå
¬éã»ç£æ»ïŒã¯æ¢ããããªãããåå¥ã®çæå€±æã¯èš±å®¹ãããã衚çŸããŠããŸãã|| echo ã || true ã«å€ãããšå€±æã¡ãã»ãŒãžãæ¶ãããã°ãèŠãŠãäœãèµ·ãããåãããªããªããŸãã
ãã¹ããã©ã¯ãã£ã¹
3ã¶æä»¥äžã®å®çšŒåã§èžçãããèšèšååã§ãã
1. timeoutã¯å®æž¬å€Ã2以äžã«èšå®ããç°å¢å€æ°ã§å€åºããã
GEN_TIMEOUT="${AFFILIATE_FACTORY_GEN_TIMEOUT:-600}"
宿ž¬259ç§ã«å¯ŸããŠ300ç§ã§ã¯ãlaunchdèµ·åãªãŒããŒããããšæã®å€éšAPIã¬ã¹ãã³ã¹é
å»¶ã®åç®ã§ç°¡åã«è¶
ããŸããã宿ž¬å€Ã2以äžããã€ç°å¢å€æ°åããéåã§ãã.env ã®1è¡å€æŽã ãã§å皌åã§ãããã³ãŒããè§ŠããªããŠããã
2. launchdå°çšã®2ç¹ã»ããã¯ã</dev/null ïŒ --permission-mode autoã
ã©ã¡ããçæ¹ã§ã¯æ©èœããŸããã</dev/null ã ãã§ã¯WebSearchèš±å¯ããã³ããã§ãã³ã°ããŸãã--permission-mode auto ã ãã§ã¯stdinã§ãã³ã°ããŸãã察話å
¥åã§ããªãç°å¢ã§CLIãåãããã¹ãŠã®å Žé¢ã«å¿çšã§ããŸãïŒgh, git commit, ãã®ä»å¯Ÿè©±ãæ±ããããŒã«å
šè¬ïŒã
3. åªç㪠NEED èšç®ãå ã«èšèšãã
NEED = TARGET - PUB_TODAY - DRAFTS
ãäœåå®è¡ããŠã1æ¥ã®å ¬éæ°ãåæããããšããæ§è³ªããªããã°ã1æ¥3åã®launchdãããã¯äºéå ¬éãŸãã¯éäžè¶³ã®æž©åºã«ãªããŸããåªçæ§ã¯èªååã®æ ¹å¹¹ã§ãã倱æãªã«ããªãŒããæ¬¡ã®ããããèªåã§æ®ããåããããšããäžæã§å®çµããŸãã
4. å£ãèšäºã¯3段ã§é²åŸ¡ãã
1段ç®ïŒresp_is_validïŒçæçŽåŸïŒâ 2段ç®ïŒgrep -qE 'äžæãªåå|Request timed out'ïŒæçš¿çŽåïŒâ 3段ç®ïŒaudit-heal.sh æé€ïŒãããæåŸïŒãåæ®µã§å¥ã«ãŒãããå
¥ã蟌ãã å£ãèšäºãæãŸããŸãã1段ç®ã ãã ãšããã¹ãå¿ççµç±ããããã¯ãŒã¯ç°åžžã«ããçãå¿çãéãæããŸããå€éé²è¡ã¯ããããã«èŠããŸãããæ¬çªã«ãRequest timed outã®ããããæé€æ©ã¬ãã¥ãŒããå
¬éããçµéšãããã°è¿·ãããªããªããŸãã
5. ã¢ãã£ãªãªã³ã¯çœ®æã¯3çš®ã§ç¶²çŸ ãã
# 眮æâ ãã¬ãŒã¹ãã«ããŒïŒâŒæ¥œå€©ã§ãâŠããæ€çŽ¢ããŠãªã³ã¯ã貌ãïŒ
# 眮æâ¡ MDãªã³ã¯ [楜倩ã§ãâŠããæ¢ã](ä»»æURL)
# 眮æâ¢ 楜倩ãã¡ã€ã³å
šè¬ [ä»»æããã¹ã](rakuten.co.jp/...)
Claudeã®ã¢ãŠããããããæå®ãã圢åŒéãã«æ¥ãããšããåæã¯å¿
ãè£åãããŸããã©ã®åœ¢åŒã§æ¥ãŠã hb.afl.rakuten.co.jp ã«çµ±äžãã3段ã®çœ®æãçšæããããšã§ãaudit-heal.sh ã®æçµãã§ãã¯ã確å®ã«éãããšãã§ããŸãã
6. macOS wc -l 㯠| tr -d ' ' ã§å¿
ããµãã¿ã€ãºãã
ç®è¡å±éã絡ãç®æã®ãã¹ãŠã«ä»ããç¿æ £ã«ãããå°æ¥Linuxã«ç§»æ€ãããšãå·®åã1ç®æã§æžã¿ãŸããã³ã¹ãã¯ãŒãã§ãã
7. 楜倩APIã¯ãèªåãåè©Šè¡ â ãã©ã³ãã¬ãŒã â hgcãã©ãŒã«ããã¯ãã®3段
åååå¥ãªã³ã¯ãåããªããŠãå ±é
¬ã諊ããªãããšãéèŠã§ããhgcãã©ãŒã«ããã¯URLã¯ã¢ãã£ãªèšæž¬ä»ãæ€çŽ¢ããŒãžãªã³ã¯ã§ãåå¥ååããŒãžããæçŽçã¯èœã¡ãŸãããaudit-heal.sh ã® hb.afl.rakuten.co.jp ãã§ãã¯ãééããå ±é
¬ãŒããé²ããŸãããååãªã³ã¯ãåããªããªãèšäºãæšãŠãããããå¿
ãäœããã®ã¢ãã£ãªãªã³ã¯ãå
¥ããŠå
¬éãããèšèšã®ã»ãã皌åçãé«ããªããŸãã
8. shopt -s nullglob ã¯ã¹ã³ãŒããæå°åããŠäœ¿ã
globãã¿ãŒã³ã䜿ãforã«ãŒãã®ååŸã ããå²ããã¹ã¯ãªããå
šäœã«é©çšãããšãä»ã®ç®æã§æå³ããglobãéãã«ç©ºé
åã«ãªããŸããshopt -u nullglob ãšã»ããã§äœ¿ãããšã§ãå¯äœçšã®å°çšãæå°åã§ããŸãã
9. 2æ¬ã®ãã°ã¯åœ¹å²ã§çµ¶å¯Ÿã«åãã
posted-products.log ã¯åååïŒpaste -sd 'ã' ã§ããã³ããã«åã蟌ãïŒãposted-hatena.log ã¯ãã¡ã€ã«ãã¹ïŒgrep -qxF ã§å®å
šäžèŽæ€çŽ¢ïŒã圢åŒãéããã1æ¬ã«ãŸãšãããšå¿
ãã©ã¡ããã®çšéãå£ããŸãããã·ã³ãã«ã«ãããããšããèªæã«ä¹ããªãããšã
10. --model sonnet ãæç€ºããŠã³ã¹ããåºå®ãã
"$CLAUDE" -p "$PROMPT" --allowedTools WebSearch --model sonnet ...
çç¥ãããšClaude Codeã®ããã©ã«ãã¢ãã«ã䜿ãããŸããããã©ã«ããå€ãã£ãã¿ã€ãã³ã°ãèšå®ãã¡ã€ã«ã®å€æŽã§æå³ããé«ã³ã¹ããªã¢ãã«ã«åãæ¿ãããŸãã1æ¬ãããã®ã³ã¹ããåºå®ããŠãããšãææ¬¡ã§ãçææ¬æ° à 1æ¬ã³ã¹ããã§è«æ±ãäºæž¬ã§ããŸãã
11. ãã¹ãç°å¢å€æ°ã§APIã³ãŒã«ãªãã«åäœç¢ºèªãã
if [ -n "${AFFILIATE_FACTORY_TEST_RESPONSE:-}" ]; then
RESP="$AFFILIATE_FACTORY_TEST_RESPONSE"
fi
postprocess_body ã®æå確èªã楜倩ãªã³ã¯çœ®æã®ãããã°ã«ãæ¯å260ç§åŸ
ã¡ãšAPIã³ã¹ãã¯äžèŠã§ããAFFILIATE_FACTORY_TEST_RESPONSE="PRODUCT: ãã¹ãåå\n# ã¿ã€ãã«..." ãç°å¢å€æ°ã«å
¥ããã°ãclaudeåŒã³åºããã¹ãããããŠåŸæ®µåŠçãå³æç¢ºèªã§ããŸãããã®äžå·¥å€«ã§éçºãµã€ã¯ã«ã倧å¹
ã«ççž®ãããŸãã
12. bainaryã®PATHã¯çµ¶å¯Ÿãã¹ïŒç°å¢å€æ°ã§ãªãŒããŒã©ã€ãå¯èœã«ãã
CLAUDE="${CLAUDE:-~/.local/bin/claude}"
ããã©ã«ãã¯çµ¶å¯Ÿãã¹ã§æžãã€ã€ãç°å¢å€æ°ã§äžæžãå¯èœã«ãããMacãå£ããŠclaudeãåã€ã³ã¹ããŒã«ããå Žåã .env ã®1è¡å€æŽã§å¯Ÿå¿ã§ããŸããã¹ã¯ãªããå
ã«ãã¹ãããŒãã³ãŒãããã ãã§ã¯å°æ¥ã®èªåãå°ãããŸãã
ãŸãšã
ãæ¯æ3æ¬ã®ã¢ãã£ãªèšäºãå®å šèªåã§å ¬éããä»çµã¿ãã¯ãçµå±ã®ãšãã4æ¬ã®ã·ã§ã«ã¹ã¯ãªããã«å šå€æãç§»ããèšèšã§ãã
äœãæžããïŒååéžå®ïŒâ generate.sh + Claude
ã©ã調ã¹ããïŒã¹ããã¯ïŒâ WebSearch
ã©ãã«æçš¿ããã â post-to-hatena.sh + blogsync
ã¡ãããšåããŠããã â audit-heal.sh + macOSéç¥
æåã®3æ¥éã§1æ¬ãå ¬éã§ããªãã£ãåå ã¯ãtimeout 300ç§ vs 宿ž¬259ç§ã®å·®41ç§ãã§ãããããã¯launchdã®èµ·åãªãŒããŒããããšæã®WebSearchã¬ã¹ãã³ã¹é å»¶ãšãããæåå®è¡ã§ã¯çµ¶å¯Ÿã«æ°ã¥ããªãèŠå ã®åç®ã§ãããæå ã§åãã = èªååå®äºãã¯èª€ãã§ãlaunchdçµç±ã§åãçµæãåŸããããŸã§ãèªååã®å®äºæ¡ä»¶ã§ãã
ä»å玹ä»ããã€ãŸãããã€ã³ãã®å€§åã¯ãæå ã§åãã®ã«launchdäžã§ã ãå£ããããšããå ±éãã¿ãŒã³ãæã¡ãŸãã察çã®å ±éç¹ã¯ãç°å¢å·®ç°ãåæã«æžãïŒçµ¶å¯Ÿãã¹ã»stdinéãã»ã¹ããŒã¹ãµãã¿ã€ãºã»nullglobïŒãã§ãã
åªçæ§ã»å€éé²è¡ã»ç£æ»ãã°ããã®3ã€ã®çµã¿åãããã誰ãè§ŠããªããŠãæ¯æ¥3æ¬ãç©ã¿äžããç¶ããä»çµã¿ã®æ¬äœã§ãã
ä»çµã¿ã®å šäœåã»æ120äžã®å èš³ã»30æ¥æé ã¯æænoteã«ãŸãšããŠããŸã â ð Claude CodeèªåŸç°å¢ã§ãå®éã©ã皌ãã â ä»çµã¿ã»å®äŸã»å§ãæ¹ã»ãµããŒã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- äœã£ãã¢ããªã¯ ããŒããã©ãªãª ã«ãŸãšããŠããŸãð±
- æ°çã»éçºã®è£åŽã¯ X @bokuwalily ã§çºä¿¡ããŠããŸãð
- OSS: github.com/bokuwalily ð
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ