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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +244 -0
- package/monol-plugin-scout-pkg/CLAUDE.md +125 -8
- package/monol-plugin-scout-pkg/api/mock/categories.json +81 -0
- package/monol-plugin-scout-pkg/api/mock/insights.json +111 -0
- package/monol-plugin-scout-pkg/api/mock/marketplace.json +501 -0
- package/monol-plugin-scout-pkg/api/mock/trending.json +96 -0
- package/monol-plugin-scout-pkg/commands/console.md +79 -0
- package/monol-plugin-scout-pkg/commands/frequency.md +32 -0
- package/monol-plugin-scout-pkg/commands/install.md +32 -0
- package/monol-plugin-scout-pkg/commands/priority.md +30 -0
- package/monol-plugin-scout-pkg/commands/quiet.md +32 -0
- package/monol-plugin-scout-pkg/commands/schedule.md +32 -0
- package/monol-plugin-scout-pkg/commands/scout.md +8 -5
- package/monol-plugin-scout-pkg/commands/skills.md +160 -0
- package/monol-plugin-scout-pkg/commands/team.md +32 -0
- package/monol-plugin-scout-pkg/commands/timing.md +32 -0
- package/monol-plugin-scout-pkg/commands/trust.md +85 -0
- package/monol-plugin-scout-pkg/commands/trusted-install.md +98 -0
- package/monol-plugin-scout-pkg/commands/uninstall.md +31 -0
- package/monol-plugin-scout-pkg/config.yaml +57 -0
- package/monol-plugin-scout-pkg/data/.cache/676d5ab664292155e5f509c69d4edae1 +10 -0
- package/monol-plugin-scout-pkg/data/.session +8 -0
- package/monol-plugin-scout-pkg/data/analytics.json +102 -0
- package/monol-plugin-scout-pkg/data/history.json +67 -11
- package/monol-plugin-scout-pkg/data/logs/scout.log +1 -0
- package/monol-plugin-scout-pkg/data/schedules.json +17 -0
- package/monol-plugin-scout-pkg/data/team.json +53 -0
- package/monol-plugin-scout-pkg/data/usage.json +100 -7
- package/monol-plugin-scout-pkg/docs/ARCHITECTURE.md +201 -0
- package/monol-plugin-scout-pkg/hooks/generate-insights.sh +102 -0
- package/monol-plugin-scout-pkg/hooks/hooks.json +36 -1
- package/monol-plugin-scout-pkg/hooks/on-session-end.sh +136 -0
- package/monol-plugin-scout-pkg/hooks/on-session-start.sh +43 -0
- package/monol-plugin-scout-pkg/hooks/on-stop.md +79 -0
- package/monol-plugin-scout-pkg/hooks/open-console.sh +111 -0
- package/monol-plugin-scout-pkg/hooks/open-dashboard.sh +61 -0
- package/monol-plugin-scout-pkg/hooks/track-usage.sh +59 -0
- package/monol-plugin-scout-pkg/lib/ai-recommender.sh +505 -0
- package/monol-plugin-scout-pkg/lib/cache.sh +194 -0
- package/monol-plugin-scout-pkg/lib/data-validator.sh +360 -0
- package/monol-plugin-scout-pkg/lib/error-handler.sh +296 -0
- package/monol-plugin-scout-pkg/lib/logger.sh +263 -0
- package/monol-plugin-scout-pkg/lib/plugin-manager.sh +239 -0
- package/monol-plugin-scout-pkg/lib/priority-scorer.sh +262 -0
- package/monol-plugin-scout-pkg/lib/profile-learner.sh +339 -0
- package/monol-plugin-scout-pkg/lib/project-analyzer.sh +281 -0
- package/monol-plugin-scout-pkg/lib/recommendation-controller.sh +290 -0
- package/monol-plugin-scout-pkg/lib/rejection-learner.sh +232 -0
- package/monol-plugin-scout-pkg/lib/scheduler.sh +275 -0
- package/monol-plugin-scout-pkg/lib/skill-scout.sh +729 -0
- package/monol-plugin-scout-pkg/lib/sync.sh +221 -0
- package/monol-plugin-scout-pkg/lib/team-learner.sh +450 -0
- package/monol-plugin-scout-pkg/lib/team-manager.sh +369 -0
- package/monol-plugin-scout-pkg/lib/trend-learner.sh +428 -0
- package/monol-plugin-scout-pkg/lib/trust-manager.sh +261 -0
- package/monol-plugin-scout-pkg/lib/trusted-installer.sh +738 -0
- package/monol-plugin-scout-pkg/plugin.json +3 -2
- package/monol-plugin-scout-pkg/skills/audit.md +6 -0
- package/monol-plugin-scout-pkg/skills/cleanup.md +6 -0
- package/monol-plugin-scout-pkg/skills/compare.md +6 -0
- package/monol-plugin-scout-pkg/skills/console.md +315 -0
- package/monol-plugin-scout-pkg/skills/explore.md +6 -0
- package/monol-plugin-scout-pkg/skills/fork.md +6 -0
- package/monol-plugin-scout-pkg/skills/frequency.md +93 -0
- package/monol-plugin-scout-pkg/skills/install.md +127 -0
- package/monol-plugin-scout-pkg/skills/priority.md +77 -0
- package/monol-plugin-scout-pkg/skills/quiet.md +73 -0
- package/monol-plugin-scout-pkg/skills/schedule.md +95 -0
- package/monol-plugin-scout-pkg/skills/scout.md +27 -17
- package/monol-plugin-scout-pkg/skills/skills.md +230 -0
- package/monol-plugin-scout-pkg/skills/team.md +117 -0
- package/monol-plugin-scout-pkg/skills/timing.md +97 -0
- package/monol-plugin-scout-pkg/skills/trust.md +120 -0
- package/monol-plugin-scout-pkg/skills/trusted-install.md +264 -0
- package/monol-plugin-scout-pkg/skills/uninstall.md +100 -0
- package/monol-plugin-scout-pkg/web/components/activity-chart.js +208 -0
- package/monol-plugin-scout-pkg/web/components/index.js +27 -0
- package/monol-plugin-scout-pkg/web/components/insight-card.js +365 -0
- package/monol-plugin-scout-pkg/web/components/overview.js +154 -0
- package/monol-plugin-scout-pkg/web/components/plugin-list.js +242 -0
- package/monol-plugin-scout-pkg/web/components/stats-card.js +126 -0
- package/monol-plugin-scout-pkg/web/components/team-list.js +346 -0
- package/monol-plugin-scout-pkg/web/console.html +2098 -0
- package/monol-plugin-scout-pkg/web/dashboard.html +2106 -0
- package/monol-plugin-scout-pkg/web/manifest.json +29 -0
- package/package.json +1 -1
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Plugin Scout - 트렌드 학습 시스템
|
|
3
|
+
# 주간/월간 인기 플러그인 추적 및 카테고리별 트렌드 변화 감지
|
|
4
|
+
|
|
5
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
|
|
6
|
+
DATA_DIR="$PLUGIN_ROOT/data"
|
|
7
|
+
TRENDS_FILE="$DATA_DIR/trends.json"
|
|
8
|
+
|
|
9
|
+
# 트렌드 데이터 초기화
|
|
10
|
+
init_trends() {
|
|
11
|
+
if [ ! -f "$TRENDS_FILE" ]; then
|
|
12
|
+
mkdir -p "$DATA_DIR"
|
|
13
|
+
echo '{
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"lastUpdated": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
|
|
16
|
+
"weekly": {
|
|
17
|
+
"currentWeek": "'$(date +"%Y-W%W")'",
|
|
18
|
+
"plugins": {},
|
|
19
|
+
"categories": {}
|
|
20
|
+
},
|
|
21
|
+
"monthly": {
|
|
22
|
+
"currentMonth": "'$(date +"%Y-%m")'",
|
|
23
|
+
"plugins": {},
|
|
24
|
+
"categories": {}
|
|
25
|
+
},
|
|
26
|
+
"allTime": {
|
|
27
|
+
"plugins": {},
|
|
28
|
+
"categories": {}
|
|
29
|
+
},
|
|
30
|
+
"newPlugins": [],
|
|
31
|
+
"trending": [],
|
|
32
|
+
"alerts": []
|
|
33
|
+
}' > "$TRENDS_FILE"
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# 주/월 변경 시 데이터 롤오버
|
|
38
|
+
check_period_rollover() {
|
|
39
|
+
init_trends
|
|
40
|
+
|
|
41
|
+
if ! command -v jq &> /dev/null; then return; fi
|
|
42
|
+
|
|
43
|
+
local current_week=$(date +"%Y-W%W")
|
|
44
|
+
local current_month=$(date +"%Y-%m")
|
|
45
|
+
local stored_week=$(jq -r '.weekly.currentWeek // ""' "$TRENDS_FILE")
|
|
46
|
+
local stored_month=$(jq -r '.monthly.currentMonth // ""' "$TRENDS_FILE")
|
|
47
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
48
|
+
|
|
49
|
+
local needs_update=false
|
|
50
|
+
|
|
51
|
+
# 주간 롤오버
|
|
52
|
+
if [ "$current_week" != "$stored_week" ]; then
|
|
53
|
+
jq --arg week "$current_week" --arg ts "$timestamp" '
|
|
54
|
+
.weekly.previousWeek = .weekly.plugins |
|
|
55
|
+
.weekly.currentWeek = $week |
|
|
56
|
+
.weekly.plugins = {} |
|
|
57
|
+
.weekly.categories = {} |
|
|
58
|
+
.lastUpdated = $ts
|
|
59
|
+
' "$TRENDS_FILE" > "$TRENDS_FILE.tmp" && mv "$TRENDS_FILE.tmp" "$TRENDS_FILE"
|
|
60
|
+
needs_update=true
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# 월간 롤오버
|
|
64
|
+
if [ "$current_month" != "$stored_month" ]; then
|
|
65
|
+
jq --arg month "$current_month" --arg ts "$timestamp" '
|
|
66
|
+
.monthly.previousMonth = .monthly.plugins |
|
|
67
|
+
.monthly.currentMonth = $month |
|
|
68
|
+
.monthly.plugins = {} |
|
|
69
|
+
.monthly.categories = {} |
|
|
70
|
+
.lastUpdated = $ts
|
|
71
|
+
' "$TRENDS_FILE" > "$TRENDS_FILE.tmp" && mv "$TRENDS_FILE.tmp" "$TRENDS_FILE"
|
|
72
|
+
needs_update=true
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
if [ "$needs_update" = "true" ]; then
|
|
76
|
+
update_trending
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# 플러그인 인기도 기록
|
|
81
|
+
record_plugin_popularity() {
|
|
82
|
+
local plugin_name="$1"
|
|
83
|
+
local category="$2"
|
|
84
|
+
local event="$3" # view, recommend, install, use
|
|
85
|
+
|
|
86
|
+
check_period_rollover
|
|
87
|
+
|
|
88
|
+
if ! command -v jq &> /dev/null; then return; fi
|
|
89
|
+
|
|
90
|
+
local weight=1
|
|
91
|
+
case "$event" in
|
|
92
|
+
view) weight=1 ;;
|
|
93
|
+
recommend) weight=2 ;;
|
|
94
|
+
install) weight=5 ;;
|
|
95
|
+
use) weight=3 ;;
|
|
96
|
+
esac
|
|
97
|
+
|
|
98
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
99
|
+
|
|
100
|
+
jq --arg plugin "$plugin_name" \
|
|
101
|
+
--arg cat "$category" \
|
|
102
|
+
--argjson weight "$weight" \
|
|
103
|
+
--arg ts "$timestamp" \
|
|
104
|
+
'
|
|
105
|
+
.lastUpdated = $ts |
|
|
106
|
+
.weekly.plugins[$plugin] = ((.weekly.plugins[$plugin] // 0) + $weight) |
|
|
107
|
+
.weekly.categories[$cat] = ((.weekly.categories[$cat] // 0) + $weight) |
|
|
108
|
+
.monthly.plugins[$plugin] = ((.monthly.plugins[$plugin] // 0) + $weight) |
|
|
109
|
+
.monthly.categories[$cat] = ((.monthly.categories[$cat] // 0) + $weight) |
|
|
110
|
+
.allTime.plugins[$plugin] = ((.allTime.plugins[$plugin] // 0) + $weight) |
|
|
111
|
+
.allTime.categories[$cat] = ((.allTime.categories[$cat] // 0) + $weight)
|
|
112
|
+
' "$TRENDS_FILE" > "$TRENDS_FILE.tmp" && mv "$TRENDS_FILE.tmp" "$TRENDS_FILE"
|
|
113
|
+
|
|
114
|
+
echo "Recorded: $plugin_name ($event)"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# 새 플러그인 등록
|
|
118
|
+
register_new_plugin() {
|
|
119
|
+
local plugin_name="$1"
|
|
120
|
+
local category="$2"
|
|
121
|
+
local source="$3" # marketplace, user_created, forked
|
|
122
|
+
|
|
123
|
+
init_trends
|
|
124
|
+
|
|
125
|
+
if ! command -v jq &> /dev/null; then return; fi
|
|
126
|
+
|
|
127
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
128
|
+
|
|
129
|
+
# 이미 등록된 플러그인인지 확인
|
|
130
|
+
local exists=$(jq -r --arg plugin "$plugin_name" '.newPlugins | map(select(.name == $plugin)) | length' "$TRENDS_FILE")
|
|
131
|
+
|
|
132
|
+
if [ "$exists" = "0" ]; then
|
|
133
|
+
jq --arg plugin "$plugin_name" \
|
|
134
|
+
--arg cat "$category" \
|
|
135
|
+
--arg src "$source" \
|
|
136
|
+
--arg ts "$timestamp" \
|
|
137
|
+
'
|
|
138
|
+
.lastUpdated = $ts |
|
|
139
|
+
.newPlugins = ([{
|
|
140
|
+
"name": $plugin,
|
|
141
|
+
"category": $cat,
|
|
142
|
+
"source": $src,
|
|
143
|
+
"discoveredAt": $ts
|
|
144
|
+
}] + .newPlugins) | .[0:50]
|
|
145
|
+
' "$TRENDS_FILE" > "$TRENDS_FILE.tmp" && mv "$TRENDS_FILE.tmp" "$TRENDS_FILE"
|
|
146
|
+
|
|
147
|
+
# 알림 추가
|
|
148
|
+
add_alert "new_plugin" "$plugin_name" "New plugin discovered: $plugin_name ($category)"
|
|
149
|
+
|
|
150
|
+
echo "Registered new plugin: $plugin_name"
|
|
151
|
+
fi
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# 트렌딩 플러그인 업데이트
|
|
155
|
+
update_trending() {
|
|
156
|
+
init_trends
|
|
157
|
+
|
|
158
|
+
if ! command -v jq &> /dev/null; then return; fi
|
|
159
|
+
|
|
160
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
161
|
+
|
|
162
|
+
# 주간 기준 상위 10개 + 성장률 기반 트렌딩 계산
|
|
163
|
+
jq --arg ts "$timestamp" '
|
|
164
|
+
.lastUpdated = $ts |
|
|
165
|
+
.trending = (
|
|
166
|
+
.weekly.plugins | to_entries |
|
|
167
|
+
sort_by(-.value) |
|
|
168
|
+
.[0:10] |
|
|
169
|
+
map({
|
|
170
|
+
name: .key,
|
|
171
|
+
score: .value,
|
|
172
|
+
trend: (
|
|
173
|
+
if .value > ((.weekly.previousWeek[.key] // 0)) then "rising"
|
|
174
|
+
elif .value < ((.weekly.previousWeek[.key] // 0)) then "falling"
|
|
175
|
+
else "stable"
|
|
176
|
+
end
|
|
177
|
+
)
|
|
178
|
+
})
|
|
179
|
+
)
|
|
180
|
+
' "$TRENDS_FILE" > "$TRENDS_FILE.tmp" && mv "$TRENDS_FILE.tmp" "$TRENDS_FILE"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# 알림 추가
|
|
184
|
+
add_alert() {
|
|
185
|
+
local type="$1"
|
|
186
|
+
local subject="$2"
|
|
187
|
+
local message="$3"
|
|
188
|
+
|
|
189
|
+
init_trends
|
|
190
|
+
|
|
191
|
+
if ! command -v jq &> /dev/null; then return; fi
|
|
192
|
+
|
|
193
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
194
|
+
|
|
195
|
+
jq --arg type "$type" \
|
|
196
|
+
--arg subject "$subject" \
|
|
197
|
+
--arg msg "$message" \
|
|
198
|
+
--arg ts "$timestamp" \
|
|
199
|
+
'
|
|
200
|
+
.lastUpdated = $ts |
|
|
201
|
+
.alerts = ([{
|
|
202
|
+
"type": $type,
|
|
203
|
+
"subject": $subject,
|
|
204
|
+
"message": $msg,
|
|
205
|
+
"createdAt": $ts,
|
|
206
|
+
"read": false
|
|
207
|
+
}] + .alerts) | .[0:20]
|
|
208
|
+
' "$TRENDS_FILE" > "$TRENDS_FILE.tmp" && mv "$TRENDS_FILE.tmp" "$TRENDS_FILE"
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# 알림 읽음 처리
|
|
212
|
+
mark_alerts_read() {
|
|
213
|
+
init_trends
|
|
214
|
+
|
|
215
|
+
if ! command -v jq &> /dev/null; then return; fi
|
|
216
|
+
|
|
217
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
218
|
+
|
|
219
|
+
jq --arg ts "$timestamp" '
|
|
220
|
+
.lastUpdated = $ts |
|
|
221
|
+
.alerts = (.alerts | map(.read = true))
|
|
222
|
+
' "$TRENDS_FILE" > "$TRENDS_FILE.tmp" && mv "$TRENDS_FILE.tmp" "$TRENDS_FILE"
|
|
223
|
+
|
|
224
|
+
echo "All alerts marked as read"
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# 주간 인기 플러그인 조회
|
|
228
|
+
get_weekly_top() {
|
|
229
|
+
local limit="${1:-10}"
|
|
230
|
+
|
|
231
|
+
check_period_rollover
|
|
232
|
+
|
|
233
|
+
if ! command -v jq &> /dev/null; then
|
|
234
|
+
echo "jq required"
|
|
235
|
+
return
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
jq -r --argjson limit "$limit" '
|
|
239
|
+
.weekly.plugins | to_entries |
|
|
240
|
+
sort_by(-.value) |
|
|
241
|
+
.[0:$limit] |
|
|
242
|
+
.[] | "\(.key): \(.value)"
|
|
243
|
+
' "$TRENDS_FILE"
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# 월간 인기 플러그인 조회
|
|
247
|
+
get_monthly_top() {
|
|
248
|
+
local limit="${1:-10}"
|
|
249
|
+
|
|
250
|
+
check_period_rollover
|
|
251
|
+
|
|
252
|
+
if ! command -v jq &> /dev/null; then
|
|
253
|
+
echo "jq required"
|
|
254
|
+
return
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
jq -r --argjson limit "$limit" '
|
|
258
|
+
.monthly.plugins | to_entries |
|
|
259
|
+
sort_by(-.value) |
|
|
260
|
+
.[0:$limit] |
|
|
261
|
+
.[] | "\(.key): \(.value)"
|
|
262
|
+
' "$TRENDS_FILE"
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# 카테고리 트렌드 조회
|
|
266
|
+
get_category_trends() {
|
|
267
|
+
check_period_rollover
|
|
268
|
+
|
|
269
|
+
if ! command -v jq &> /dev/null; then
|
|
270
|
+
echo "jq required"
|
|
271
|
+
return
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
echo "=== Category Trends ==="
|
|
275
|
+
echo ""
|
|
276
|
+
echo "This Week:"
|
|
277
|
+
jq -r '.weekly.categories | to_entries | sort_by(-.value) | .[] | " \(.key): \(.value)"' "$TRENDS_FILE"
|
|
278
|
+
echo ""
|
|
279
|
+
echo "This Month:"
|
|
280
|
+
jq -r '.monthly.categories | to_entries | sort_by(-.value) | .[] | " \(.key): \(.value)"' "$TRENDS_FILE"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# 새 플러그인 목록 조회
|
|
284
|
+
get_new_plugins() {
|
|
285
|
+
local days="${1:-7}"
|
|
286
|
+
|
|
287
|
+
init_trends
|
|
288
|
+
|
|
289
|
+
if ! command -v jq &> /dev/null; then
|
|
290
|
+
echo "jq required"
|
|
291
|
+
return
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
local cutoff=$(date -u -v-${days}d +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -d "$days days ago" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
|
|
295
|
+
|
|
296
|
+
echo "=== New Plugins (last $days days) ==="
|
|
297
|
+
jq -r --arg cutoff "$cutoff" '
|
|
298
|
+
.newPlugins |
|
|
299
|
+
map(select(.discoveredAt >= $cutoff)) |
|
|
300
|
+
.[] | " \(.name) [\(.category)] - \(.discoveredAt | split("T")[0])"
|
|
301
|
+
' "$TRENDS_FILE"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
# 트렌딩 플러그인 조회
|
|
305
|
+
get_trending() {
|
|
306
|
+
update_trending
|
|
307
|
+
|
|
308
|
+
if ! command -v jq &> /dev/null; then
|
|
309
|
+
echo "jq required"
|
|
310
|
+
return
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
echo "=== Trending Plugins ==="
|
|
314
|
+
jq -r '.trending | .[] | " \(.name): \(.score) [\(.trend)]"' "$TRENDS_FILE"
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# 읽지 않은 알림 조회
|
|
318
|
+
get_unread_alerts() {
|
|
319
|
+
init_trends
|
|
320
|
+
|
|
321
|
+
if ! command -v jq &> /dev/null; then
|
|
322
|
+
echo "jq required"
|
|
323
|
+
return
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
local count=$(jq -r '[.alerts[] | select(.read == false)] | length' "$TRENDS_FILE")
|
|
327
|
+
|
|
328
|
+
if [ "$count" = "0" ]; then
|
|
329
|
+
echo "No unread alerts"
|
|
330
|
+
return
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
echo "=== Unread Alerts ($count) ==="
|
|
334
|
+
jq -r '.alerts | map(select(.read == false)) | .[] | " [\(.type)] \(.message)"' "$TRENDS_FILE"
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
# 트렌드 요약 출력
|
|
338
|
+
get_trends_summary() {
|
|
339
|
+
check_period_rollover
|
|
340
|
+
|
|
341
|
+
if ! command -v jq &> /dev/null; then
|
|
342
|
+
echo "jq required"
|
|
343
|
+
return
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
echo "=== Trends Summary ==="
|
|
347
|
+
echo ""
|
|
348
|
+
echo "Week: $(jq -r '.weekly.currentWeek' "$TRENDS_FILE")"
|
|
349
|
+
echo "Month: $(jq -r '.monthly.currentMonth' "$TRENDS_FILE")"
|
|
350
|
+
echo ""
|
|
351
|
+
echo "Top 5 This Week:"
|
|
352
|
+
get_weekly_top 5
|
|
353
|
+
echo ""
|
|
354
|
+
echo "Trending:"
|
|
355
|
+
jq -r '.trending | .[0:3] | .[] | " \(.name) [\(.trend)]"' "$TRENDS_FILE"
|
|
356
|
+
echo ""
|
|
357
|
+
echo "New Plugins: $(jq -r '.newPlugins | length' "$TRENDS_FILE")"
|
|
358
|
+
echo "Unread Alerts: $(jq -r '[.alerts[] | select(.read == false)] | length' "$TRENDS_FILE")"
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# 트렌드 데이터 초기화 (리셋)
|
|
362
|
+
reset_trends() {
|
|
363
|
+
if [ -f "$TRENDS_FILE" ]; then
|
|
364
|
+
rm "$TRENDS_FILE"
|
|
365
|
+
init_trends
|
|
366
|
+
echo "Trends data reset successfully"
|
|
367
|
+
else
|
|
368
|
+
echo "No trends data to reset"
|
|
369
|
+
fi
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
# CLI 인터페이스
|
|
373
|
+
case "$1" in
|
|
374
|
+
init)
|
|
375
|
+
init_trends
|
|
376
|
+
echo "Trends initialized"
|
|
377
|
+
;;
|
|
378
|
+
record)
|
|
379
|
+
record_plugin_popularity "$2" "$3" "$4"
|
|
380
|
+
;;
|
|
381
|
+
new-plugin)
|
|
382
|
+
register_new_plugin "$2" "$3" "$4"
|
|
383
|
+
;;
|
|
384
|
+
weekly-top)
|
|
385
|
+
get_weekly_top "$2"
|
|
386
|
+
;;
|
|
387
|
+
monthly-top)
|
|
388
|
+
get_monthly_top "$2"
|
|
389
|
+
;;
|
|
390
|
+
category-trends)
|
|
391
|
+
get_category_trends
|
|
392
|
+
;;
|
|
393
|
+
new-plugins)
|
|
394
|
+
get_new_plugins "$2"
|
|
395
|
+
;;
|
|
396
|
+
trending)
|
|
397
|
+
get_trending
|
|
398
|
+
;;
|
|
399
|
+
alerts)
|
|
400
|
+
get_unread_alerts
|
|
401
|
+
;;
|
|
402
|
+
mark-read)
|
|
403
|
+
mark_alerts_read
|
|
404
|
+
;;
|
|
405
|
+
summary)
|
|
406
|
+
get_trends_summary
|
|
407
|
+
;;
|
|
408
|
+
reset)
|
|
409
|
+
reset_trends
|
|
410
|
+
;;
|
|
411
|
+
*)
|
|
412
|
+
echo "Usage: $0 {init|record|new-plugin|weekly-top|monthly-top|category-trends|new-plugins|trending|alerts|mark-read|summary|reset}"
|
|
413
|
+
echo ""
|
|
414
|
+
echo "Commands:"
|
|
415
|
+
echo " init - Initialize trends data"
|
|
416
|
+
echo " record <plugin> <category> <event> - Record plugin popularity (view|recommend|install|use)"
|
|
417
|
+
echo " new-plugin <plugin> <cat> <source> - Register new plugin"
|
|
418
|
+
echo " weekly-top [limit] - Get weekly top plugins"
|
|
419
|
+
echo " monthly-top [limit] - Get monthly top plugins"
|
|
420
|
+
echo " category-trends - Get category trends"
|
|
421
|
+
echo " new-plugins [days] - Get new plugins"
|
|
422
|
+
echo " trending - Get trending plugins"
|
|
423
|
+
echo " alerts - Get unread alerts"
|
|
424
|
+
echo " mark-read - Mark all alerts as read"
|
|
425
|
+
echo " summary - Show trends summary"
|
|
426
|
+
echo " reset - Reset trends data"
|
|
427
|
+
;;
|
|
428
|
+
esac
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# trust-manager.sh — Trust author management module
|
|
3
|
+
# Manages the trusted_authors list in config.yaml for auto-install decisions.
|
|
4
|
+
# Can be used as CLI or sourced by other scripts.
|
|
5
|
+
|
|
6
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
|
|
7
|
+
DATA_DIR="$PLUGIN_ROOT/data"
|
|
8
|
+
CONFIG_FILE="$PLUGIN_ROOT/config.yaml"
|
|
9
|
+
EVENTS_LOG="$DATA_DIR/logs/events.jsonl"
|
|
10
|
+
HISTORY_FILE="$DATA_DIR/history.json"
|
|
11
|
+
LOGGER="$PLUGIN_ROOT/lib/logger.sh"
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Helpers
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
read_config() {
|
|
18
|
+
local key="$1"
|
|
19
|
+
grep "^${key}:" "$CONFIG_FILE" 2>/dev/null | sed "s/^${key}:[[:space:]]*//"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_get_trusted_line() {
|
|
23
|
+
grep 'trusted_authors:' "$CONFIG_FILE" 2>/dev/null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_parse_trusted_authors() {
|
|
27
|
+
# Parse trusted_authors: ["Anthropic", "vercel"] → one author per line
|
|
28
|
+
local line
|
|
29
|
+
line=$(_get_trusted_line)
|
|
30
|
+
if [ -z "$line" ]; then
|
|
31
|
+
return
|
|
32
|
+
fi
|
|
33
|
+
echo "$line" \
|
|
34
|
+
| sed 's/.*\[//' \
|
|
35
|
+
| sed 's/\].*//' \
|
|
36
|
+
| tr ',' '\n' \
|
|
37
|
+
| sed 's/^[[:space:]]*//;s/[[:space:]]*$//' \
|
|
38
|
+
| sed 's/^"//;s/"$//' \
|
|
39
|
+
| grep -v '^$'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_write_trusted_authors() {
|
|
43
|
+
# Accepts authors as arguments, writes them back to config.yaml
|
|
44
|
+
local authors=("$@")
|
|
45
|
+
local json_array=""
|
|
46
|
+
for a in "${authors[@]}"; do
|
|
47
|
+
if [ -n "$json_array" ]; then
|
|
48
|
+
json_array="${json_array}, \"${a}\""
|
|
49
|
+
else
|
|
50
|
+
json_array="\"${a}\""
|
|
51
|
+
fi
|
|
52
|
+
done
|
|
53
|
+
local new_line=" trusted_authors: [${json_array}]"
|
|
54
|
+
|
|
55
|
+
if grep -q 'trusted_authors:' "$CONFIG_FILE"; then
|
|
56
|
+
sed -i.bak "s|.*trusted_authors:.*|${new_line}|" "$CONFIG_FILE"
|
|
57
|
+
rm -f "${CONFIG_FILE}.bak"
|
|
58
|
+
else
|
|
59
|
+
# Append under auto_install section
|
|
60
|
+
echo "$new_line" >> "$CONFIG_FILE"
|
|
61
|
+
fi
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_log_event() {
|
|
65
|
+
local action="$1"
|
|
66
|
+
local detail="$2"
|
|
67
|
+
if [ -x "$LOGGER" ]; then
|
|
68
|
+
bash "$LOGGER" log "trust" "$action" "$detail"
|
|
69
|
+
elif [ -f "$LOGGER" ]; then
|
|
70
|
+
bash "$LOGGER" log "trust" "$action" "$detail"
|
|
71
|
+
else
|
|
72
|
+
# Fallback: write directly to events.jsonl
|
|
73
|
+
mkdir -p "$(dirname "$EVENTS_LOG")"
|
|
74
|
+
local ts
|
|
75
|
+
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
76
|
+
echo "{\"timestamp\":\"${ts}\",\"source\":\"trust-manager\",\"action\":\"${action}\",\"detail\":\"${detail}\"}" >> "$EVENTS_LOG"
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# Core functions
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
add_trusted() {
|
|
85
|
+
local author="$1"
|
|
86
|
+
if [ -z "$author" ]; then
|
|
87
|
+
echo "Error: author name required"
|
|
88
|
+
return 1
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Strip leading @ if present
|
|
92
|
+
author="${author#@}"
|
|
93
|
+
|
|
94
|
+
# Check if already trusted
|
|
95
|
+
local existing
|
|
96
|
+
existing=$(_parse_trusted_authors)
|
|
97
|
+
if echo "$existing" | grep -qx "$author"; then
|
|
98
|
+
echo "\"${author}\" is already in the trusted authors list."
|
|
99
|
+
return 0
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# Build new list
|
|
103
|
+
local authors=()
|
|
104
|
+
while IFS= read -r a; do
|
|
105
|
+
[ -n "$a" ] && authors+=("$a")
|
|
106
|
+
done <<< "$existing"
|
|
107
|
+
authors+=("$author")
|
|
108
|
+
|
|
109
|
+
_write_trusted_authors "${authors[@]}"
|
|
110
|
+
_log_event "trust_add" "Added trusted author: ${author}"
|
|
111
|
+
echo "Added \"${author}\" to trusted authors."
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
remove_trusted() {
|
|
115
|
+
local author="$1"
|
|
116
|
+
if [ -z "$author" ]; then
|
|
117
|
+
echo "Error: author name required"
|
|
118
|
+
return 1
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# Strip leading @ if present
|
|
122
|
+
author="${author#@}"
|
|
123
|
+
|
|
124
|
+
# Protect Anthropic
|
|
125
|
+
if [ "$author" = "Anthropic" ] || [ "$author" = "anthropic" ]; then
|
|
126
|
+
echo "Error: \"Anthropic\" is a protected author and cannot be removed."
|
|
127
|
+
return 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
local existing
|
|
131
|
+
existing=$(_parse_trusted_authors)
|
|
132
|
+
if ! echo "$existing" | grep -qx "$author"; then
|
|
133
|
+
echo "\"${author}\" is not in the trusted authors list."
|
|
134
|
+
return 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# Build new list without the target
|
|
138
|
+
local authors=()
|
|
139
|
+
while IFS= read -r a; do
|
|
140
|
+
if [ -n "$a" ] && [ "$a" != "$author" ]; then
|
|
141
|
+
authors+=("$a")
|
|
142
|
+
fi
|
|
143
|
+
done <<< "$existing"
|
|
144
|
+
|
|
145
|
+
_write_trusted_authors "${authors[@]}"
|
|
146
|
+
_log_event "trust_remove" "Removed trusted author: ${author}"
|
|
147
|
+
echo "Removed \"${author}\" from trusted authors."
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
list_trusted() {
|
|
151
|
+
echo "=== Trusted Authors ==="
|
|
152
|
+
local authors
|
|
153
|
+
authors=$(_parse_trusted_authors)
|
|
154
|
+
if [ -z "$authors" ]; then
|
|
155
|
+
echo " (none)"
|
|
156
|
+
return 0
|
|
157
|
+
fi
|
|
158
|
+
local i=1
|
|
159
|
+
while IFS= read -r a; do
|
|
160
|
+
if [ -n "$a" ]; then
|
|
161
|
+
echo " ${i}. ${a}"
|
|
162
|
+
i=$((i + 1))
|
|
163
|
+
fi
|
|
164
|
+
done <<< "$authors"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
is_trusted() {
|
|
168
|
+
local author="$1"
|
|
169
|
+
if [ -z "$author" ]; then
|
|
170
|
+
return 1
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# Strip leading @ if present
|
|
174
|
+
author="${author#@}"
|
|
175
|
+
|
|
176
|
+
local existing
|
|
177
|
+
existing=$(_parse_trusted_authors)
|
|
178
|
+
if echo "$existing" | grep -qx "$author"; then
|
|
179
|
+
return 0
|
|
180
|
+
else
|
|
181
|
+
return 1
|
|
182
|
+
fi
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export_audit() {
|
|
186
|
+
local export_file="$DATA_DIR/trust-audit-export.json"
|
|
187
|
+
mkdir -p "$DATA_DIR"
|
|
188
|
+
|
|
189
|
+
# Collect trust-related events from events.jsonl
|
|
190
|
+
local trust_events="[]"
|
|
191
|
+
if [ -f "$EVENTS_LOG" ]; then
|
|
192
|
+
trust_events=$(grep -E '"trust|trusted|trust_add|trust_remove"' "$EVENTS_LOG" 2>/dev/null \
|
|
193
|
+
| jq -s '.' 2>/dev/null || echo "[]")
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
# Collect trustedInstallLog from history.json
|
|
197
|
+
local trusted_install_log="[]"
|
|
198
|
+
if [ -f "$HISTORY_FILE" ]; then
|
|
199
|
+
trusted_install_log=$(jq '.trustedInstallLog // []' "$HISTORY_FILE" 2>/dev/null || echo "[]")
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
# Collect current trusted authors
|
|
203
|
+
local current_authors
|
|
204
|
+
current_authors=$(_parse_trusted_authors)
|
|
205
|
+
local authors_json="[]"
|
|
206
|
+
if [ -n "$current_authors" ]; then
|
|
207
|
+
authors_json=$(echo "$current_authors" | jq -R '.' | jq -s '.' 2>/dev/null || echo "[]")
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# Build export JSON
|
|
211
|
+
local ts
|
|
212
|
+
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
213
|
+
jq -n \
|
|
214
|
+
--arg ts "$ts" \
|
|
215
|
+
--argjson events "$trust_events" \
|
|
216
|
+
--argjson installs "$trusted_install_log" \
|
|
217
|
+
--argjson authors "$authors_json" \
|
|
218
|
+
'{
|
|
219
|
+
exported_at: $ts,
|
|
220
|
+
current_trusted_authors: $authors,
|
|
221
|
+
trust_events: $events,
|
|
222
|
+
trusted_install_log: $installs
|
|
223
|
+
}' > "$export_file"
|
|
224
|
+
|
|
225
|
+
echo "Audit exported to: ${export_file}"
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
# CLI interface
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
233
|
+
case "$1" in
|
|
234
|
+
add)
|
|
235
|
+
add_trusted "$2"
|
|
236
|
+
;;
|
|
237
|
+
remove)
|
|
238
|
+
remove_trusted "$2"
|
|
239
|
+
;;
|
|
240
|
+
list)
|
|
241
|
+
list_trusted
|
|
242
|
+
;;
|
|
243
|
+
check)
|
|
244
|
+
is_trusted "$2" && echo "trusted" || echo "not trusted"
|
|
245
|
+
;;
|
|
246
|
+
export)
|
|
247
|
+
export_audit
|
|
248
|
+
;;
|
|
249
|
+
*)
|
|
250
|
+
echo "Usage: $0 {add|remove|list|check|export} [author]"
|
|
251
|
+
echo ""
|
|
252
|
+
echo "Commands:"
|
|
253
|
+
echo " add <author> Add author to trusted list"
|
|
254
|
+
echo " remove <author> Remove author from trusted list"
|
|
255
|
+
echo " list Show all trusted authors"
|
|
256
|
+
echo " check <author> Check if author is trusted"
|
|
257
|
+
echo " export Export full trust audit log"
|
|
258
|
+
exit 1
|
|
259
|
+
;;
|
|
260
|
+
esac
|
|
261
|
+
fi
|