ð èªåçæã¹ãã«ãå šãããžã§ã¯ãã«é±æ¬¡é åžãã
æ10äžã®å€§åŠçã ã£ãããã¯ãèªååããšããèšèãé ãã£ããæãæã¡ã§æ60äžã«ãªããäŒç€Ÿéœåã§è§£éãããŠãŒãã«æ»ããå幎ãããŠClaude Codeã®èªåŸç°å¢ãçµã¿çŽããŠä»ã¯æå120äžããã®éçšã§ããã£ãã®ã¯ã皌ããã©ããããå ã«ãç°å¢ãèªåã§è³¢ããªãç¶ãããã©ããããåå²ç¹ã ãšããããšã§ãã
ãªããã®ä»çµã¿ãå¹ãã®ã
Claude Codeã® ~/.claude/skills/auto/ ã¯ããã°å人ã®ãæçã©ã€ãã©ãªãã§ããäœæ¥äžã«çºèŠããåé¿çã»å®äºæ¡ä»¶ã»æ€èšŒã³ãã³ããAIãèªåã§ã¹ãã«ãã¡ã€ã«ã«æžãåºããæ¬¡ã®äŸé Œã§å³åº§ã«åç
§ã§ããââããããä»çµã¿ã«ãªã£ãŠããŸãã
ãšãããçŸå®ã¯å°ãéããŸãã
ã¹ãã«ã¯ .claude/skills/auto/ ã«æºãŸãç¶ããŸããã§ãæ°ããgitãªããžããªãåã£ããããžã§ã¯ããæ°é±éã¶ãã«éãã坿¥æ¡ä»¶ãå¥ã®èšèªã§æžããããŒã«çŸ€ââãããã«ã¯ãã®ã¹ãã«ãæåããååšããªãã人éãæã§ã³ããŒããããæ¯åããã®ã¹ãã«ãåç
§ããŠããšæç€ºãå
¥ããªãéããæè§ç©ã¿äžããæçãå¥ãããžã§ã¯ãã§ã¯å®å
šã«æ»ãã§ããŸãã
åé¡ã®æ§é ã¯ããã§ãã
- ã¹ãã«ã¯
.claude/skills/auto/ãšããäžãæã«èç©ãããïŒã°ããŒãã«ïŒ - å®éã«åç
§ãããã®ã¯ããã®ãããžã§ã¯ãã«
.agents/ãŸãã¯.claude/skills/ããããšããïŒããŒã«ã«ïŒ - æ°èŠãããžã§ã¯ããäœããã³ã«ãã®æ©æž¡ãã¯èµ·ããªãïŒãŒãã¹ã¿ãŒãïŒ
ããã¯ãç°å¢ãè²ãŠããã§ã¯ãªããæ¯åè²ãŠçŽããã§ããæåãäžå®ã©ã€ã³ãè¶ ãããšæ¡ä»¶ã®æãæã¡æ°ãå¢ããæ°èŠãªããžããªãé±ã«2ã3æ¬åãããšãããããã®ãã³ã«ã¹ãã«ã®æ¬ èœã«æ°ã¥ããæåã³ããŒããŠã確èªããŠââãšããäœæ¥ã¯éãã«æéãé£ããŸããããŒã«åŒã³åºã1ååã®æéã§ã¯ãªããããã®ã¹ãã«ãä»ããã«ããã°3åã§çµãã£ãã®ã«ããšããæ©äŒæå€±ã§ãã
ãã®åé¡ã解決ããã®ã鱿¬¡ã®èªåé åžã¹ã¯ãªããã§ããæ¯é±æ¥æã®æ©æãå šgitãªããžããªãèµ°æ»ããŠã¹ãã«ãæµã蟌ãã人éãäœãããªããŠããææã«éãããããžã§ã¯ãã«ã¯ææ°ã®ã¹ãã«ãæã£ãŠããã
äœæ¥éãå¢ããã®ã§ã¯ãªããç°å¢ã®å質ãåºäžãããã Claude CodeèªåŸç°å¢ãçµãäžã§ããã®èšèšææ³ãäžçªå¹ããŠããŸãã
äžç¹ãéèŠãªåæããããŸããããã§èšããã¹ãã«ããšã¯ãèªåã§ç©ã¿äžãã ~/.claude/skills/auto/ é
äžã®ãã¡ã€ã«çŸ€ã®ããšã§ãããã³ãã«ã¹ãã«ã ~/.claude/skills/ecc/ ã«ã¯äžåè§ŠããŸãããé
åžå¯Ÿè±¡ã¯ãããŸã§èªåã®æçã©ã€ãã©ãªã ãã§ãã
å šäœã®æµã
ä»çµã¿å šäœã俯ç°ããŸãã
[launchd]
com.shun.autoskills-sync
æ¥æ 06:10 èµ·å
|
v
[autoskills-sync.sh]
|
+-- â ãªã³ã©ã€ã³ç¢ºèª
| curl -sf -m 8 https://registry.npmjs.org/
| ãªãã©ã€ã³ â exit 0ïŒäœãããæ£åžžçµäºïŒ
|
+-- â¡ ãããžã§ã¯ãæ¢çŽ¢
| find ~ ~/dev -maxdepth 2
| -name .git â 芪ãã£ã¬ã¯ããªããªã¹ã
| -name package.json / pyproject.toml /
| requirements.txt / go.mod /
| Cargo.toml / pubspec.yaml /
| skills-lock.json
| sort -u ã§éè€é€å»
|
+-- ⢠é€å€ãã£ã«ã¿
| oss-trial/* / *-public / node_modules/*
| Documents/ Library/ Applications/ go/
| .claude/ config-snapshots/ claude-obsidian/
| digital-products
|
+-- ⣠åãããžã§ã¯ããžé
åž
| npx -y autoskills --yes
| åºåãããN skills installedããããŒã¹
|
+-- †gitignore 远èšïŒæ¬çªã®ã¿ïŒ
| .agents/ / .claude/skills/ / skills-lock.json
|
+-- ⥠ãã°èšé²
~/.claude/logs/com.shun.autoskills-sync.log
launchd èšå®ïŒplistã§ãæ¥æ 06:10ãã«åºå®
macOSã®ãžã§ãã¹ã±ãžã¥ãŒã©launchdã¯ã~/Library/LaunchAgents/ ã«çœ®ããplistãèªã¿èŸŒãã§èªåå®è¡ããŸããcom.shun.autoskills-sync.plist ã®äžèº«ã¯ãããªã£ãŠããŸãã
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key>
<integer>6</integer>
<key>Minute</key>
<integer>10</integer>
<key>Weekday</key>
<integer>0</integer>
</dict>
</array>
Weekday ã 0 ã¯æ¥ææ¥ã§ããæ¯é±æ¥æã®6æ10åã«èµ·åããŸããRunAtLoad ã false ãªã®ã§ãplistãããŒãããç¬éã«ã¯èµ°ããŸãããæ¬¡ã®æ¥æãŸã§åŸ
æ©ããŸãã
PATHã®èšå®ãå ¥ã£ãŠããŸãã
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>~/.nvm/versions/node/v24.13.0/bin:
/opt/homebrew/bin:/opt/homebrew/sbin:
/usr/local/bin:/usr/bin:/bin:...</string>
</dict>
launchdã¯ã·ã§ã«ãçµç±ããªããããéåžžã® .zshrc ãèªãŸããŸãããnvmã§ç®¡çããŠããNode.jsã®ãã¹ãæç€ºçã«PATHãžæžããŠãããªããšãnpx ã³ãã³ããèŠã€ãããã«å€±æããŸãã/bin/zsh -lc ã§èµ·åããŠããŸãããããã§ãlaunchdã®ç°å¢ã§ã¯ .zshrc ã¯èªãŸããªãããããã®PATH泚å
¥ã¯å¿
é ã§ãã
StandardOutPath ãš StandardErrorPath ã¯äž¡æ¹ãšã ~/.claude/logs/com.shun.autoskills-sync.log ã«åããŠããŸããã¹ã¯ãªããå
ã® log() 颿°ãåããã¡ã€ã«ã«è¿œèšããããããã°ã¯1ãã¡ã€ã«ã«éçŽãããŸãã
â ãªã³ã©ã€ã³ç¢ºèªïŒ8ç§ã¿ã€ã ã¢ãŠãã§å®å šã«æãã
if ! curl -sf -m 8 https://registry.npmjs.org/ >/dev/null 2>&1; then
log "offline â skip"; exit 0
fi
èµ·åã¿ã€ãã³ã°ãã¹ãªãŒãæããæ©å
ã¢ãŒããšéãªãããšããããŸãããã®ãšãã« npx ãåŒãã§ã倱æããã ããªã®ã§ãæåã«npmjsãžã®HTTPçéã確èªããŸãã-m 8 ã§8ç§ä»¥å
ã«å¿çããªããã°ãªãã©ã€ã³æ±ããšããexit 0 ã§æ£åžžçµäºããŸãããšã©ãŒã³ãŒããè¿ããªãçç±ã¯ãlaunchdããšã©ãŒçµäºããªãã©ã€ã®ããªã¬ãŒã«ããããšãããããã§ãã
â¡ãããžã§ã¯ãæ¢çŽ¢ïŒ6çš®ã®ãããã§ã¹ãã§èšèªã暪æ
find "$HOME_DIR" "$HOME_DIR/dev" -maxdepth 2 -name .git -type d 2>/dev/null \
| sed 's|/\.git$||'
find "$HOME_DIR" "$HOME_DIR/dev" -maxdepth 2 \
\( -name package.json -o -name pyproject.toml -o -name requirements.txt \
-o -name go.mod -o -name Cargo.toml -o -name pubspec.yaml \
-o -name skills-lock.json \) \
-not -path '*/node_modules/*' 2>/dev/null | sed -E 's|/[^/]+$||'
æ¢çŽ¢ã¯ ~ ãš ~/dev ã® -maxdepth 2ãã€ãŸã2éå±€ãŸã§ã§ããããä»¥äžæ·±ããã¹ãã®ãã£ã¬ã¯ããªã¯å¯Ÿè±¡å€ããã®å¶éã¯ããã©ãŒãã³ã¹ãšæå³çãªã¹ã³ãŒãã®äž¡æ¹ã«çç±ããããŸãã
.git ãã£ã¬ã¯ããªãæ¢ããŠãã®èŠªãåãçµè·¯ãšããããã§ã¹ããã¡ã€ã«ãæ¢ããŠèŠªãã£ã¬ã¯ããªãåãçµè·¯ã®2系統ãORã§èµ°ãããsort -u ã§éè€é€å»ããŸããgitãªããžããªã§ãªããŠã package.json ãããã°å¯Ÿè±¡ã«ãªãããgitãªããžããªã§ã package.json ããªããã°ãããã§ã¹ãçµè·¯ã§ã¯æŸãããªãââäž¡æ¹ã®çµè·¯ã§åãããŒããé²ãã§ããŸãã
察å¿èšèªã¯JavaScript/TypeScriptïŒpackage.jsonïŒãPythonïŒpyproject.tomlãrequirements.txtïŒãGoïŒgo.modïŒãRustïŒCargo.tomlïŒãFlutter/DartïŒpubspec.yamlïŒããããŠAutoskillsãçæããlockïŒskills-lock.jsonïŒãå®è³ªçã«ã»ãŒãã¹ãŠã®å人éçºã¹ã¿ãã¯ãã«ããŒããŠããŸãã
bash 3.2ãšã®äºææ§ã®ãã mapfile/readarray ã䜿ãããwhile IFS= read -r line ã§é
åã«è©°ããŠããŸããmacOSã® /bin/bash ã¯ããã©ã«ã3.2ã§ããHomebrewã§bash 5ãå
¥ããŠããŠããã¹ã¯ãªããã®å
é è¡ã #!/usr/bin/env bash ã§ããã° /bin/bash ãåŒã°ããŸãããã®èœãšã穎ã¯ã³ã¡ã³ãã«ãæèšãããŠããŸãã
# NOTE: mapfile/readarray 㯠macOS æšæº /bin/bash 3.2 ã«ç¡ããã䜿ããªãïŒ3.2/5 䞡察å¿ã® while-readïŒ
â¢é€å€ãã£ã«ã¿ïŒãªããã®4ã«ããŽãªã匟ãã®ã
is_excluded() {
local d="$1"
[ "$d" = "$HOME_DIR" ] && return 0
[ "$d" = "$HOME_DIR/dev" ] && return 0
case "$d" in
*/oss-trial/*) return 0 ;; # 第äžè
fork
*-public) return 0 ;; # å
¬éOSSãã©ãŒ
*/node_modules/*) return 0 ;;
"$HOME_DIR"/Documents/*|"$HOME_DIR"/Library/*|"$HOME_DIR"/Applications/*) return 0 ;;
"$HOME_DIR"/Public/*|"$HOME_DIR"/go|"$HOME_DIR"/go/*) return 0 ;;
*/.claude/*|*config-snapshots*|*claude-obsidian*) return 0 ;;
"$HOME_DIR"/digital-products|"$HOME_DIR"/digital-products/*) return 0 ;;
esac
return 1
}
é€å€ã«ãŒã«ã¯4ã€ã®ã«ããŽãªã«åããŠèªããšããããããã§ãã
ã«ãŒããã£ã¬ã¯ããªèªèº«ã®ä¿è·ã $HOME ãš $HOME/dev ã¯ããèªäœãé€å€ããŸãã~/.claude/ ã ~/.agents/ ãšãã£ãèšå®é åãäžæžããããªã¹ã¯ãããããã§ããæ¢çŽ¢ã®èµ·ç¹ã§ãããã®2ãã£ã¬ã¯ããªã¯ãäžèº«ã察象ã«ããŠã芪èªèº«ã¯å¯Ÿè±¡ã«ããŸããã
第äžè
ã³ãŒãã®ä¿è·ã */oss-trial/* ã¯ä»äººã®OSSãªããžããªããã©ãŒã¯ããŠè©ŠããŠããå Žæã§ããããã«èªåã®ã¹ãã«ãæžã蟌ãã®ã¯ãã¬ããžããªã®ãªãŒããŒã®æå³ãšé¢ä¿ãªã倿Žãæ··å
¥ããããšãæå³ããŸããå
¬éOSSãã©ãŒãæã *-public ãåæ§ã§ãããããã®ãããžã§ã¯ãã«å¯ŸããŠautoskillsãèµ°ãããŠããŸããšãæå³ããªã .gitignore ã®å€æŽã skills-lock.json ã®çæãèµ·ããå
¬éãªããžããªãæ±æãããªã¹ã¯ããããŸãã
macOSã·ã¹ãã é åã®ä¿è·ã Documents/ãLibrary/ãApplications/ãPublic/ãgo/ ã¯ã³ãŒããããžã§ã¯ãã§ã¯ãããŸããããããã« package.json ãå¶ç¶ååšããå Žåã§ãé€å€ããŸããLibrary/ ã«ã¯Autoskillsãšã¯ç¡é¢ä¿ã®ã¢ããªããŒã¿ã倧éã«ããã誀ã£ãŠèµ°ããããšæå³äžæãªãã¡ã€ã«ãçæãããŸãã
Claude Codeèªèº«ã®èšå®é åã®ä¿è·ã .claude/ãconfig-snapshotsãclaude-obsidian ã¯Claude Codeã®èšå®ãã¡ã€ã«ãäŒè©±ãã°ãObsidianã®vaultã§ãããããã¯git管çãããŠããŸãããã¹ãã«é
åžã®å¯Ÿè±¡ã§ã¯ãããŸãããç¹ã« ~/.claude/ èªäœãæ¢çŽ¢ã§åŒã£ãããå¯èœæ§ããããããæç€ºçã«é€å€ããŠããŸãã
ã³ã³ãã³ãç³»ãã£ã¬ã¯ããªã®ä¿è·ã digital-products ã¯ããã³ããéãªã©ã®ã³ã³ãã³ã販売çšãã£ã¬ã¯ããªã§ãã³ãŒãã¹ã¿ãã¯ãæã¡ãŸãããã¹ãã«ãå
¥ããæå³ããªãããé€å€ããŠããŸããã³ã¡ã³ãã«ãã¹ã¿ãã¯ç¡ããšæèšãããŠããã®ã¯ãå°æ¥èª°ãããã®èšå®ãèªãã ãšãã«é€å€çç±ããããããã«ããããã§ãã
â£é åžïŒnpxã§æ¯åææ°ãåãã«è¡ã
out="$(cd "$d" && npx -y autoskills ${DRY:---yes} 2>&1)"
n="$(echo "$out" | grep -oE '([0-9]+) skills installed' | grep -oE '^[0-9]+' | head -1)"
[ -z "$n" ] && n="$(echo "$out" | grep -oE 'Skills to install \([0-9]+\)' | grep -oE '[0-9]+' | head -1)"
[ -z "$n" ] && n=0
npx -y autoskills --yes ããããžã§ã¯ããã£ã¬ã¯ããªã§å®è¡ããŸãã-y ã¯npxã®ç¢ºèªããã³ãããçç¥ã--yes ã¯autoskillsã®ã€ã³ã¿ã©ã¯ã·ã§ã³ãçç¥ããŸãã2>&1 ã§æšæºãšã©ãŒãåã蟌ã¿ã$out 倿°ã«å
šåºåãæ ŒçŽããŠããããŒã¹ããŸãã
åºåãã©ãŒãããããN skills installedããšãSkills to install (N)ãã®2ãã¿ãŒã³ããããšã«å¯Ÿå¿ãããããgrepã2段éã§æžããŠããŸããã©ã¡ãã«ããããããªããã° n=0 ãšããŠæ±ãã0ã®ãããžã§ã¯ãã¯ãã°ã«ã0 (skip)ããšèšé²ããŠãã®ãŸãŸæ¬¡ãžé²ã¿ãŸãã
${DRY:---yes} ã¯bashã®ãã©ã¡ãŒã¿å±éã§ããDRY 倿°ãé空ïŒ--dry-run ãã©ã°ããïŒã®ãšã㯠--dry-run ãæž¡ãã空ã®ãšã㯠--yes ãæž¡ããŸããããã«ããæåå®è¡ã§ãæ¬çªå®è¡ã§ãåãã¹ã¯ãªããã䜿ãåããŸãã
â€gitignore远èšïŒã¹ãã«ããªããžããªããéé¢ãã
if [ -z "$DRY" ] && (cd "$d" && git rev-parse --git-dir >/dev/null 2>&1); then
for pat in ".agents/" ".claude/skills/" "skills-lock.json"; do
grep -qxF "$pat" "$d/.gitignore" 2>/dev/null || echo "$pat" >> "$d/.gitignore"
done
fi
ã¹ãã«ã1件以äžã€ã³ã¹ããŒã«ãããgitãªããžããªã«ã ãå®è¡ãããŸãã.agents/ã.claude/skills/ãskills-lock.json ã®3ãã¿ãŒã³ã .gitignore ã«è¿œèšããŸãããgrep -qxF ã§äºåã«ååšç¢ºèªãããã§ã«ããè¡ã¯éè€è¿œèšããŸããã
ãã®åŠçãéèŠãªçç±ã¯ãããŒã ãªããžããªãå ¬éãªããžããªãžèª€ã£ãŠã¹ãã«ãã³ããããããªã¹ã¯ãé²ãããã§ããã¹ãã«ãã¡ã€ã«ã¯å人ã®ç°å¢äŸåã§ãããä»ã®éçºè ã®ç°å¢ã§ã¯æå³ããªããªããããããæ··ä¹±ãæããŸãã
--dry-run ã®ãšã㯠DRY ãé空ãªã®ã§ [ -z "$DRY" ] ãåœãšãªããgitignore远èšã¯ã¹ããããããŸãããã©ã€ã©ã³ã¯èªã¿åãã®ã¿ããšããååã培åºããŠããŸãã
â¥ãã°ïŒ1ãã¡ã€ã«ã«å šæ å ±
å®è¡ã®ãã³ã« ~/.claude/logs/com.shun.autoskills-sync.log ãžã¿ã€ã ã¹ã¿ã³ãä»ãã§è¿œèšããŸãã
[2026-07-13 06:10:03] ==== autoskills-sync start ====
[2026-07-13 06:10:04] lead-finder: 12 skills
[2026-07-13 06:10:06] affiliate-fc2: 8 skills
[2026-07-13 06:10:08] note-autolike: 0 (skip)
[2026-07-13 06:10:09] oss-trial: (excluded)
[2026-07-13 06:10:10] ==== done: 2 projects / 20 skills, 1 excluded ====
å®äºè¡ã® done: N projects / M skills, K excluded ã®ãã©ãŒãããã§ãé
åžããããããžã§ã¯ãæ°ã»ã¹ãã«ç·æ°ã»é€å€æ°ãäžç®ã§ããããŸããé±ã«1åããèµ°ããªãããããã°ãè¥å€§åããããŒã¹ãç·©ããã§ãã
å®è£ ã®è©³çް
set -uo pipefail ââ3ãã©ã°ãæããçç±
ã¹ã¯ãªããåé ã®1è¡ã§ãã
set -uo pipefail
-uïŒnounsetïŒã¯æªå®çŸ©å€æ°ãå±éããããšãããšãã«ãšã©ãŒã§åæ¢ããŸããã¿ã€ããããŸãŸ rm -rf "$UNDEFINED_DIR/" ã®ãããªã³ãŒããèµ°ãããšãé²ããŸãããã ãåŸè¿°ããŸããã空é
åã«å¯ŸããŠã¯ -u ãæå³å€ã®åäœããããããå¥éã¬ãŒããå¿
èŠã«ãªããŸããã
-o pipefail ã¯ãã€ãã©ã€ã³ã®éäžã³ãã³ãã倱æãããšãããã€ãå
šäœã®çµäºã³ãŒãããæåã«å€±æããã³ãã³ãã®ã³ãŒããã«ããŸããgrep -oE '...' | head -1 ã®ãããªå€æ®µãã€ãã§ãæåã®grepã空ãããã§ã head -1 ã 0 ãè¿ããŠæåæ±ãã«ãªãçœ ãé²ããŸãã
-eïŒerrexitïŒã¯ãããŠä»ããŠããŸãããis_excluded 颿°ã return 0ïŒé€å€=çïŒãè¿ãåäœã¯ãã·ã§ã«ããèŠããšãã³ãã³ãã倱æãããã«æ ããŸãã-e ããããšãã®é¢æ°ãåŒãã æç¹ã§ã¹ã¯ãªããå
šäœãçµäºããŸããæ¡ä»¶åå²ãå«ã颿°ã« -e ã¯é¬Œéã§ãããšã©ãŒãã³ããªã³ã°ã¯æç€ºç㪠|| { log "..."; exit 1; } ã§æžãã»ãã誀çããªãã
ãµãã·ã§ã«cdãã¿ãŒã³ ââäœæ¥ãã£ã¬ã¯ããªãæ±æããªã
out="$(cd "$d" && npx -y autoskills ${DRY:---yes} 2>&1)"
cd "$d" && npx ... ãã³ãã³ã眮æ $() ã§å²ãã§ããŸããcd ã®å¹æããµãã·ã§ã«ã«éã蟌ããããã§ãã $() å
ã¯ç¬ç«ããã·ã§ã«ç°å¢ãªã®ã§ãå
éšã§ã® cd ã¯èŠªã·ã§ã«ã®ã«ã¬ã³ããã£ã¬ã¯ããªã«åœ±é¿ããŸãããããããªããšãã«ãŒãã®2åç®ä»¥éã§ npx ãåã®ãããžã§ã¯ããã£ã¬ã¯ããªã§å®è¡ããç¶ããå¯èœæ§ããããŸãã
gitignore ã®ç¢ºèªéšåãåãèãæ¹ã§ãã
if [ -z "$DRY" ] && (cd "$d" && git rev-parse --git-dir >/dev/null 2>&1); then
ãã¡ã㯠$() ã§ãªã () ã§ããç®çã¯åããã$d ãgitãªããã©ããã芪ã·ã§ã«ã®cdãªãã«ç¢ºèªãããã¯ã³ã©ã€ããŒãšããŠæ©èœããŸããæ¡ä»¶å€å®ã®ããã« cd ããããããã® cd ãã«ãŒãã«åŒãç¶ãããããªããšãã() ã¯ã·ã³ãã«ã§ç¢ºå®ãªææ®µã§ãã
bash 3.2äºææ§ ââmapfileçŠæ¢ã®èæ¯
æå㯠mapfile ã§ã³ã¬ã¯ã·ã§ã³ãäœãããšããŸããã
# æžãããã£ããæžããªãã£ã
mapfile -t CANDIDATES < <(find ...)
macOSã®ããã©ã«ã /bin/bash ã¯ããŒãžã§ã³3.2ã§ããmapfileïŒå¥å readarrayïŒã¯bash 4.0以éã®æ©èœãªã®ã§ã/bin/bash ã§å®è¡ãããšãã®ãŸãŸ command not found ã§ããã¹ã¯ãªããå
é ã #!/usr/bin/env bash ã§ãã£ãŠããlaunchdã®PATHèšå®ã«ãã£ãŠã¯ /bin/bashïŒ3.2ïŒãåŒã°ããŸããã³ã¡ã³ãã«ãæèšãããŠããŸãã
# NOTE: mapfile/readarray 㯠macOS æšæº /bin/bash 3.2 ã«ç¡ããã䜿ããªãïŒ3.2/5 䞡察å¿ã® while-readïŒ
解決ç㯠while IFS= read -r ã«ãŒãã§ãã
CANDIDATES=()
while IFS= read -r line; do
[ -n "$line" ] && CANDIDATES+=("$line")
done < <(
{ find "$HOME_DIR" "$HOME_DIR/dev" -maxdepth 2 -name .git -type d 2>/dev/null | sed 's|/\.git$||'
find "$HOME_DIR" "$HOME_DIR/dev" -maxdepth 2 \
\( -name package.json -o -name pyproject.toml -o -name requirements.txt \
-o -name go.mod -o -name Cargo.toml -o -name pubspec.yaml \
-o -name skills-lock.json \) \
-not -path '*/node_modules/*' 2>/dev/null | sed -E 's|/[^/]+$||'
} | sort -u
)
IFS= ã§ãã£ãŒã«ãåå²ãç¡å¹åãã-r ã§ããã¯ã¹ã©ãã·ã¥ãšã¹ã±ãŒããç¡èŠããããšã§ãã¹ããŒã¹ãæ¬åŒ§ãå«ããã¹ã§ã1è¡ãã€æ£ç¢ºã«èªã¿èŸŒããŸãã[ -n "$line" ] ã§ç©ºè¡ã匟ããŠããã®ã¯ãsort -u ã®åºåæ«å°Ÿã«ç©ºè¡ãæ··ããããšãããïŒç°å¢äŸåïŒããã§ãã
空é
åã¬ãŒã ââset -u ã®çœ
# bash 3.2 + set -u ã§ã¯ç©ºé
åã® "${arr[@]}" ã unbound ã§èœã¡ãããã¬ãŒã
if [ "${#CANDIDATES[@]}" -eq 0 ]; then
log "==== no candidates found; nothing to sync ===="
exit 0
fi
set -u ãæå¹ãªãšããbash 3.2ã§ã¯ç©ºé
å CANDIDATES=() ã«å¯Ÿã㊠"${CANDIDATES[@]}" ãå±éãããšunbound variable ãšã©ãŒã«ãªããŸããbash 5.x ã§ã¯ç©ºé
åãå±éããŠãåé¡ãããŸãããã3.2ã§ã¯ãååšããã空ã®é
åãããæªå®çŸ©å€æ°ããšããŠæ±ãã±ãŒã¹ãããã
${#CANDIDATES[@]} ã¯é
åã®èŠçŽ æ°ãè¿ããŸããããã¯ç©ºé
åã§ã 0 ãè¿ããset -u ããã£ãŠããšã©ãŒã«ãªããŸããããã®ã¬ãŒããå
¥ããããšã§ãåè£ãŒãã®ãšãããã°ã«çç±ãæ®ããŠæ£åžžçµäºã§ããŸãã
2段égrepã®å¿ èŠæ§
n="$(echo "$out" | grep -oE '([0-9]+) skills installed' | grep -oE '^[0-9]+' | head -1)"
[ -z "$n" ] && n="$(echo "$out" | grep -oE 'Skills to install \(([0-9]+)\)' | grep -oE '[0-9]+' | head -1)"
[ -z "$n" ] && n=0
autoskillsã®åºåãã©ãŒãããã¯ããŒãžã§ã³éã§å€ãããŸããã以å㯠12 skills installed ãšãã圢åŒã§ããããããããŒãžã§ã³ãã Skills to install (12) ãšãã圢åŒãæ··åšããããã«ãªããŸãããçæ¹ã«ãã察å¿ããŠããªããšãããäžæ¹ã®ããŒãžã§ã³ã§ã¯åžžã« n=0 ã«ãªããå®éã«ã¹ãã«ãå
¥ã£ãŠããŠãå
šãããžã§ã¯ãããskipãæ±ãã«ãªããŸãã
head -1 ãä»ããŠããã®ã¯ãgrep -oE ãå
šããããè€æ°è¡ã§åºåãããšããåŸç¶ã® [ "$n" -gt 0 ] ã®æ°å€æ¯èŒã«æ¹è¡èŸŒã¿ã®å€ãæž¡ãããŠãšã©ãŒã«ãªãããšãžã®ä¿è·ã§ãã
ç§ãè©°ãŸã£ã話
è©°ãŸãâ launchdãnpxãèŠã€ããããªã
æåã«plistãæžããŠããŒããããšããã¹ã¯ãªããã¯èµ·åããŠããã®ã«ãã°ã«äœãåºãŸããã§ãããlaunchctl list com.shun.autoskills-sync ã§ã¹ããŒã¿ã¹ã確èªãããšçµäºã³ãŒã 127ãã³ãã³ããèŠã€ãããªããšã©ãŒã§ãã
çç¶: ãžã§ããèµ·åããèšé²ã¯ããã®ã«ãã°ãã¡ã€ã«ã空ãautoskillsã®åºåãäœããªãã
åå : launchdã¯éåžžã®ãã°ã€ã³ã·ã§ã«ãšç¬ç«ããç°å¢ã§åãããã.zshrc ã«æžããnvmã®ãã¹èšå®ãäžåèªãŸããŸããã/usr/bin/npx ã¯ååšããªããã.nvm 以äžã®npxã¯launchdã®çŽ ã®PATHã«å«ãŸããŠããªããã¹ã¯ãªããèªäœã¯èµ·åã§ããŠãããã®äžã§åŒã¶ npx ãèŠã€ãããªãç¶æ
ã§ããã
çŽãæ¹: plistã® EnvironmentVariables ã«nvmã®ãã¹ãå«ãããã«PATHãæç€ºããŸãã
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>~/.nvm/versions/node/v24.13.0/bin:/opt/homebrew/bin:/opt/homebrew/sbin:
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:~/.local/bin</string>
</dict>
nvmã®ããŒãžã§ã³ã v24.13.0 ãšåºå®ããŠããã®ã¯æå³çãªå€æã§ãã.nvm/alias/default ããåçã«è§£æ±ºããããšãããšãã·ã§ã«å±éã䜿ããªãplistã§ã®èšè¿°ãè€éã«ãªããŸããåºå®ããããšã§èšå®ã確å®ã«åãã代ããã«ãnvmã§NodeããŒãžã§ã³ãåãæ¿ãããšãã¯plistãæŽæ°ããéçšãå¿
èŠã§ãã
ProgramArguments ã /bin/zsh -lc ã¹ã¯ãªãããã¹ ã«ãªã£ãŠããã®ããã®çµç·¯ããã§ããæå㯠/bin/bash ã¹ã¯ãªãããã¹ ã§ãããã-lïŒãã°ã€ã³ã·ã§ã«ïŒãä»ãããš /etc/zprofile çãèªãŸããç°å¢ãæ¬çªã«è¿ããªããŸããããã§ãnvmã®ãã¹ã¯ .zshrc ã«ããæžãããŠããªããããEnvironmentVariables ã«ããæç€ºæ³šå
¥ãæçµçã«å¿
èŠã§ããã
è©°ãŸãâ¡ --dry-runãªã®ã«gitignoreãæžãæãã£ã
åæããŒãžã§ã³ã§ --dry-run ãã©ã°ãä»ããŠå®è¡ããã®ã«ãè€æ°ã®ãããžã§ã¯ãã® .gitignore ãæžãæãããŸããã
çç¶: ãã©ã€ã©ã³ã®ã¯ãããgit diff ãèµ°ããããš .gitignore ã«3è¡è¿œèšãããŠããã
åå : åæå®è£
ã§ã¯ gitignore 远èšããžãã¯ã« DRY ãã§ãã¯ãå
¥ã£ãŠããªãã£ããnpxã«ã¯ --dry-run ãæž¡ããŠããŸããããgitignore ã®è¿œèšéšåã¯ãã©ã°ãèŠãŠããããã¹ãã«ã®ã€ã³ã¹ããŒã«ä»¶æ°ïŒn > 0ïŒã ããæ¡ä»¶ã«ããŠããããããã©ã€ã©ã³ã§ãæžãæããèµ°ã£ãŠããŸããã
# åé¡ã®ãã£ãåæããŒãžã§ã³
out="$(cd "$d" && npx -y autoskills --dry-run 2>&1)"
n=... # ããŒã¹
if [ "$n" -gt 0 ]; then
# DRYãã§ãã¯ãªãã§gitignoreãæžãæããŠãã
for pat in ".agents/" ".claude/skills/" "skills-lock.json"; do
grep -qxF "$pat" "$d/.gitignore" 2>/dev/null || echo "$pat" >> "$d/.gitignore"
done
fi
çŽãæ¹: gitignore 远èšãããã¯ã®æåã« [ -z "$DRY" ] ã远å ããŸãã
if [ -z "$DRY" ] && (cd "$d" && git rev-parse --git-dir >/dev/null 2>&1); then
for pat in ".agents/" ".claude/skills/" "skills-lock.json"; do
grep -qxF "$pat" "$d/.gitignore" 2>/dev/null || echo "$pat" >> "$d/.gitignore"
done
fi
$DRY ã空ïŒïŒæ¬çªå®è¡ïŒã®ãšãã ã远èšããããã®ä¿®æ£åŸã¯ --dry-run ããå¯äœçšãŒãã®ç¢ºèªã¢ãŒãããšããŠæ£ããæ©èœããããã«ãªããŸããã
æèšã¯ããã©ã°ã®æå³ãå®è£
ã¬ãã«ã§çµ±äžãããã§ãã--dry-run ãåãåã£ããªããå¯äœçšã®ããæäœã¯ãã¹ãŠ DRY ãã§ãã¯ã§å²ãããnpxã«ã¯æž¡ãããã倧äžå€«ããšããéšåçãªé©çšã§ã¯ãæãã¬æžãæããæ®ããŸãã
è©°ãŸã⢠空é åã§set -uãã¹ã¯ãªãããç¡èšã§èœãšãã
set -uo pipefail ã远å ããçŽåŸã®å®è¡ã§ãã¹ã¯ãªãããèµ·åãã°ã®1è¡ãåºããã«çµäºããããã«ãªããŸããã
çç¶: launchdã®ã¹ããŒã¿ã¹ã³ãŒãã 1ããã°ãã¡ã€ã«ã¯å®å
šã«ç©ºãèµ·åãã圢跡ãããªãã
åå ã®ç¹å®: bash -x autoskills-sync.sh ã§ãã¬ãŒã¹å®è¡ãããšããããªãšã©ãŒãåºãŸããã
+ for d in "${CANDIDATES[@]}"
autoskills-sync.sh: line 63: CANDIDATES[@]: unbound variable
set -u ãæå¹ãªãšããbash 3.2ã§ã¯ç©ºé
å CANDIDATES=() ã«å¯Ÿã㊠"${CANDIDATES[@]}" ãå±éãããšunbound variable ãšã©ãŒã«ãªããŸããlog() 颿°ã®åŒã³åºãããåã«bashãæ»ã¬ããããã°ã«äœãæ®ããŸããã ããããã°ã空ã ã£ãçç±ã§ãã
çŽãæ¹: for ã«ãŒãã®æåã«é
åãµã€ãºãã§ãã¯ãå
¥ããŸãã
if [ "${#CANDIDATES[@]}" -eq 0 ]; then
log "==== no candidates found; nothing to sync ===="
exit 0
fi
${#CANDIDATES[@]} ã¯ç©ºé
åã§ã 0 ãè¿ããset -u ã§ããšã©ãŒã«ãªããŸããããã®ã¬ãŒãã远å ããåŸã¯ã空é
åã§ããã°ã«çç±ãæ®ã£ãŠ exit 0 ããããã«ãªããŸããã
ãããã°ã§å¹ããæé : launchdã®ãžã§ãã¯é±1åããåããªãããããããããããšæ°ã¥ããŸã§ã«1é±éããããŸããåé¡ã®åãåãã¯ã¿ãŒããã«ãã bash -x autoskills-sync.sh --dry-run ãæåå®è¡ããã®ãæéã§ãããã¬ãŒã¹åºåã«ã¯å€æ°ã®å±ééçšãå
šéšåºããããã©ã®è¡ã§æ»ãã ããäžç®ã§ããããŸãã鱿¬¡ãžã§ãã®ãããã°ãµã€ã¯ã«ãççž®ããã«ã¯ããæåã§ãåãã¹ã¯ãªãããåŒæ°ã ãå€ããŠèµ°ããããããèšèšãå¿
é ã ãšãã®ä»¶ã§ç¢ºä¿¡ããŸããã
è©°ãŸã⣠Library/é äžã®è¬ãããžã§ã¯ããé åžå¯Ÿè±¡ã«ãªã£ã
ããé±ã®å®è¡ãã°ã«èŠèŠãã®ãªããšã³ããªãèšé²ãããŠããŸããã
[2026-XX-XX 06:10:33] Caches: 3 skills
~/Library/Caches ã®äžã« npmããã±ãŒãžã®ãã£ãã·ã¥ãååšããããã« package.json ããã£ãã®ã§ãããããã§ã¹ãæ¢çŽ¢ã® find ã ~/Library/ ã2éå±€ãŸã§èŠãããããã£ãã·ã¥ã® package.json ãããããžã§ã¯ãããšããŠæŸã£ãŠããŸãããåœæã® is_excluded ã«ã¯ãŸã Library/* ã®é€å€ã«ãŒã«ããããŸããã§ããã
çç¶: æå³ããªããã£ãã·ã¥ãã£ã¬ã¯ããªã« autoskills ãå®è¡ãããskills-lock.json ãçæããããgitãªãã§ãªããã gitignore ãžã®è¿œèšã¯èµ·ããªãã£ããããã£ãã·ã¥ã«ããã¯ãã¡ã€ã«ãæ®ã£ãã
çŽãæ¹: é€å€ã«ãŒã«ã« Documents/*ãLibrary/*ãApplications/* ã远å ããŸãã
"$HOME_DIR"/Documents/*|"$HOME_DIR"/Library/*|"$HOME_DIR"/Applications/*) return 0 ;;
çŸåšã®ã¹ã¯ãªããã«ã¯ãã®è¡ãå«ãŸããŠãããLibrary é
äžã¯ãã¹ãŠé€å€ãããŸãã
ãã®çµéšããèšèšæ¹éãåºãŸããŸããããã³ãŒãã®ãããžã§ã¯ããããå Žæãèœåçã«å®çŸ©ãããã®ã§ã¯ãªãããã³ãŒãã®ãããžã§ã¯ãã§ãªãå Žæãé€å€ãããããšã§å®å
šåŽã«åããmacOSã®ããŒã ãã£ã¬ã¯ããªã¯æã£ã以äžã« package.json ã go.mod ãæ£åšããŠããŸããæ¢çŽ¢ç¯å²ãåºããã»ã©èª€çãå¢ãããããæ¢çŽ¢ã¯ ~ ãš ~/dev ã®2éå±€ã«çµãã€ã€ãé€å€ã«ãŒã«ã¯æåããããšãããã©ã³ã¹ãä»ã®åœ¢ã§ãã
4ã€ã®è©°ãŸãã¯å
šéšãããŒã«ã«ã§æåå®è¡ããŠãããšãã¯åºãªãã£ãåé¡ãã§ãããlaunchdã§é±1åã ãåãä»çµã¿ã¯ãäœããããããŠã1é±éæ°ã¥ããªãããã®ã³ã¹ããé«ãããã--dry-run ã§å
ã«ç¢ºèªããŠãã°ã§åŸè¿œãã§ããæ§é ã«ããŠããŸããææã®æã«éãããããžã§ã¯ãã«ææ°ã¹ãã«ãæã£ãŠããã®ã¯ããã®å°éãªå€±æã®ç©ã¿éãããã£ãŠããã§ãã
ã€ãŸãããã€ã³ã
åç« ã§ã¯ãããŒã«ã«æåå®è¡ã§ã¯åºãªãã£ãlaunchdåºæã®åé¡ãã4äºäŸåãäžããŸãããããã§ã¯åšèŸºã§ããã«èžãã 现ããçœ ãç®æ¡æžãã§ç¶²çŸ ããŸãã
plist ã® ~ ã¯ãã«ãå±éãããªã
EnvironmentVariables ã® PATH ã« ~/.nvm/versions/node/v24.13.0/bin ãšæžããŠããlaunchdã¯ãã«ãå±éãè¡ããŸããã~ ããã®ãŸãŸæååãšããŠè§£éããããã/usr/bin/npx ã ~/.nvm/... é
äžã®npxãèŠã€ãããªãç¶æ
ã«ãªããŸãã解決çã¯ããŒã ãã£ã¬ã¯ããªã®ãã«ãã¹ãæžãããšã®ã¿ã§ããå®éã®plistã§ããã«ãã¹ãèšè¿°ãããŠããã~ ã¯äžå䜿ãããŠããŸããã
RunAtLoad ã true ã«ãããš plist ããŒãçŽåŸã«æ¬çªå®è¡ãèµ°ã
launchctl load ~/Library/LaunchAgents/com.shun.autoskills-sync.plist ãå®è¡ããç¬éã«ã¹ã¯ãªãããèµ·åããŸããé€å€ã«ãŒã«ã®ç¢ºèªã --dry-run ããŸã ããŠããªã段éã§å
šãããžã§ã¯ããèµ°ãããšã«ãªããŸããååã¯å¿
ã RunAtLoad ã false ã«ããŠãããlaunchctl start com.shun.autoskills-sync ã§æåããªã¬ãŒããŠåäœã確èªããŠããæ¬¡ã®æ¥æãåŸ
ã¡ãŸããçŸåšã®plist㯠<false/> ã«åºå®ãããŠããã®ã¯ãã®çç±ããã§ãã
Weekday=0 ãæ¥æãšããlaunchdåºæã®æ°ãæ¹
macOSã®launchd plist㯠0=æ¥æã1=ææãâŠã6=åæ ã§ãããé±ã®å§ãŸãã¯ææ=1ããšããæèŠã§ Weekday ã 1 ã«èšå®ãããšãææã§ã¯ãªãç«æã«åãèšå®ã«ãªããŸããcom.shun.autoskills-sync.plist ã® Weekday ã 0 ãªã®ã¯ãæ¥ææ©æã«èµ°ã£ãŠææã®äœæ¥éå§åã«é
åžãæžãŸããããšããæå³ã§ããplistãæžããçŽåŸã¯ launchctl start ã§å³æå®è¡ãããã°ã«æ®ãã¿ã€ã ã¹ã¿ã³ãã§ææ¥ã®ãºã¬ããªãããšã確èªããŸãã
launchctl load ã ãã§ã¯å€æŽãåæ ãããªã
plistãæžãæããåŸ launchctl load ã ãåå®è¡ããŠããlaunchdã¯å€ãèšå®ãä¿æãç¶ããŸãã倿Žãåæ ããã«ã¯ launchctl unload ~/Library/LaunchAgents/com.shun.autoskills-sync.plist ãå
ã«å®è¡ãããã®åŸ launchctl load ãå®è¡ããŸãããèšå®ãçŽããã®ã«åããå€ãããªãããšããç¶æ³ã¯ã»ãŒäŸå€ãªããããåå ã§ãã
LastExitStatus=0 ããæ£åžžçµäºããããšã¯éããªã
launchctl list com.shun.autoskills-sync ã®åºåã« "LastExitStatus" = 0; ããã£ãŠããããã¯ãååã®å®è¡ãæ£åžžçµäºãããã ãã§ãªããããŸã äžåºŠãå®è¡ãããŠããªãïŒåæå€ã0ïŒãã®å ŽåããããŸãããã°ãã¡ã€ã«ã«ãšã³ããªãååšãããã©ããã§ãæ¬åœã«å®è¡ãããããã確èªããŸãããã°ã空ã®ãŸãŸ LastExitStatus=0 ãšããç¶æ³ã¯ãååããŒãçŽåŸã§ãŸã æ¥æãæ¥ãŠããªããã§ããããšãå€ãã§ãã
npx ã® -y ãš autoskills ã® --yes ã¯å¥ã®ç¢ºèªãçç¥ãã
npx -y ã¯npxãããã®ããã±ãŒãžãããŠã³ããŒãããŸããïŒããšç¢ºèªããããã³ãããçç¥ããŸããautoskills --yes ã¯autoskillsèªèº«ã®ã€ã³ã¿ã©ã¯ãã£ã確èªãçç¥ããŸããã©ã¡ããçæ¹ã ãä»ããŠããããäžæ¹ã§æ¢ãŸããŸããlaunchdã®ç¡äººå®è¡ã§ã¯ããã³ããåŸ
ã¡ãæ°žç¶ãããããæ¬¡ã®æ¥æãŸã§æ¢ãŸãç¶ããŠãèª°ãæ°ã¥ããªãææªã®ãã¿ãŒã³ã«ãªããŸããã¹ã¯ãªããã® ${DRY:---yes} ã¯DRYã空ã®ãšã --yes ãæž¡ããã©ã¡ãŒã¿å±éã§ãããåæå®è£
ã§ã¯å€æ°ä»£å
¥åã«å±éããŠããŸãåžžã«ç©ºæååãæž¡ã£ãŠããããšããããŸããã
sort -u ãçããšåäžãããžã§ã¯ãã«2åå®è¡ããã
.git æ¢çŽ¢çµè·¯ãšãããã§ã¹ããã¡ã€ã«æ¢çŽ¢çµè·¯ã¯ç¬ç«ããŠèµ°ããŸãã.git ããã package.json ããããããžã§ã¯ãã¯äž¡çµè·¯ããåããã¹ãåºãŠããŸããsort -u ããªããã°ã«ãŒããåäžãã£ã¬ã¯ããªã§2åå®è¡ãããŸãã2åç®ã¯ãã€ã³ã¹ããŒã«æžã¿ããšããŠ0ä»¶ã«ãªãã ãã§å®å®³ã¯å°ããã§ãããéä¿¡ã»æéã»ãã°ãšã³ããªãäœèšã«å¢ããŸãã
grep -qxF ã® -FïŒåºå®æååïŒãš -xïŒè¡å
šäœäžèŽïŒã®æå³
gitignore远èšã®ç¢ºèªã§ -F ãçããš .agents/ ã®å
é . ãæ£èŠè¡šçŸã¯ã€ã«ãã«ãŒãã«ãªããŸããxagents/ ãšããè¡ããã£ãŠãããããããããšå€å®ãããæ¬æ¥ã® .agents/ ãã¿ãŒã³ã远èšãããŸããã-x ãçããš # .agents/ ãšããã³ã¡ã³ãè¡ã«ãéšåãããããŠã远èšäžèŠããšèª€å€å®ããŸããã©ã¡ããæ¬ ããŠããå®éã«ã¯å¹ããŠããªãã®ã«è¿œèšããããšæã蟌ããçµæã«ãªããŸãã
ProcessType ã AdaptiveïŒçç¥æïŒã ãšçãšãã¢ãŒãã§ãã£ã³ã»ã«ããã
plist ã« ProcessType ãæžããªãã Adaptive ã«ãããšãmacOSã®çãšãå¶åŸ¡äžã§ãžã§ããå»¶æãŸãã¯äžæãããããšããããŸããã¹ãªãŒãæãã«æ¥æ06:10ã®ããªã¬ãŒãéããŠããå ŽåãBackground ãªããã®ãŠã£ã³ããŠãç¶æããŸãã Adaptive ã§ã¯æ¶ããå ŽåããããŸããé±ã«1åãããªããã£ã³ã¹ãçãšãã§é£ã¶ã³ã¹ãã¯ç¡èŠã§ããŸãããçŸåšã®plistã«ã¯ <key>ProcessType</key><string>Background</string> ãæç€ºãããŠããŸãã
å®è¡æš©éãšplistã®èµ·åæ¹åŒã®é¢ä¿
plistã® ProgramArguments ã ["/bin/zsh", "-lc", "ã¹ã¯ãªãããã¹"] ã®åœ¢ã«ãããšãzsh çµç±ã§å®è¡ããããã¹ã¯ãªããèªäœã®å®è¡æš©éïŒ+xïŒã¯äžèŠã«ãªããŸãã["/path/to/script.sh"] ã®åœ¢ïŒçŽæ¥å®è¡ïŒã«ãããš +x ãå¿
é ã§ããçŽæ¥å®è¡åœ¢åŒã§ +x ãå¿ãããšçµäºã³ãŒã 126ïŒPermission deniedïŒã§ãµã€ã¬ã³ãã«å€±æããŸããçŸåšã®plistã /bin/zsh -lc 圢åŒãªã®ã¯ããã°ã€ã³ã·ã§ã«ãšã®äºææ§ã ãã§ãªãæš©éåé¡ã®åé¿ãå
ŒããŠããŸãã
ãã¹ããã©ã¯ãã£ã¹
å®éã«åãããŠåŸãéçšã®åããåçŸæ§ã®é«ãé ã«äžŠã¹ãŸãã
1. 倿Žãããå³ --dry-run ãå
ã«èµ°ããã
bash ~/.claude/scripts/autoskills-sync.sh --dry-run
é€å€ã«ãŒã«ã远å ãããšããæ¢çŽ¢ãã¹ãå€ãããšããplistãæžãæãããšããã©ããªå€æŽã®åŸã§ããã®1è¡ãå
ã«æã¡ãŸãããã©ã€ã©ã³ã¯å¯äœçšãŒãã§äœåã§ãå®è¡ã§ããŸãã鱿¬¡ãžã§ãã®æå€§ã®åŒ±ç¹ã¯ãåé¡ã«æ°ã¥ããŸã§ã«1é±éããããããšã§ããã®æé ã²ãšã€ã§äºé²çã«æœ°ããŸããç¹ã«ãã©ã€ã©ã³å®è¡åŸã®ãã°æ«å°Ÿã« done: N projects / M skills ã®æ°åãåºãŠããããç®èŠããŸãã
2. stdout ãš stderr ãåäžãã¡ã€ã«ãžéçŽãã
plistã® StandardOutPath ãš StandardErrorPath ãåããã¹ã«åããã¹ã¯ãªããå
ã® log() 颿°ã tee -a ã§åãã¡ã€ã«ã«è¿œèšããŸãã2ãã¡ã€ã«ã«åãããšããšã©ãŒãšéåžžåºåãã©ã®é çªã§åºããããåçŸããã®ãå°é£ã«ãªããŸãã1ãã¡ã€ã«ã®æç³»åãã°ã§å
šçµç·¯ã远ããŸããcom.shun.autoskills-sync.plist ã§ã¯äž¡ãã¹ãšã ~/.claude/logs/com.shun.autoskills-sync.log ã«åããŠããŸãã
3. é€å€ã«ãŒã«ã¯ãèš±å¯ãªã¹ããã§ãªããçŠæ¢ãªã¹ããã§å®å šåŽã«åã
ãã³ãŒããããžã§ã¯ãã ãšãããå Žæã ãèš±å¯ãããèšèšãããã³ãŒããããžã§ã¯ãã§ãªããšç¢ºå®ã«ãããå ŽæãçŠæ¢ãããèšèšã®ã»ããå®å
šã§ããmacOSã®ããŒã ãã£ã¬ã¯ããªã¯ package.json ã go.mod ãæå€ãªå Žæã«æ£åšããŸããæ¢çŽ¢ç¯å²ã¯ ~ ãš ~/dev ã® maxdepth 2 ã«çµããçŠæ¢ãªã¹ãïŒis_excluded 颿°ïŒãæåããããã©ã³ã¹ãå®å®ããŸããæ°ããé åãæ¢çŽ¢å¯Ÿè±¡ã«ããããªã£ããããã®åã«å¿
ã --dry-run ã§ã©ã®ãããžã§ã¯ãã远å ããããã確èªããŸãã
4. plist ã® EnvironmentVariables ã«ã¯ãã«ãã¹ãæžã
~ ã¯plistã§ã¯å±éãããŸãããnvm管çã®Node.jsã䜿ãå ŽåãããŒãžã§ã³ãåºå®ãããã«ãã¹ã PATH ã«æžããŸããNode.jsããŒãžã§ã³ã nvm use ã§å€ãããšãã¯plistã®è©²åœç®æãåæã«æŽæ°ããŸããå¿ãããšæ¬¡ã®æ¥æã«ãnpx: command not foundãã§å
šä»¶ã¹ããããšããéããªäºæ
ãèµ·ããŸããããŒãžã§ã³åºå®ã¯ç®¡çäžã®æéãå¢ããŸããã.nvm/alias/default ããåç解決ããè€éãªèšè¿°ããæç€ºçãªã»ããä¿¡é Œã§ããŸãã
5. ãµãã·ã§ã«cdã§äœæ¥ãã£ã¬ã¯ããªãæ±æããªã
out="$(cd "$d" && npx -y autoskills ${DRY:---yes} 2>&1)"
$() ã®äžã§ cd ããããšã§ãã£ã¬ã¯ããªç§»åããµãã·ã§ã«ã«éã蟌ããŸããã«ãŒãã®æ¬¡ã®ã€ãã¬ãŒã·ã§ã³ã« cd ã®å¹æãåŒãç¶ãããŸãããgitignore確èªã® (cd "$d" && git rev-parse ...) ãåæ§ã§ãããã«ã¬ã³ããã£ã¬ã¯ããªã«äŸåããã³ãã³ããã«ãŒãã§åãããšãã¯ãã®ãã¿ãŒã³ãæãã·ã³ãã«ã§ç¢ºå®ã§ãã
6. mapfile ã䜿ãã while IFS= read -r ã§ bash 3.2 äºæãä¿ã€
macOSã®ããã©ã«ã /bin/bash ã¯ããŒãžã§ã³3.2ã§ããmapfileïŒreadarrayïŒã¯bash 4.0以éã®æ©èœã§ããwhile IFS= read -r ã«ãŒãã§ä»£æ¿ããã³ã¡ã³ãã«ããªãmapfileã䜿ããªããããæ®ããŠãããŸããå幎åŸã®èªåããæé©åããšç§°ã㊠mapfile ã«æžãæããŠåããªããªãäºæ
ãé²ããŸããIFS= ã§ãã£ãŒã«ãåå²ãç¡å¹åã -r ã§ããã¯ã¹ã©ãã·ã¥ãšã¹ã±ãŒããç¡èŠããããšã§ãã¹ããŒã¹ãæ¬åŒ§ãå«ããã¹ã§ãæ£ç¢ºã«èªã¿èŸŒããŸãã
7. 空é
åã¬ãŒãã set -u ã®æåã«å
¥ãã
if [ "${#CANDIDATES[@]}" -eq 0 ]; then
log "==== no candidates found; nothing to sync ===="; exit 0
fi
set -u ãæå¹ãªãšããbash 3.2ã§ã¯ç©ºé
åã«å¯Ÿã㊠${arr[@]} ãå±éãããšunbound variableãšã©ãŒã«ãªããŸãã${#arr[@]} ã¯ç©ºé
åã§ã 0 ãè¿ããset -u ã§ããšã©ãŒã«ãªããŸããããã®ã¬ãŒãããªããšåè£ãŒãã®ãšãããã°ã空ã®ãŸãŸçµäºã³ãŒã1ããšããç¶æ³ãçãŸããŸãããšã©ãŒã¯ç¡èšã§èµ·ãããã1é±éæ°ã¥ããªãããšã«ãªããŸãã
8. autoskills ã®åºåãã©ãŒãããå€åã«2段égrepã§å¯Ÿå¿ãã
n="$(echo "$out" | grep -oE '([0-9]+) skills installed' | grep -oE '^[0-9]+' | head -1)"
[ -z "$n" ] && n="$(echo "$out" | grep -oE 'Skills to install \(([0-9]+)\)' | grep -oE '[0-9]+' | head -1)"
[ -z "$n" ] && n=0
npmããã±ãŒãžã®åºåãã©ãŒãããã¯ããŒãžã§ã³éã§å€ãããŸãã1ãã¿ãŒã³ã ãã«å¯Ÿå¿ããŠãããšãããŒãžã§ã³ã¢ããåŸã«å
šä»¶ãã0ä»¶ïŒskipïŒããšããŠèšé²ãããå®éã«ã¯ã¹ãã«ãå
¥ã£ãŠããããšã«æ°ã¥ããªãç¶æ
ãç¶ããŸããhead -1 ãä»ããŠããã®ã¯ãè€æ°è¡ããããè¿ã£ãŠãããšãåŸç¶ã®æ°å€æ¯èŒã§ãšã©ãŒã«ãªãããšãžã®ä¿è·ã§ãã
9. gitignore 远èšã«ã¯ -qxF ã3æåã»ããã§äœ¿ã
grep -qxF "$pat" "$d/.gitignore" 2>/dev/null || echo "$pat" >> "$d/.gitignore"
-qïŒquietã»ãããã®æç¡ã ãè¿ãïŒã»-xïŒè¡å
šäœäžèŽïŒã»-FïŒåºå®æååïŒã¯3ã€æã£ãŠæ©èœããŸãã-F ããªããš .agents/ ã®å
é . ãæ£èŠè¡šçŸã¯ã€ã«ãã«ãŒãã«ãªããŸãã-x ããªããš # .agents/ ãšããã³ã¡ã³ãè¡ã«ãéšåãããããŸããã©ã¡ããæ¬ ããŠãããªãè¡ãèŠã€ãããšèª€å€å®ããŠã¹ãããããŸãã¯ãå¥ã®è¡ã«ãããããŠéè€è¿œèšãã®ãããããèµ·ããŸãã
10. ãã°ã®å®äºè¡ã«ä»¶æ°ãµããªãŒãå«ãã
==== done: 2 projects / 20 skills, 1 excluded ====
鱿¬¡å®è¡ã®ãããéå»ãã°ãšã®æ¯èŒããããããã©ãŒããããåºå®ããŸãããå
é±ã¯20ä»¶ãä»é±ã¯0ä»¶ããšããå€åãããã«æ€ç¥ã§ããŸãããäœãããããããã«æ°ã¥ãæ©äŒãé±ã«1åãããªãããããã°ã®æ
å ±å¯åºŠã¯é«ãããŠãããŸããtail -5 ~/.claude/logs/com.shun.autoskills-sync.log ã§çŽè¿ã®å®äºè¡ã ã確èªããç¿æ
£ãäœããšã鱿¬¡ç¢ºèªã30ç§ã§çµãããŸãã
11. RunAtLoad ã false ã«ããŠåå㯠launchctl start ã§ç¢ºèªãã
launchctl load ~/Library/LaunchAgents/com.shun.autoskills-sync.plist
launchctl start com.shun.autoskills-sync
tail -20 ~/.claude/logs/com.shun.autoskills-sync.log
RunAtLoad=false ã®ãŸãŸplistãããŒãããlaunchctl start ã§æåããªã¬ãŒããããšã§ãæ¥æãŸã§åŸ
ããã«åäœç¢ºèªããšãæå³ããªã峿å®è¡ã®é²æ¢ããäž¡ç«ããŸããåå確èªã§ãã°ã« done: è¡ãåºãŠããã°èšå®ã¯æ£ããæ©èœããŠããŸãã
12. ProcessType ã Background ã«æç€ºãã
<key>ProcessType</key>
<string>Background</string>
çç¥ïŒAdaptiveïŒã«ãããšmacOSããä»ã¯åŸåãã«ããŠãããããšå€æããå Žåã«ãžã§ããã¹ããããããå¯èœæ§ããããŸããé±ã«1åãããªããã£ã³ã¹ãçãšãã§é£ã¶ã³ã¹ãã¯ç¡èŠã§ããŸãããBackground ã®æç€ºã¯job schedulingã«ãããä¿éºã§ãã
13. plist ã倿Žããã unload â load ã®é åºãå®ã
launchctl unload ~/Library/LaunchAgents/com.shun.autoskills-sync.plist
launchctl load ~/Library/LaunchAgents/com.shun.autoskills-sync.plist
load ã ãåå®è¡ããŠãå€ãèšå®ãæ®ããŸãããèšå®ãçŽããã®ã«åããå€ãããªããã¯ã»ãŒäŸå€ãªãunloadãå¿ããŠããããšãåå ã§ããã»ããã§1ã³ãã³ãåããŠããããæé ãã³ã¡ã³ãã«æ®ããã®ã©ã¡ããã§é²ããŸãã
14. Node.js ããŒãžã§ã³ãå€ããã plist ãæŽæ°ãã
nvmã§Node.jsã®ããŒãžã§ã³ãåãæ¿ãããšããplistã® EnvironmentVariables ã® PATH å
ã®ããŒãžã§ã³çªå·ãæŽæ°ãå¿
èŠã§ãããããå¿ãããšå€ãããŒãžã§ã³ã®npxã䜿ããç¶ããŸããnvm use ãå®è¡ããã¿ã€ãã³ã°ã§plistã®æŽæ°ãç¿æ
£åãããã䜿çšããŒãžã§ã³ãã³ã¡ã³ãã«æžããŠãããŠå®æç¢ºèªããŸããèªååã®ããã®èªååãéãã«å€ããŸãŸåãç¶ããã®ã¯ã誰ã§ãèžãçœ ã§ãã
15. set -e ããããŠä»ããªãèšèšæå³ãææ¡ããŠãã
ã¹ã¯ãªããåé ã« set -e ããªãçç±ã¯ãis_excluded 颿°ã return 1ïŒé€å€ããªã=falseïŒãè¿ãããšãã«ã·ã§ã«ããã³ãã³ãã倱æããããšè§£éããããã§ããset -e ãæå¹ã ãš if is_excluded "$d"; then ã®æ¡ä»¶è©äŸ¡äžã« return 1 ãåºãæç¹ã§ã¹ã¯ãªããå
šäœãçµäºããã±ãŒã¹ããããŸããæ¡ä»¶åå²ãå«ã颿°ãå€çšããå Žåãset -e ããæç€ºç㪠|| { log "error: ..."; exit 1; } ã®ã»ãã誀çãå°ãªãã§ããset -u ãš pipefail ã ããæ®ããçŸåšã®èšå®ã¯ããã®å€æã®çµæã§ãã
ãŸãšã
ã¹ãã«ãèç©ãããŠãå±ããªããã°æå³ããªãââãã®åçŽãªåé¡ãã鱿¬¡ã®èªåé åžãšããä»çµã¿ãçã¿ãŸããã
autoskills-sync.sh ã®88è¡ã¯ããªãã©ã€ã³ç¢ºèªã»2çµè·¯ã®æ¢çŽ¢ã»5ã«ããŽãªã®é€å€ã»npxå®è¡ã»gitignore远èšã»ä»¶æ°ãã°ãšãã6ã¹ããããäžæ¬ã«ç¹ãã ãã®ã§ããplistã¯ãããæ¯é±æ¥æ06:10ã«èªåèµ·åããŸããæåã§äœãããªããŠããææã®æã«éãããããžã§ã¯ãã«ã¯ææ°ã¹ãã«ãæã£ãŠããç¶æ
ãäœãããã®ãæå°éã®ä»æãã§ãã
æ¬èšäºã§åãäžããã€ãŸãããã€ã³ããšãã¹ããã©ã¯ãã£ã¹ã®æ ¹åºã«ã¯å
±éãã1ã€ã®èšèšææ³ããããŸãããé±1åããåããªããžã§ãã¯ãåé¡ã«æ°ã¥ããŸã§ã«1é±éãããããã ãã --dry-run ã§å
ã«ç¢ºèªãããã°ã«ä»¶æ°ãµããªãŒãæ®ããæåã§ãåãã¹ã¯ãªãããèµ°ãããããèšèšã«ããããã®3ç¹ãæã£ãŠããã°ãlaunchdã®åšèŸºã§èµ·ãã倧åã®äºæ
ã¯äºåã«é²ããŸãã
ç°å¢ãè²ãŠããšã¯ãã¹ãã«ãèç©ããããšã ãã§ã¯ãããŸããããã®ã¹ãã«ãå¿ èŠãªå Žæã«å±ãä»çµã¿ãäœãããã®ä»çµã¿èªäœãå®å šã«éçšããèšèšãè²ãŠãããšã§ããæå120äžãæ¯ããç°å¢ã¯ãããããå°å³ãªé±æ¬¡ãžã§ãã®ç©ã¿éãã§æãç«ã£ãŠããŸãã
ä»çµã¿ã®å šäœåã»æ120äžã®å èš³ã»30æ¥æé ã¯æænoteã«ãŸãšããŠããŸãã
ð Claude CodeèªåŸç°å¢ã§ãå®éã©ã皌ãã â ä»çµã¿ã»å®äŸã»å§ãæ¹ã»ãµããŒã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- AIã§ãå¯ãŠãŠãåãä»çµã¿ããäœã£ãŠæ120äžã«ãã話㯠noteã®ææèšäº ã«ð°
- OSS: github.com/bokuwalily ð
- ææ°æ å ±ã»ãåãåãã㯠X @bokuwalily ãžð
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ