䜿ãããªããªã£ãã¹ãã«ãèªåã§å¢å Žéã â launchd鱿¬¡ã®ã¹ãã«ã©ã€ããµã€ã¯ã«ç®¡ç
åå㯠Claude Codeã®ã¢ãã«çµäºãèªåæ€ç¥ããŠãšã€ãªã¢ã¹ãåãæ¿ãã話 ãæžããŸãããä»åãåã launchd ã®ä»çµã¿ã䜿ã£ãŠãèšãäžãã auto-skill ãè ãããªãã©ã€ããµã€ã¯ã«ç®¡çãå®è£ ãã話ã§ãã
以åã®èšäºã§æžããéããauto-skill ã¯äŒè©±ãã°ããå€éããããèªåçæããŸããæŸçœ®ãããš ~/.claude/skills/auto/ ãééãªãè¥å€§ããClaude Code èµ·åæã®ã³ã³ããã¹ãããŒããéããªããããšãã£ãŠæåã§éåŒãã®ã¯ç¶ããŸãããä»åã¯ãæçµäœ¿çšæ¥ã®èªåç®åº â 2段ééé¿ãã§ããããå®å
šã«èªååããŸããã
å°ãããšïŒçæã¯èªåãªã®ã«å»æ£ã¯æå
å€éããã skill-harvest.sh ã¯æ¯æ¥ 3:30 ã«èµ°ã£ãŠã¹ãã«ã远å ãç¶ããŸããäžæ¹ãããã®ã¹ãã«ã¯ãã䜿ãããŠããªãããç¥ãä»çµã¿ãäœããªããæ°ã¥ããšä»¥äžã®ç¶æ
ã«ãªã£ãŠããŸãã
- ã©ããçŸåœ¹ã§ã©ããæ»ãã§ããã誰ãåãããªã
- å šã¹ãã«ãã³ã³ããã¹ãã«ããŒãããèµ·åãéããªã
- åé€ããã®ãæããŠæåæŽçãåŸåãã«ãªã
ãåé€ã¯ããããªãããè ã£ããã®ã¯èŠéããå€ãããããšããèŠä»¶ãæºããéç Žå£ã®2段éé¿ãå¿ èŠã§ããã
å šäœèšèšïŒharvestïŒæ¥æ¬¡ïŒãš curateïŒé±æ¬¡ïŒãåé¢ãã
æ¯æ¥ 3:30 skill-harvest.sh äŒè©±ãã° â æ°ã¹ãã«çæ
æ¯é±æ¥æ 4:15 skill-curate.sh ã¹ãã«åãäŒè©±ãã°ã§éåŒã â last-used ç®åº â éé¿
çæãšæŽçãå¥ããã»ã¹ã«åãé¢ãã®ããã€ã³ãã§ããharvest ãè¶³ãç®ãcurate ãåŒãç®ïŒå®éã«ã¯ãå¢å Žãžã®ç§»éãïŒãharvest ã® 45 ååŸã« curate ãèµ°ãèšèšã«ããŠããã®ã¯ãçãŸãããŠã® status: active ã¹ãã«ãåœæ¥ã® curate ã«è©äŸ¡ãããªãããã§ãã
harvestïŒlast-used ã®å祚ãäœã
skill-curate.sh ãããã®ã¹ãã«ããã€æåŸã«äœ¿ã£ãããã倿ããã«ã¯æ ¹æ ãå¿
èŠã§ãããã®æ ¹æ ããã¹ãã«åãäŒè©±ãã°ã«ç»å Žããæçµ mtimeãã§ãã
harvest åŽã§äŒè©±ãã°ã ~/Documents/my-knowledge-base/raw/conversations/*.md ã«èç©ããcurate ãããã grep ããŸãã
harvest ã®ãã©ã¡ãŒã¿ã¯ä»¥äžã®éãã§ãïŒskill-harvest.sh ããïŒã
MAX_LOGS=3 # 1åã§æ±ããã°æ¬æ°
PER_LOG_BYTES=15000 # ãã°1æ¬ãããã®åã蟌ã¿äžéãã€ã
BUDGET_USD=1.20 # æŽèµ°é²æ¢ã®ã³ã¹ããã£ãã
TIMEOUT_SEC=600
ãã°ã¯ grep -v ã§ system-reminder è¡ãšã¹ãã«äžèЧè¡ã®ãã€ãºãé€å€ããŠãããã€ãäžéã§åããclaude -p ã«æž¡ããŠããŸãã
grep -v -e 'system-reminder' -e '^- [a-z0-9].*:' "$f" 2>/dev/null | head -c $PER_LOG_BYTES
curateïŒlast-used ã®ç®åºããžãã¯
curate ã®æ žã¯ãã¹ãã«åãããŒã«äŒè©±ãã°ã grep ã㊠mtime ãéåŒãããéšåã§ãïŒskill-curate.sh ããïŒã
lastlog=$(grep -rl -- "$skill" "$LOGS" 2>/dev/null \
| while read f; do stat -f '%m' "$f" 2>/dev/null; done \
| sort -rn | head -1)
grep -rl ã§ã¹ãã«åãå«ããã¡ã€ã«ãåæããstat -f '%m' ã§åãã¡ã€ã«ã® mtimeïŒUnix ã¿ã€ã ã¹ã¿ã³ãïŒãåãåºããŠæå€§å€ãéžã³ãŸãã
ãã®å€ã Python3 ã¯ã³ã©ã€ããŒã§çµéæ¥æ°ã«å€æããŸãã
import sys, os, time, datetime
lastlog, created, md = (sys.argv + ["","",""])[1:4]
ref = None
if lastlog.strip():
try: ref = float(lastlog)
except: ref = None
if ref is None and created.strip():
try: ref = time.mktime(datetime.datetime.strptime(created.strip(), "%Y-%m-%d").timetuple())
except: ref = None
if ref is None:
ref = os.path.getmtime(md)
print(int((time.time() - ref) // 86400))
ãã©ãŒã«ããã¯ã 3 段ãããŸããâ äŒè©±ãã°ã® mtime â â¡frontmatter ã® created: â ⢠SKILL.md ãã¡ã€ã«èªäœã® mtimeããã°ã«äžåºŠãç»å ŽããŠããªãæ°ã¹ãã«ã§ããcreated æ¥ããã«ãŠã³ããå§ãŸããŸãã
2段éé¿ïŒ30æ¥ â stale / 90æ¥ â .archive
çµéæ¥æ°ãåºãã以äžã®æ¡ä»¶åå²ã§åŠçããŸãã
if (( days > ARCHIVE_DAYS )); then # 90æ¥è¶
mv "$d" "$ARCH/" && echo "[$(ts)] ARCHIVED (${days}d unused): $skill" >> "$LOG"
elif (( days > STALE_DAYS )); then # 30æ¥è¶
python3 - "$md" <<'PY'
import sys, re
p = sys.argv[1]; s = open(p).read()
if re.search(r'^status:', s, re.M):
s = re.sub(r'^status:.*$', 'status: stale', s, count=1, flags=re.M)
else:
s = re.sub(r'^(author:[ \t]*auto.*)$', r'\1\nstatus: stale', s, count=1, flags=re.M)
open(p, 'w').write(s)
PY
echo "[$(ts)] stale (${days}d unused): $skill" >> "$LOG"
((active++))
else
((active++))
fi
- 30æ¥è¶
ïŒfrontmatter ã®
status:ãstaleã«æžãæããããã¡ã€ã«ã¯åãããªã - 90æ¥è¶
ïŒ
~/.claude/skills/auto/.archive/ã«ç©çç§»åãããå®åé€ã¯ããªã
status: stale ã¯å°æ¥çã«ã¹ãã«ããŒããŒåŽã§ãã£ã«ã¿ããéã®ç®å°ã§ããstale ãé€å€ããã°ã³ã³ããã¹ãããŒãã軜ãã§ããŸãã
ã30æ¥è§Šã£ãŠããªããã¯ã¹ãã«ãæªããšã¯éããªãããã®äœæ¥ãããŠããªãã ããããããªããã ããæçµå€æã¯äººéã«æ®ããcurate ã¯éæ Œãšç§»éã®ã¿ãæ åœããŸãã
éç Žå£ã®å®å šè£ 眮
å®è¡åã¹ãããã·ã§ãã
curate ãäœãããåã«å¿ ããããéããŸãã
tar czf "$SNAP/auto-$(date +%Y%m%d-%H%M%S).tar.gz" \
-C "$HOME/.claude/skills" \
--exclude='auto/.snapshots' --exclude='auto/.archive' \
auto 2>/dev/null \
&& echo "[$(ts)] snapshot taken" >> "$LOG"
~/.claude/skills/auto/.snapshots/auto-YYYYMMDD-HHMMSS.tar.gz ã«ã¹ããããåã£ãŠããåŠçãéå§ããŸããééããŠéé¿ãããŠã tar xzf ã§å³åŸ©å
ã§ããŸãã
author: auto 以å€ã¯çµ¶å¯Ÿã«è§Šããªã
if ! grep -q '^author:[[:space:]]*auto' "$md"; then
echo "[$(ts)] skip (not author:auto): $skill" >> "$LOG"
continue
fi
ææžãã¹ãã«ããã³ãã«ã¹ãã«ã¯ author: auto ãæããªãããå®å
šã«ã¹ããããããŸãã
LLM çµ±åææ¡ãã§ãŒãº
æ¥æ°ã«ããæ©æ¢°çãªéé¿ã«å ããŠãæå³çã«éè€ããŠããã¹ãã«ã LLM ã«æŽãåºããããã§ãŒãºããããŸãã
if [[ "$RUN_LLM" != "nollm" ]] && (( active >= 2 )) && [[ -x "$CLAUDE" ]]; then
# ååææ¡ãã¡ã€ã«ããåŸã«æŽæ°ãããSKILL.mdã ãã察象ã«ãã
...
STG=$(mktemp -d -t skill-curate-stg)
( cd "$STG" && perl -e 'alarm 600; exec @ARGV' "$CLAUDE" -p "..." \
--model sonnet \
--permission-mode acceptEdits \
--allowedTools "Write Edit Read" \
--add-dir "$AUTO" \
--max-budget-usd 5.00 >> "$LOG" 2>&1 < /dev/null )
[[ -f "$STG/curator-proposals.md" ]] \
&& cp "$STG/curator-proposals.md" "$PROP" \
&& echo "[$(ts)] proposals written -> $PROP" >> "$LOG"
rm -rf "$STG"
fi
èšèšäžã®å·¥å€«ã 2 ã€ãããŸãã
å·®åã ãæž¡ãïŒååã® .curator-proposals.md ããåŸã«æŽæ°ããã SKILL.md ã ãã察象ã«ããŸããæ¯é±å
šã¹ãã«ã LLM ã«èªãŸãããšã³ã¹ããç©ã¿äžããããã倿Žã®ãã£ãã¹ãã«ã®ã¿ãåŠçããŸãã
ææ¡ã¯æžããããé©çšã¯ããªãïŒclaude ã« ./curator-proposals.md ã Write ãããshell ã ~/.claude/skills/auto/.curator-proposals.md ã«ã³ããŒããŠçµããã§ããã¹ãã«ã®å®ãã¡ã€ã«ã«ã¯äžåè§ŠããŸãããã¬ãã¥ãŒãšé©çšã¯äººéã®ä»äºã§ãã
第 1 åŒæ°
nollmãæž¡ããš LLM ãã§ãŒãºãäžžããšã¹ãããã§ããŸãããã¹ãæã«åœ¹ç«ã¡ãŸããskill-curate.sh nollm
launchd ç»é²
~/Library/LaunchAgents/com.shun.skill-harvest.plistïŒã¹ã±ãžã¥ãŒã«éšåïŒ
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer>3</integer>
<key>Minute</key><integer>30</integer>
</dict>
~/Library/LaunchAgents/com.shun.skill-curate.plistïŒã¹ã±ãžã¥ãŒã«éšåïŒ
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer>4</integer>
<key>Minute</key><integer>15</integer>
<key>Weekday</key><integer>0</integer>
</dict>
Weekday: 0 ãæ¥ææ¥ã§ããäž¡ plist ãšã ProcessType: Background ã§åãããã°ã¯ãããã ~/.claude/logs/com.shun.skill-harvest.log ãš ~/.claude/logs/com.shun.skill-curate.log ã«åãããŠåãããŸãã
ç»é²ãšå³æãã¹ãã¯ä»¥äžã§è¡ããŸãã
launchctl load ~/Library/LaunchAgents/com.shun.skill-harvest.plist
launchctl load ~/Library/LaunchAgents/com.shun.skill-curate.plist
# ä»ããèµ°ãããŠåäœç¢ºèª
launchctl start com.shun.skill-harvest
launchctl start com.shun.skill-curate
èžãã èœãšã穎
grep -rlãå šãã°ãæ¯åèµ°æ»ããïŒwatermark 㯠harvest åŽã®ããã®ãã®ã§ curate ãã¹ãã«åã軞ã«åŒãæ§é äžã¯äœ¿ããªãããã°æ¬æ°ãå¢ãããšé ããªããçŸç¶ã¯èš±å®¹ç¯å²å ã ããå°æ¥ã¯ã€ã³ããã¯ã¹ãäœãå¿ èŠãããstat -f '%m'㯠macOS å°çšïŒLinux ã§ã¯stat --format='%Y'ããã®ã¹ã¯ãªãã㯠macOS ã® zsh åæã§æžãããŠãã- launchd ã® PATH ãæ¥µå°ïŒçŽ ã® launchd ç°å¢ã§ã¯
claudeã³ãã³ããèŠã€ãããªããplist ã®EnvironmentVariables/PATHã« nvm ã® bin ãã£ã¬ã¯ããªãš~/.local/binãæç€ºçã«è¿œå ããªããšå³ exit ãã ~/.claudeãžã®çŽæžãã permission denied ã«ãªãïŒClaude Code ãèµ·åäžã¯~/.claudeé äžãžã®æžã蟌ã¿ããããã¯ããããšããããharvest ã curate ãmktempã§ã¹ããŒãžã³ã°ãã£ã¬ã¯ããªãäœããshell ãã³ããŒããæ§é ã«ãªã£ãŠãããçŽæ¥~/.claude/skills/auto/ã«æžããããšèœã¡ã- LLM ã«å
šã¹ãã«ãæž¡ããšã³ã¹ããè·³ããïŒ
--add-dir "$AUTO"ã§èªããç¶æ ã«ãã€ã€ãããã³ããåŽã§ãååææ¡ãã¡ã€ã«ããåŸã«æŽæ°ããã SKILL.md ã ãã Read ããŠããšæç€ºããŠãããæç€ºããªããš LLM ãå šä»¶èªã¿ã«è¡ã
ãŸãšã
- auto-skill ã®ã©ã€ããµã€ã¯ã«ã¯ 30æ¥ â
status: stale/ 90æ¥ â.archive/ç§»éã® 2 段ã§ç®¡çãã - ãæçµäœ¿çšæ¥ãã¯äŒè©±ãã°ãžã®ã¹ãã«åç»å Ž mtime ã
grep -rlã§éåŒãããŠç®åºãã - å®è¡å tar ã¹ãããã·ã§ãã +
author: auto以å€ã¯è§Šããªããã§éç Žå£ãä¿èšŒãã - LLM çµ±åææ¡ã¯å·®åã ãæž¡ããŠ
.curator-proposals.mdã«æžããããã¹ãã«å®ãã¡ã€ã«ã®é©çšã¯äººéãè¡ã - harvestïŒæ¥æ¬¡ 3:30ïŒãš curateïŒé±æ¬¡æ¥æ 4:15ïŒã launchd ã«åé¢ããŠç»é²ãã
次åã¯ãã¹ãã«ã»ã¡ã¢ãªã»äŒè©±ãã°ãç©ã¿äžãã£ãåŸã« Claude Code èªäœãéããªã£ã話 ââ ã³ã³ããã¹ãæ³šå ¥éã®ç£æ»ãšåæžãæžãäºå®ã§ãã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- AIã§ãå¯ãŠãŠãåãä»çµã¿ããäœã£ãŠæ120äžã«ãã話㯠noteã®ææèšäº ã«ð°
- OSS: github.com/bokuwalily ð
- ææ°æ å ±ã»ãåãåãã㯠X @bokuwalily ãžð
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ