monol-plugin-scout 2.1.3 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +244 -0
  3. package/monol-plugin-scout-pkg/CLAUDE.md +125 -8
  4. package/monol-plugin-scout-pkg/api/mock/categories.json +81 -0
  5. package/monol-plugin-scout-pkg/api/mock/insights.json +111 -0
  6. package/monol-plugin-scout-pkg/api/mock/marketplace.json +501 -0
  7. package/monol-plugin-scout-pkg/api/mock/trending.json +96 -0
  8. package/monol-plugin-scout-pkg/commands/console.md +79 -0
  9. package/monol-plugin-scout-pkg/commands/frequency.md +32 -0
  10. package/monol-plugin-scout-pkg/commands/install.md +32 -0
  11. package/monol-plugin-scout-pkg/commands/priority.md +30 -0
  12. package/monol-plugin-scout-pkg/commands/quiet.md +32 -0
  13. package/monol-plugin-scout-pkg/commands/schedule.md +32 -0
  14. package/monol-plugin-scout-pkg/commands/scout.md +8 -5
  15. package/monol-plugin-scout-pkg/commands/skills.md +160 -0
  16. package/monol-plugin-scout-pkg/commands/team.md +32 -0
  17. package/monol-plugin-scout-pkg/commands/timing.md +32 -0
  18. package/monol-plugin-scout-pkg/commands/trust.md +85 -0
  19. package/monol-plugin-scout-pkg/commands/trusted-install.md +98 -0
  20. package/monol-plugin-scout-pkg/commands/uninstall.md +31 -0
  21. package/monol-plugin-scout-pkg/config.yaml +57 -0
  22. package/monol-plugin-scout-pkg/data/.cache/676d5ab664292155e5f509c69d4edae1 +10 -0
  23. package/monol-plugin-scout-pkg/data/.session +8 -0
  24. package/monol-plugin-scout-pkg/data/analytics.json +102 -0
  25. package/monol-plugin-scout-pkg/data/history.json +67 -11
  26. package/monol-plugin-scout-pkg/data/logs/scout.log +1 -0
  27. package/monol-plugin-scout-pkg/data/schedules.json +17 -0
  28. package/monol-plugin-scout-pkg/data/team.json +53 -0
  29. package/monol-plugin-scout-pkg/data/usage.json +100 -7
  30. package/monol-plugin-scout-pkg/docs/ARCHITECTURE.md +201 -0
  31. package/monol-plugin-scout-pkg/hooks/generate-insights.sh +102 -0
  32. package/monol-plugin-scout-pkg/hooks/hooks.json +36 -1
  33. package/monol-plugin-scout-pkg/hooks/on-session-end.sh +136 -0
  34. package/monol-plugin-scout-pkg/hooks/on-session-start.sh +43 -0
  35. package/monol-plugin-scout-pkg/hooks/on-stop.md +79 -0
  36. package/monol-plugin-scout-pkg/hooks/open-console.sh +111 -0
  37. package/monol-plugin-scout-pkg/hooks/open-dashboard.sh +61 -0
  38. package/monol-plugin-scout-pkg/hooks/track-usage.sh +59 -0
  39. package/monol-plugin-scout-pkg/lib/ai-recommender.sh +505 -0
  40. package/monol-plugin-scout-pkg/lib/cache.sh +194 -0
  41. package/monol-plugin-scout-pkg/lib/data-validator.sh +360 -0
  42. package/monol-plugin-scout-pkg/lib/error-handler.sh +296 -0
  43. package/monol-plugin-scout-pkg/lib/logger.sh +263 -0
  44. package/monol-plugin-scout-pkg/lib/plugin-manager.sh +239 -0
  45. package/monol-plugin-scout-pkg/lib/priority-scorer.sh +262 -0
  46. package/monol-plugin-scout-pkg/lib/profile-learner.sh +339 -0
  47. package/monol-plugin-scout-pkg/lib/project-analyzer.sh +281 -0
  48. package/monol-plugin-scout-pkg/lib/recommendation-controller.sh +290 -0
  49. package/monol-plugin-scout-pkg/lib/rejection-learner.sh +232 -0
  50. package/monol-plugin-scout-pkg/lib/scheduler.sh +275 -0
  51. package/monol-plugin-scout-pkg/lib/skill-scout.sh +729 -0
  52. package/monol-plugin-scout-pkg/lib/sync.sh +221 -0
  53. package/monol-plugin-scout-pkg/lib/team-learner.sh +450 -0
  54. package/monol-plugin-scout-pkg/lib/team-manager.sh +369 -0
  55. package/monol-plugin-scout-pkg/lib/trend-learner.sh +428 -0
  56. package/monol-plugin-scout-pkg/lib/trust-manager.sh +261 -0
  57. package/monol-plugin-scout-pkg/lib/trusted-installer.sh +738 -0
  58. package/monol-plugin-scout-pkg/plugin.json +3 -2
  59. package/monol-plugin-scout-pkg/skills/audit.md +6 -0
  60. package/monol-plugin-scout-pkg/skills/cleanup.md +6 -0
  61. package/monol-plugin-scout-pkg/skills/compare.md +6 -0
  62. package/monol-plugin-scout-pkg/skills/console.md +315 -0
  63. package/monol-plugin-scout-pkg/skills/explore.md +6 -0
  64. package/monol-plugin-scout-pkg/skills/fork.md +6 -0
  65. package/monol-plugin-scout-pkg/skills/frequency.md +93 -0
  66. package/monol-plugin-scout-pkg/skills/install.md +127 -0
  67. package/monol-plugin-scout-pkg/skills/priority.md +77 -0
  68. package/monol-plugin-scout-pkg/skills/quiet.md +73 -0
  69. package/monol-plugin-scout-pkg/skills/schedule.md +95 -0
  70. package/monol-plugin-scout-pkg/skills/scout.md +27 -17
  71. package/monol-plugin-scout-pkg/skills/skills.md +230 -0
  72. package/monol-plugin-scout-pkg/skills/team.md +117 -0
  73. package/monol-plugin-scout-pkg/skills/timing.md +97 -0
  74. package/monol-plugin-scout-pkg/skills/trust.md +120 -0
  75. package/monol-plugin-scout-pkg/skills/trusted-install.md +264 -0
  76. package/monol-plugin-scout-pkg/skills/uninstall.md +100 -0
  77. package/monol-plugin-scout-pkg/web/components/activity-chart.js +208 -0
  78. package/monol-plugin-scout-pkg/web/components/index.js +27 -0
  79. package/monol-plugin-scout-pkg/web/components/insight-card.js +365 -0
  80. package/monol-plugin-scout-pkg/web/components/overview.js +154 -0
  81. package/monol-plugin-scout-pkg/web/components/plugin-list.js +242 -0
  82. package/monol-plugin-scout-pkg/web/components/stats-card.js +126 -0
  83. package/monol-plugin-scout-pkg/web/components/team-list.js +346 -0
  84. package/monol-plugin-scout-pkg/web/console.html +2098 -0
  85. package/monol-plugin-scout-pkg/web/dashboard.html +2106 -0
  86. package/monol-plugin-scout-pkg/web/manifest.json +29 -0
  87. package/package.json +1 -1
@@ -0,0 +1,232 @@
1
+ #!/bin/bash
2
+ # Plugin Scout - 거절 학습 시스템
3
+ # 플러그인 거절 패턴을 학습하여 추천 품질 향상
4
+
5
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
6
+ DATA_DIR="$PLUGIN_ROOT/data"
7
+ HISTORY_FILE="$DATA_DIR/history.json"
8
+
9
+ # 거절 기록 추가
10
+ record_rejection() {
11
+ local plugin_name="$1"
12
+ local reason="$2"
13
+ local category="$3"
14
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
15
+ local date_only=$(date +"%Y-%m-%d")
16
+
17
+ # history.json 없으면 생성
18
+ if [ ! -f "$HISTORY_FILE" ]; then
19
+ echo '{
20
+ "version": "2.0.0",
21
+ "lastUpdated": "'$timestamp'",
22
+ "declined": {},
23
+ "installed": {},
24
+ "preferences": {
25
+ "favoriteCategories": [],
26
+ "blockedCategories": [],
27
+ "autoRecommend": true,
28
+ "quietMode": false
29
+ },
30
+ "rejectionPatterns": {
31
+ "byCategory": {},
32
+ "byReason": {}
33
+ }
34
+ }' > "$HISTORY_FILE"
35
+ fi
36
+
37
+ # jq로 거절 기록 업데이트
38
+ if command -v jq &> /dev/null; then
39
+ local current_count=$(jq -r ".declined[\"$plugin_name\"].count // 0" "$HISTORY_FILE")
40
+ local new_count=$((current_count + 1))
41
+
42
+ # 거절 기록 업데이트
43
+ jq --arg plugin "$plugin_name" \
44
+ --arg reason "$reason" \
45
+ --arg category "$category" \
46
+ --arg date "$date_only" \
47
+ --arg ts "$timestamp" \
48
+ --argjson count "$new_count" \
49
+ '
50
+ .lastUpdated = $ts |
51
+ .declined[$plugin].count = $count |
52
+ .declined[$plugin].lastDeclined = $date |
53
+ .declined[$plugin].reason = $reason |
54
+ .declined[$plugin].category = $category |
55
+ .declined[$plugin].declinedAt = ((.declined[$plugin].declinedAt // []) + [$ts]) |
56
+ .rejectionPatterns.byCategory[$category] = ((.rejectionPatterns.byCategory[$category] // 0) + 1) |
57
+ .rejectionPatterns.byReason[$reason] = ((.rejectionPatterns.byReason[$reason] // 0) + 1)
58
+ ' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
59
+
60
+ echo "Recorded rejection: $plugin_name ($reason)"
61
+ else
62
+ echo "Warning: jq not installed, cannot record rejection"
63
+ fi
64
+ }
65
+
66
+ # 플러그인이 추천 가능한지 확인
67
+ can_recommend() {
68
+ local plugin_name="$1"
69
+ local cooldown_days="${2:-30}" # 기본 30일 쿨다운
70
+
71
+ if [ ! -f "$HISTORY_FILE" ]; then
72
+ echo "true"
73
+ return
74
+ fi
75
+
76
+ if ! command -v jq &> /dev/null; then
77
+ echo "true"
78
+ return
79
+ fi
80
+
81
+ local declined=$(jq -r ".declined[\"$plugin_name\"]" "$HISTORY_FILE")
82
+
83
+ if [ "$declined" = "null" ]; then
84
+ echo "true"
85
+ return
86
+ fi
87
+
88
+ local count=$(echo "$declined" | jq -r '.count // 0')
89
+ local last_declined=$(echo "$declined" | jq -r '.lastDeclined // ""')
90
+
91
+ # 3회 이상 거절 시 영구 차단
92
+ if [ "$count" -ge 3 ]; then
93
+ echo "blocked:too_many_rejections"
94
+ return
95
+ fi
96
+
97
+ # 쿨다운 기간 체크
98
+ if [ -n "$last_declined" ]; then
99
+ local last_ts=$(date -j -f "%Y-%m-%d" "$last_declined" "+%s" 2>/dev/null || date -d "$last_declined" "+%s" 2>/dev/null)
100
+ local now_ts=$(date "+%s")
101
+ local diff_days=$(( (now_ts - last_ts) / 86400 ))
102
+
103
+ if [ "$diff_days" -lt "$cooldown_days" ]; then
104
+ echo "cooldown:$((cooldown_days - diff_days))_days_remaining"
105
+ return
106
+ fi
107
+ fi
108
+
109
+ echo "true"
110
+ }
111
+
112
+ # 카테고리가 차단되었는지 확인
113
+ is_category_blocked() {
114
+ local category="$1"
115
+ local threshold="${2:-5}" # 기본 5회 이상 거절 시 차단
116
+
117
+ if [ ! -f "$HISTORY_FILE" ]; then
118
+ echo "false"
119
+ return
120
+ fi
121
+
122
+ if ! command -v jq &> /dev/null; then
123
+ echo "false"
124
+ return
125
+ fi
126
+
127
+ local count=$(jq -r ".rejectionPatterns.byCategory[\"$category\"] // 0" "$HISTORY_FILE")
128
+
129
+ if [ "$count" -ge "$threshold" ]; then
130
+ echo "true"
131
+ else
132
+ echo "false"
133
+ fi
134
+ }
135
+
136
+ # 거절 통계 출력
137
+ get_rejection_stats() {
138
+ if [ ! -f "$HISTORY_FILE" ]; then
139
+ echo "No rejection history"
140
+ return
141
+ fi
142
+
143
+ if ! command -v jq &> /dev/null; then
144
+ echo "jq required for stats"
145
+ return
146
+ fi
147
+
148
+ echo "=== Rejection Statistics ==="
149
+ echo ""
150
+ echo "By Plugin:"
151
+ jq -r '.declined | to_entries | sort_by(-.value.count) | .[:5] | .[] | " \(.key): \(.value.count) times (\(.value.reason))"' "$HISTORY_FILE"
152
+ echo ""
153
+ echo "By Category:"
154
+ jq -r '.rejectionPatterns.byCategory | to_entries | sort_by(-.value) | .[] | " \(.key): \(.value) rejections"' "$HISTORY_FILE"
155
+ echo ""
156
+ echo "By Reason:"
157
+ jq -r '.rejectionPatterns.byReason | to_entries | sort_by(-.value) | .[] | " \(.key): \(.value) times"' "$HISTORY_FILE"
158
+ }
159
+
160
+ # 거절 초기화 (특정 플러그인)
161
+ reset_rejection() {
162
+ local plugin_name="$1"
163
+
164
+ if [ ! -f "$HISTORY_FILE" ]; then
165
+ echo "No history file"
166
+ return
167
+ fi
168
+
169
+ if ! command -v jq &> /dev/null; then
170
+ echo "jq required"
171
+ return
172
+ fi
173
+
174
+ jq --arg plugin "$plugin_name" 'del(.declined[$plugin])' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
175
+ echo "Reset rejection for: $plugin_name"
176
+ }
177
+
178
+ # 추천 필터링 (거절 학습 기반)
179
+ filter_recommendations() {
180
+ local plugins_json="$1" # JSON array of plugin names
181
+
182
+ if [ ! -f "$HISTORY_FILE" ]; then
183
+ echo "$plugins_json"
184
+ return
185
+ fi
186
+
187
+ if ! command -v jq &> /dev/null; then
188
+ echo "$plugins_json"
189
+ return
190
+ fi
191
+
192
+ # 필터링 로직
193
+ echo "$plugins_json" | jq -r '.[]' | while read plugin; do
194
+ local status=$(can_recommend "$plugin")
195
+ if [ "$status" = "true" ]; then
196
+ echo "$plugin"
197
+ fi
198
+ done | jq -R -s -c 'split("\n") | map(select(. != ""))'
199
+ }
200
+
201
+ # CLI 인터페이스
202
+ case "$1" in
203
+ record)
204
+ record_rejection "$2" "$3" "$4"
205
+ ;;
206
+ check)
207
+ can_recommend "$2" "$3"
208
+ ;;
209
+ category-blocked)
210
+ is_category_blocked "$2" "$3"
211
+ ;;
212
+ stats)
213
+ get_rejection_stats
214
+ ;;
215
+ reset)
216
+ reset_rejection "$2"
217
+ ;;
218
+ filter)
219
+ filter_recommendations "$2"
220
+ ;;
221
+ *)
222
+ echo "Usage: $0 {record|check|category-blocked|stats|reset|filter}"
223
+ echo ""
224
+ echo "Commands:"
225
+ echo " record <plugin> <reason> <category> - Record a rejection"
226
+ echo " check <plugin> [cooldown_days] - Check if plugin can be recommended"
227
+ echo " category-blocked <category> [threshold] - Check if category is blocked"
228
+ echo " stats - Show rejection statistics"
229
+ echo " reset <plugin> - Reset rejection for a plugin"
230
+ echo " filter <json_array> - Filter recommendations"
231
+ ;;
232
+ esac
@@ -0,0 +1,275 @@
1
+ #!/bin/bash
2
+ # Plugin Scout - 스케줄러
3
+ # 플러그인 체크, 알림, 정기 작업 예약
4
+
5
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
6
+ DATA_DIR="$PLUGIN_ROOT/data"
7
+ SCHEDULE_FILE="$DATA_DIR/schedules.json"
8
+
9
+ # 스케줄 파일 초기화
10
+ init_schedules() {
11
+ if [ ! -f "$SCHEDULE_FILE" ]; then
12
+ cat > "$SCHEDULE_FILE" << 'EOF'
13
+ {
14
+ "version": "1.0.0",
15
+ "tasks": [],
16
+ "lastRun": null
17
+ }
18
+ EOF
19
+ fi
20
+ }
21
+
22
+ # 스케줄 추가
23
+ add_schedule() {
24
+ local task_type="$1" # check-updates, cleanup, audit, remind
25
+ local interval="$2" # daily, weekly, monthly, once
26
+ local target="$3" # plugin name, category, or "all"
27
+ local message="$4" # reminder message
28
+ local now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
29
+
30
+ init_schedules
31
+
32
+ if ! command -v jq &> /dev/null; then
33
+ echo "Error: jq required"
34
+ return 1
35
+ fi
36
+
37
+ # 다음 실행 시간 계산
38
+ local next_run
39
+ case "$interval" in
40
+ daily)
41
+ next_run=$(date -v+1d -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -d "+1 day" -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
42
+ ;;
43
+ weekly)
44
+ next_run=$(date -v+7d -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -d "+7 days" -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
45
+ ;;
46
+ monthly)
47
+ next_run=$(date -v+1m -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -d "+1 month" -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
48
+ ;;
49
+ once)
50
+ next_run="$now"
51
+ ;;
52
+ *)
53
+ next_run=$(date -v+1d -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -d "+1 day" -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
54
+ ;;
55
+ esac
56
+
57
+ local task_id=$(date +%s%N | md5 2>/dev/null | head -c 8 || echo "$RANDOM")
58
+
59
+ jq --arg id "$task_id" --arg type "$task_type" --arg interval "$interval" \
60
+ --arg target "${target:-all}" --arg msg "${message:-}" \
61
+ --arg created "$now" --arg next "$next_run" '
62
+ .tasks += [{
63
+ "id": $id,
64
+ "type": $type,
65
+ "interval": $interval,
66
+ "target": $target,
67
+ "message": $msg,
68
+ "createdAt": $created,
69
+ "nextRun": $next,
70
+ "lastRun": null,
71
+ "enabled": true
72
+ }]
73
+ ' "$SCHEDULE_FILE" > "$SCHEDULE_FILE.tmp" && mv "$SCHEDULE_FILE.tmp" "$SCHEDULE_FILE"
74
+
75
+ echo "Schedule added: $task_type ($interval) - ID: $task_id"
76
+ }
77
+
78
+ # 스케줄 삭제
79
+ remove_schedule() {
80
+ local task_id="$1"
81
+
82
+ init_schedules
83
+
84
+ jq --arg id "$task_id" '.tasks = [.tasks[] | select(.id != $id)]' \
85
+ "$SCHEDULE_FILE" > "$SCHEDULE_FILE.tmp" && mv "$SCHEDULE_FILE.tmp" "$SCHEDULE_FILE"
86
+
87
+ echo "Schedule removed: $task_id"
88
+ }
89
+
90
+ # 스케줄 목록
91
+ list_schedules() {
92
+ init_schedules
93
+
94
+ echo "=== Scheduled Tasks ==="
95
+ echo ""
96
+
97
+ jq -r '.tasks[] | "[\(.id)] \(.type) (\(.interval)) - Target: \(.target) - Next: \(.nextRun | split("T")[0]) - \(if .enabled then "enabled" else "disabled" end)"' "$SCHEDULE_FILE"
98
+ }
99
+
100
+ # 스케줄 활성화/비활성화
101
+ toggle_schedule() {
102
+ local task_id="$1"
103
+
104
+ init_schedules
105
+
106
+ jq --arg id "$task_id" '
107
+ .tasks = [.tasks[] | if .id == $id then .enabled = (.enabled | not) else . end]
108
+ ' "$SCHEDULE_FILE" > "$SCHEDULE_FILE.tmp" && mv "$SCHEDULE_FILE.tmp" "$SCHEDULE_FILE"
109
+
110
+ echo "Schedule toggled: $task_id"
111
+ }
112
+
113
+ # 실행 대기 중인 스케줄 확인
114
+ check_due() {
115
+ init_schedules
116
+
117
+ local now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
118
+
119
+ jq -r --arg now "$now" '
120
+ .tasks[] | select(.enabled == true and .nextRun <= $now) | .id
121
+ ' "$SCHEDULE_FILE"
122
+ }
123
+
124
+ # 스케줄 실행
125
+ run_schedule() {
126
+ local task_id="$1"
127
+
128
+ init_schedules
129
+
130
+ local task=$(jq -r --arg id "$task_id" '.tasks[] | select(.id == $id)' "$SCHEDULE_FILE")
131
+
132
+ if [ -z "$task" ]; then
133
+ echo "Task not found: $task_id"
134
+ return 1
135
+ fi
136
+
137
+ local task_type=$(echo "$task" | jq -r '.type')
138
+ local target=$(echo "$task" | jq -r '.target')
139
+ local message=$(echo "$task" | jq -r '.message')
140
+ local interval=$(echo "$task" | jq -r '.interval')
141
+
142
+ echo "Running: $task_type ($target)"
143
+
144
+ # 작업 유형별 실행
145
+ case "$task_type" in
146
+ check-updates)
147
+ echo "Checking for plugin updates..."
148
+ # bash "$PLUGIN_ROOT/lib/plugin-manager.sh" check-updates "$target"
149
+ ;;
150
+ cleanup)
151
+ echo "Running cleanup..."
152
+ bash "$PLUGIN_ROOT/lib/recommendation-controller.sh" cleanup
153
+ bash "$PLUGIN_ROOT/lib/cache.sh" cleanup
154
+ ;;
155
+ audit)
156
+ echo "Running audit..."
157
+ # 보안 감사 실행
158
+ ;;
159
+ remind)
160
+ echo "Reminder: $message"
161
+ ;;
162
+ *)
163
+ echo "Unknown task type: $task_type"
164
+ ;;
165
+ esac
166
+
167
+ # 다음 실행 시간 업데이트
168
+ local now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
169
+ local next_run
170
+
171
+ case "$interval" in
172
+ daily)
173
+ next_run=$(date -v+1d -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -d "+1 day" -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
174
+ ;;
175
+ weekly)
176
+ next_run=$(date -v+7d -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -d "+7 days" -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
177
+ ;;
178
+ monthly)
179
+ next_run=$(date -v+1m -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -d "+1 month" -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
180
+ ;;
181
+ once)
182
+ # 일회성 작업은 비활성화
183
+ jq --arg id "$task_id" '.tasks = [.tasks[] | if .id == $id then .enabled = false else . end]' \
184
+ "$SCHEDULE_FILE" > "$SCHEDULE_FILE.tmp" && mv "$SCHEDULE_FILE.tmp" "$SCHEDULE_FILE"
185
+ next_run="null"
186
+ ;;
187
+ esac
188
+
189
+ # 실행 기록 업데이트
190
+ if [ "$next_run" != "null" ]; then
191
+ jq --arg id "$task_id" --arg last "$now" --arg next "$next_run" '
192
+ .tasks = [.tasks[] | if .id == $id then .lastRun = $last | .nextRun = $next else . end] |
193
+ .lastRun = $last
194
+ ' "$SCHEDULE_FILE" > "$SCHEDULE_FILE.tmp" && mv "$SCHEDULE_FILE.tmp" "$SCHEDULE_FILE"
195
+ fi
196
+ }
197
+
198
+ # 대기 중인 모든 스케줄 실행
199
+ run_due() {
200
+ local due_tasks=$(check_due)
201
+
202
+ if [ -z "$due_tasks" ]; then
203
+ echo "No tasks due"
204
+ return
205
+ fi
206
+
207
+ echo "$due_tasks" | while read task_id; do
208
+ run_schedule "$task_id"
209
+ echo ""
210
+ done
211
+ }
212
+
213
+ # 기본 스케줄 설정
214
+ setup_defaults() {
215
+ init_schedules
216
+
217
+ # 기본 스케줄이 없으면 추가
218
+ local has_cleanup=$(jq -r '.tasks[] | select(.type == "cleanup") | .id' "$SCHEDULE_FILE" | head -1)
219
+
220
+ if [ -z "$has_cleanup" ]; then
221
+ add_schedule "cleanup" "weekly" "all" "Weekly cleanup task"
222
+ fi
223
+
224
+ echo "Default schedules configured"
225
+ }
226
+
227
+ # CLI 인터페이스
228
+ case "$1" in
229
+ add)
230
+ add_schedule "$2" "$3" "$4" "$5"
231
+ ;;
232
+ remove)
233
+ remove_schedule "$2"
234
+ ;;
235
+ list)
236
+ list_schedules
237
+ ;;
238
+ toggle)
239
+ toggle_schedule "$2"
240
+ ;;
241
+ check)
242
+ check_due
243
+ ;;
244
+ run)
245
+ run_schedule "$2"
246
+ ;;
247
+ run-due)
248
+ run_due
249
+ ;;
250
+ setup)
251
+ setup_defaults
252
+ ;;
253
+ *)
254
+ echo "Usage: $0 {add|remove|list|toggle|check|run|run-due|setup}"
255
+ echo ""
256
+ echo "Commands:"
257
+ echo " add <type> <interval> [target] [message] - Add schedule"
258
+ echo " remove <id> - Remove schedule"
259
+ echo " list - List all schedules"
260
+ echo " toggle <id> - Enable/disable schedule"
261
+ echo " check - Check for due tasks"
262
+ echo " run <id> - Run specific task"
263
+ echo " run-due - Run all due tasks"
264
+ echo " setup - Setup default schedules"
265
+ echo ""
266
+ echo "Task Types:"
267
+ echo " check-updates - Check for plugin updates"
268
+ echo " cleanup - Run cleanup tasks"
269
+ echo " audit - Run security audit"
270
+ echo " remind - Show reminder message"
271
+ echo ""
272
+ echo "Intervals:"
273
+ echo " daily, weekly, monthly, once"
274
+ ;;
275
+ esac