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,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