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,738 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Plugin Scout - 신뢰된 자동 설치 엔진
|
|
3
|
+
# 신뢰된 저자(Anthropic 등)의 공식 플러그인을 자동 설치
|
|
4
|
+
|
|
5
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
|
|
6
|
+
DATA_DIR="$PLUGIN_ROOT/data"
|
|
7
|
+
|
|
8
|
+
MARKETPLACE_FILE="$HOME/.claude/plugins/marketplaces/claude-plugins-official/.claude-plugin/marketplace.json"
|
|
9
|
+
CACHE_DIR="$DATA_DIR/.cache"
|
|
10
|
+
CACHE_FILE="$CACHE_DIR/trusted-catalog.json"
|
|
11
|
+
INSTALL_LOG="$DATA_DIR/.last-auto-install.json"
|
|
12
|
+
HISTORY_FILE="$DATA_DIR/history.json"
|
|
13
|
+
USAGE_FILE="$DATA_DIR/usage.json"
|
|
14
|
+
CLAUDE_SETTINGS="$HOME/.claude/settings.json"
|
|
15
|
+
CONFIG_FILE="$PLUGIN_ROOT/config.yaml"
|
|
16
|
+
|
|
17
|
+
LOGGER="$PLUGIN_ROOT/lib/logger.sh"
|
|
18
|
+
PLUGIN_MANAGER="$PLUGIN_ROOT/lib/plugin-manager.sh"
|
|
19
|
+
PROJECT_ANALYZER="$PLUGIN_ROOT/lib/project-analyzer.sh"
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Helpers
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
_log() {
|
|
26
|
+
local level="$1"
|
|
27
|
+
local msg="$2"
|
|
28
|
+
if [ -f "$LOGGER" ]; then
|
|
29
|
+
bash "$LOGGER" "$level" "$msg" "trusted-installer"
|
|
30
|
+
fi
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_log_event() {
|
|
34
|
+
local event_type="$1"
|
|
35
|
+
local event_data="$2"
|
|
36
|
+
if [ -f "$LOGGER" ]; then
|
|
37
|
+
bash "$LOGGER" event "$event_type" "$event_data"
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Config reading
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
# Read a value from config.yaml auto_install section
|
|
46
|
+
# Usage: read_config <key> [default]
|
|
47
|
+
read_config() {
|
|
48
|
+
local key="$1"
|
|
49
|
+
local default="$2"
|
|
50
|
+
local value=""
|
|
51
|
+
|
|
52
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
53
|
+
value=$(grep "^ ${key}:" "$CONFIG_FILE" 2>/dev/null | sed "s/^ ${key}:[[:space:]]*//" | sed 's/[[:space:]]*$//')
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
if [ -z "$value" ]; then
|
|
57
|
+
echo "$default"
|
|
58
|
+
else
|
|
59
|
+
echo "$value"
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Check if trusted auto-install is enabled
|
|
64
|
+
# Returns 0 if BOTH config.yaml and history.json agree, 1 otherwise
|
|
65
|
+
is_enabled() {
|
|
66
|
+
# 1. Check config.yaml auto_install.enabled
|
|
67
|
+
local config_enabled
|
|
68
|
+
config_enabled=$(read_config "enabled" "false")
|
|
69
|
+
if [ "$config_enabled" != "true" ]; then
|
|
70
|
+
return 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# 2. Check history.json preferences.trustedAutoInstall
|
|
74
|
+
if [ -f "$HISTORY_FILE" ] && command -v jq &> /dev/null; then
|
|
75
|
+
local history_enabled
|
|
76
|
+
history_enabled=$(jq -r '.preferences.trustedAutoInstall // false' "$HISTORY_FILE" 2>/dev/null)
|
|
77
|
+
if [ "$history_enabled" != "true" ]; then
|
|
78
|
+
return 1
|
|
79
|
+
fi
|
|
80
|
+
else
|
|
81
|
+
# No history file or no jq → treat as disabled
|
|
82
|
+
return 1
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
return 0
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
# Trusted authors
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
# Get trusted authors list from config.yaml
|
|
93
|
+
# Returns JSON array, e.g. ["Anthropic"]
|
|
94
|
+
get_trusted_authors() {
|
|
95
|
+
local line
|
|
96
|
+
line=$(grep 'trusted_authors:' "$CONFIG_FILE" 2>/dev/null)
|
|
97
|
+
if [ -z "$line" ]; then
|
|
98
|
+
echo '["Anthropic"]'
|
|
99
|
+
return
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# Extract array content: trusted_authors: ["Anthropic", "vercel"] → JSON
|
|
103
|
+
local raw
|
|
104
|
+
raw=$(echo "$line" | sed 's/.*\[/[/' | sed 's/\].*/]/')
|
|
105
|
+
if echo "$raw" | jq '.' &>/dev/null; then
|
|
106
|
+
echo "$raw"
|
|
107
|
+
else
|
|
108
|
+
echo '["Anthropic"]'
|
|
109
|
+
fi
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
# Marketplace & installed plugins
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
# Parse marketplace.json and filter by trusted authors
|
|
117
|
+
# Returns JSON array of {name, description, author, category}
|
|
118
|
+
get_marketplace_plugins() {
|
|
119
|
+
local marketplace_data=""
|
|
120
|
+
|
|
121
|
+
if [ -f "$MARKETPLACE_FILE" ] && command -v jq &> /dev/null; then
|
|
122
|
+
marketplace_data=$(cat "$MARKETPLACE_FILE" 2>/dev/null)
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# Fallback to cache if marketplace file not available
|
|
126
|
+
if [ -z "$marketplace_data" ] || ! echo "$marketplace_data" | jq '.' &>/dev/null; then
|
|
127
|
+
if [ -f "$CACHE_FILE" ]; then
|
|
128
|
+
cat "$CACHE_FILE"
|
|
129
|
+
return 0
|
|
130
|
+
else
|
|
131
|
+
# No marketplace data and no cache — exit silently
|
|
132
|
+
echo "[]"
|
|
133
|
+
return 0
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
local trusted_authors
|
|
138
|
+
trusted_authors=$(get_trusted_authors)
|
|
139
|
+
|
|
140
|
+
# Filter plugins by trusted authors and project to {name, description, author, category}
|
|
141
|
+
local filtered
|
|
142
|
+
filtered=$(echo "$marketplace_data" | jq --argjson authors "$trusted_authors" '
|
|
143
|
+
[.plugins // .[] | objects] | flatten |
|
|
144
|
+
map(select(
|
|
145
|
+
.author.name as $aname |
|
|
146
|
+
($authors | map(ascii_downcase) | index($aname | ascii_downcase)) != null
|
|
147
|
+
)) |
|
|
148
|
+
map({
|
|
149
|
+
name: (.name // .id // "unknown"),
|
|
150
|
+
description: (.description // ""),
|
|
151
|
+
author: (.author.name // "unknown"),
|
|
152
|
+
category: (.category // "general")
|
|
153
|
+
})
|
|
154
|
+
' 2>/dev/null)
|
|
155
|
+
|
|
156
|
+
if [ -z "$filtered" ] || ! echo "$filtered" | jq '.' &>/dev/null; then
|
|
157
|
+
# Try alternate JSON structure (flat array)
|
|
158
|
+
filtered=$(echo "$marketplace_data" | jq --argjson authors "$trusted_authors" '
|
|
159
|
+
if type == "array" then . else [.] end |
|
|
160
|
+
map(select(
|
|
161
|
+
(.author.name // .author // "") as $aname |
|
|
162
|
+
($authors | map(ascii_downcase) | index($aname | ascii_downcase)) != null
|
|
163
|
+
)) |
|
|
164
|
+
map({
|
|
165
|
+
name: (.name // .id // "unknown"),
|
|
166
|
+
description: (.description // ""),
|
|
167
|
+
author: (if .author | type == "object" then .author.name else (.author // "unknown") end),
|
|
168
|
+
category: (.category // "general")
|
|
169
|
+
})
|
|
170
|
+
' 2>/dev/null)
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
if [ -z "$filtered" ] || ! echo "$filtered" | jq '.' &>/dev/null; then
|
|
174
|
+
echo "[]"
|
|
175
|
+
return 0
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
# Cache for offline fallback
|
|
179
|
+
local cache_catalog
|
|
180
|
+
cache_catalog=$(read_config "cache_catalog" "true")
|
|
181
|
+
if [ "$cache_catalog" = "true" ]; then
|
|
182
|
+
mkdir -p "$CACHE_DIR"
|
|
183
|
+
echo "$filtered" > "$CACHE_FILE"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
echo "$filtered"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# Get list of currently installed/enabled plugins
|
|
190
|
+
get_installed_plugins() {
|
|
191
|
+
if command -v jq &> /dev/null && [ -f "$CLAUDE_SETTINGS" ]; then
|
|
192
|
+
jq -r '.enabledPlugins // {} | keys[]' "$CLAUDE_SETTINGS" 2>/dev/null
|
|
193
|
+
fi
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# ---------------------------------------------------------------------------
|
|
197
|
+
# Language matching (LSP plugins)
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
# Check if an LSP plugin matches the current project language
|
|
201
|
+
# Returns 0 (match) or 1 (no match). Non-LSP plugins always return 0.
|
|
202
|
+
detect_language_match() {
|
|
203
|
+
local plugin_name="$1"
|
|
204
|
+
local project_types=""
|
|
205
|
+
|
|
206
|
+
# Get project types from project-analyzer
|
|
207
|
+
if [ -f "$PROJECT_ANALYZER" ]; then
|
|
208
|
+
project_types=$(bash "$PROJECT_ANALYZER" types 2>/dev/null)
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
case "$plugin_name" in
|
|
212
|
+
typescript-lsp)
|
|
213
|
+
echo "$project_types" | grep -qE '"nodejs"|"typescript"' && return 0
|
|
214
|
+
return 1
|
|
215
|
+
;;
|
|
216
|
+
pyright-lsp|python-lsp)
|
|
217
|
+
echo "$project_types" | grep -q '"python"' && return 0
|
|
218
|
+
return 1
|
|
219
|
+
;;
|
|
220
|
+
gopls-lsp|go-lsp)
|
|
221
|
+
echo "$project_types" | grep -q '"go"' && return 0
|
|
222
|
+
return 1
|
|
223
|
+
;;
|
|
224
|
+
rust-analyzer-lsp|rust-lsp)
|
|
225
|
+
echo "$project_types" | grep -q '"rust"' && return 0
|
|
226
|
+
return 1
|
|
227
|
+
;;
|
|
228
|
+
jdtls-lsp|java-lsp)
|
|
229
|
+
echo "$project_types" | grep -q '"java"' && return 0
|
|
230
|
+
return 1
|
|
231
|
+
;;
|
|
232
|
+
php-lsp)
|
|
233
|
+
echo "$project_types" | grep -q '"php"' && return 0
|
|
234
|
+
return 1
|
|
235
|
+
;;
|
|
236
|
+
clangd-lsp|c-lsp|cpp-lsp)
|
|
237
|
+
find . -maxdepth 3 -name "*.c" -o -name "*.cpp" -o -name "*.h" 2>/dev/null | head -1 | grep -q . && return 0
|
|
238
|
+
return 1
|
|
239
|
+
;;
|
|
240
|
+
swift-lsp)
|
|
241
|
+
find . -maxdepth 3 -name "*.swift" 2>/dev/null | head -1 | grep -q . && return 0
|
|
242
|
+
return 1
|
|
243
|
+
;;
|
|
244
|
+
kotlin-lsp)
|
|
245
|
+
find . -maxdepth 3 -name "*.kt" -o -name "*.kts" 2>/dev/null | head -1 | grep -q . && return 0
|
|
246
|
+
return 1
|
|
247
|
+
;;
|
|
248
|
+
csharp-lsp)
|
|
249
|
+
find . -maxdepth 3 -name "*.cs" 2>/dev/null | head -1 | grep -q . && return 0
|
|
250
|
+
return 1
|
|
251
|
+
;;
|
|
252
|
+
lua-lsp)
|
|
253
|
+
find . -maxdepth 3 -name "*.lua" 2>/dev/null | head -1 | grep -q . && return 0
|
|
254
|
+
return 1
|
|
255
|
+
;;
|
|
256
|
+
*)
|
|
257
|
+
# Non-LSP plugins always match
|
|
258
|
+
return 0
|
|
259
|
+
;;
|
|
260
|
+
esac
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
# Conflict detection
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
# Check for conflicting plugins already installed
|
|
268
|
+
# Returns 0 (no conflict) or 1 (conflict detected)
|
|
269
|
+
check_conflicts() {
|
|
270
|
+
local plugin_name="$1"
|
|
271
|
+
local installed
|
|
272
|
+
installed=$(get_installed_plugins)
|
|
273
|
+
|
|
274
|
+
# Define conflict groups
|
|
275
|
+
local -A conflict_groups
|
|
276
|
+
conflict_groups=(
|
|
277
|
+
["prettier-format"]="formatters"
|
|
278
|
+
["eslint-fix"]="formatters"
|
|
279
|
+
["biome-format"]="formatters"
|
|
280
|
+
["eslint-lsp"]="linters"
|
|
281
|
+
["biome-lsp"]="linters"
|
|
282
|
+
["commit-commands"]="git"
|
|
283
|
+
["git-helper"]="git"
|
|
284
|
+
["conventional-commits"]="git"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
local plugin_group="${conflict_groups[$plugin_name]}"
|
|
288
|
+
if [ -z "$plugin_group" ]; then
|
|
289
|
+
# Plugin is not in any conflict group
|
|
290
|
+
return 0
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
# Check if another plugin from the same group is installed
|
|
294
|
+
for key in "${!conflict_groups[@]}"; do
|
|
295
|
+
if [ "$key" = "$plugin_name" ]; then
|
|
296
|
+
continue
|
|
297
|
+
fi
|
|
298
|
+
if [ "${conflict_groups[$key]}" = "$plugin_group" ]; then
|
|
299
|
+
# Check if this conflicting plugin is installed
|
|
300
|
+
if echo "$installed" | grep -qE "^${key}(@|$)"; then
|
|
301
|
+
return 1
|
|
302
|
+
fi
|
|
303
|
+
fi
|
|
304
|
+
done
|
|
305
|
+
|
|
306
|
+
return 0
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# ---------------------------------------------------------------------------
|
|
310
|
+
# Filter pipeline
|
|
311
|
+
# ---------------------------------------------------------------------------
|
|
312
|
+
|
|
313
|
+
# Main filter: get marketplace plugins → exclude installed → apply LSP/conflict filters
|
|
314
|
+
# Returns JSON array of candidate plugins
|
|
315
|
+
filter_relevant() {
|
|
316
|
+
local marketplace_plugins
|
|
317
|
+
marketplace_plugins=$(get_marketplace_plugins)
|
|
318
|
+
|
|
319
|
+
if [ "$marketplace_plugins" = "[]" ] || [ -z "$marketplace_plugins" ]; then
|
|
320
|
+
echo "[]"
|
|
321
|
+
return
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
local installed
|
|
325
|
+
installed=$(get_installed_plugins)
|
|
326
|
+
|
|
327
|
+
local lsp_filter
|
|
328
|
+
lsp_filter=$(read_config "lsp_language_filter" "true")
|
|
329
|
+
|
|
330
|
+
local conflict_check
|
|
331
|
+
conflict_check=$(read_config "check_conflicts" "true")
|
|
332
|
+
|
|
333
|
+
local candidates="[]"
|
|
334
|
+
|
|
335
|
+
# Iterate marketplace plugins
|
|
336
|
+
local count
|
|
337
|
+
count=$(echo "$marketplace_plugins" | jq 'length' 2>/dev/null)
|
|
338
|
+
if [ -z "$count" ] || [ "$count" = "0" ]; then
|
|
339
|
+
echo "[]"
|
|
340
|
+
return
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
local i=0
|
|
344
|
+
while [ "$i" -lt "$count" ]; do
|
|
345
|
+
local plugin_json
|
|
346
|
+
plugin_json=$(echo "$marketplace_plugins" | jq ".[$i]" 2>/dev/null)
|
|
347
|
+
local plugin_name
|
|
348
|
+
plugin_name=$(echo "$plugin_json" | jq -r '.name' 2>/dev/null)
|
|
349
|
+
|
|
350
|
+
# Skip if already installed
|
|
351
|
+
if echo "$installed" | grep -qE "^${plugin_name}(@|$)"; then
|
|
352
|
+
i=$((i + 1))
|
|
353
|
+
continue
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
# Skip if LSP and language doesn't match
|
|
357
|
+
if [ "$lsp_filter" = "true" ]; then
|
|
358
|
+
if ! detect_language_match "$plugin_name"; then
|
|
359
|
+
i=$((i + 1))
|
|
360
|
+
continue
|
|
361
|
+
fi
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
# Skip if conflict detected
|
|
365
|
+
if [ "$conflict_check" = "true" ]; then
|
|
366
|
+
if ! check_conflicts "$plugin_name"; then
|
|
367
|
+
i=$((i + 1))
|
|
368
|
+
continue
|
|
369
|
+
fi
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
# Add to candidates
|
|
373
|
+
candidates=$(echo "$candidates" | jq --argjson p "$plugin_json" '. + [$p]' 2>/dev/null)
|
|
374
|
+
|
|
375
|
+
i=$((i + 1))
|
|
376
|
+
done
|
|
377
|
+
|
|
378
|
+
echo "$candidates"
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
# ---------------------------------------------------------------------------
|
|
382
|
+
# Installation
|
|
383
|
+
# ---------------------------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
# Install a single plugin via plugin-manager.sh
|
|
386
|
+
execute_install() {
|
|
387
|
+
local plugin_name="$1"
|
|
388
|
+
local marketplace
|
|
389
|
+
marketplace=$(read_config "marketplace" "claude-plugins-official")
|
|
390
|
+
|
|
391
|
+
if [ -f "$PLUGIN_MANAGER" ]; then
|
|
392
|
+
bash "$PLUGIN_MANAGER" install "${plugin_name}@${marketplace}" "trusted-auto-install" 2>&1
|
|
393
|
+
return $?
|
|
394
|
+
else
|
|
395
|
+
_log "error" "plugin-manager.sh not found"
|
|
396
|
+
return 1
|
|
397
|
+
fi
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
# Record install result in history.json and usage.json
|
|
401
|
+
record_install() {
|
|
402
|
+
local plugin_name="$1"
|
|
403
|
+
local status="$2" # success or failure
|
|
404
|
+
local timestamp
|
|
405
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
406
|
+
local date_only
|
|
407
|
+
date_only=$(date +"%Y-%m-%d")
|
|
408
|
+
|
|
409
|
+
# Add to trustedInstallLog in history.json
|
|
410
|
+
if [ -f "$HISTORY_FILE" ] && command -v jq &> /dev/null; then
|
|
411
|
+
jq --arg plugin "$plugin_name" \
|
|
412
|
+
--arg status "$status" \
|
|
413
|
+
--arg ts "$timestamp" \
|
|
414
|
+
--arg date "$date_only" \
|
|
415
|
+
'
|
|
416
|
+
.lastUpdated = $ts |
|
|
417
|
+
.trustedInstallLog = ((.trustedInstallLog // []) + [{
|
|
418
|
+
plugin: $plugin,
|
|
419
|
+
status: $status,
|
|
420
|
+
timestamp: $ts,
|
|
421
|
+
date: $date,
|
|
422
|
+
source: "trusted-auto-install"
|
|
423
|
+
}])
|
|
424
|
+
' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
|
|
425
|
+
fi
|
|
426
|
+
|
|
427
|
+
# Add to usage.json if success
|
|
428
|
+
if [ "$status" = "success" ] && [ -f "$USAGE_FILE" ] && command -v jq &> /dev/null; then
|
|
429
|
+
jq --arg plugin "$plugin_name" \
|
|
430
|
+
--arg date "$date_only" \
|
|
431
|
+
--arg ts "$timestamp" \
|
|
432
|
+
'
|
|
433
|
+
.lastUpdated = $ts |
|
|
434
|
+
.plugins[$plugin] = (.plugins[$plugin] // {
|
|
435
|
+
installed: $date,
|
|
436
|
+
usageCount: 0,
|
|
437
|
+
lastUsed: null,
|
|
438
|
+
source: "trusted-auto-install"
|
|
439
|
+
})
|
|
440
|
+
' "$USAGE_FILE" > "$USAGE_FILE.tmp" && mv "$USAGE_FILE.tmp" "$USAGE_FILE"
|
|
441
|
+
fi
|
|
442
|
+
|
|
443
|
+
_log_event "trusted_install" "{\"plugin\":\"$plugin_name\",\"status\":\"$status\"}"
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
# ---------------------------------------------------------------------------
|
|
447
|
+
# New release detection
|
|
448
|
+
# ---------------------------------------------------------------------------
|
|
449
|
+
|
|
450
|
+
# Compare cached catalog with current to detect new plugins
|
|
451
|
+
detect_new_releases() {
|
|
452
|
+
if [ ! -f "$CACHE_FILE" ]; then
|
|
453
|
+
echo "[]"
|
|
454
|
+
return
|
|
455
|
+
fi
|
|
456
|
+
|
|
457
|
+
local old_cache
|
|
458
|
+
old_cache=$(cat "$CACHE_FILE" 2>/dev/null)
|
|
459
|
+
if [ -z "$old_cache" ] || ! echo "$old_cache" | jq '.' &>/dev/null; then
|
|
460
|
+
echo "[]"
|
|
461
|
+
return
|
|
462
|
+
fi
|
|
463
|
+
|
|
464
|
+
local current
|
|
465
|
+
current=$(get_marketplace_plugins)
|
|
466
|
+
if [ -z "$current" ] || ! echo "$current" | jq '.' &>/dev/null; then
|
|
467
|
+
echo "[]"
|
|
468
|
+
return
|
|
469
|
+
fi
|
|
470
|
+
|
|
471
|
+
# Find plugins in current that were not in old cache
|
|
472
|
+
local new_releases
|
|
473
|
+
new_releases=$(jq -n --argjson old "$old_cache" --argjson cur "$current" '
|
|
474
|
+
($old | map(.name)) as $old_names |
|
|
475
|
+
[$cur[] | select(.name as $n | ($old_names | index($n)) == null)]
|
|
476
|
+
' 2>/dev/null)
|
|
477
|
+
|
|
478
|
+
if [ -z "$new_releases" ] || ! echo "$new_releases" | jq '.' &>/dev/null; then
|
|
479
|
+
echo "[]"
|
|
480
|
+
else
|
|
481
|
+
echo "$new_releases"
|
|
482
|
+
fi
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
# ---------------------------------------------------------------------------
|
|
486
|
+
# Reporting
|
|
487
|
+
# ---------------------------------------------------------------------------
|
|
488
|
+
|
|
489
|
+
# Write install report to .last-auto-install.json
|
|
490
|
+
write_install_report() {
|
|
491
|
+
local installed="$1"
|
|
492
|
+
local skipped="$2"
|
|
493
|
+
local new_releases="$3"
|
|
494
|
+
local errors="$4"
|
|
495
|
+
local timestamp
|
|
496
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
497
|
+
|
|
498
|
+
local installed_count
|
|
499
|
+
installed_count=$(echo "$installed" | jq 'length' 2>/dev/null || echo "0")
|
|
500
|
+
local skipped_count
|
|
501
|
+
skipped_count=$(echo "$skipped" | jq 'length' 2>/dev/null || echo "0")
|
|
502
|
+
local new_count
|
|
503
|
+
new_count=$(echo "$new_releases" | jq 'length' 2>/dev/null || echo "0")
|
|
504
|
+
|
|
505
|
+
jq -n \
|
|
506
|
+
--arg ts "$timestamp" \
|
|
507
|
+
--argjson installed "$installed" \
|
|
508
|
+
--argjson skipped "$skipped" \
|
|
509
|
+
--argjson newReleases "$new_releases" \
|
|
510
|
+
--argjson errors "$errors" \
|
|
511
|
+
--arg summary "Installed ${installed_count}, Skipped ${skipped_count}, New ${new_count}" \
|
|
512
|
+
'{
|
|
513
|
+
timestamp: $ts,
|
|
514
|
+
installed: $installed,
|
|
515
|
+
skipped: $skipped,
|
|
516
|
+
newReleases: $newReleases,
|
|
517
|
+
errors: $errors,
|
|
518
|
+
summary: $summary
|
|
519
|
+
}' > "$INSTALL_LOG"
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
# ---------------------------------------------------------------------------
|
|
523
|
+
# Main orchestrators
|
|
524
|
+
# ---------------------------------------------------------------------------
|
|
525
|
+
|
|
526
|
+
# Run: full auto-install cycle
|
|
527
|
+
run() {
|
|
528
|
+
# 1. Check enabled
|
|
529
|
+
if ! is_enabled; then
|
|
530
|
+
_log "info" "Trusted auto-install is disabled. Use '/scout trusted-install on' to enable."
|
|
531
|
+
return 0
|
|
532
|
+
fi
|
|
533
|
+
|
|
534
|
+
# 2. Ensure cache directory
|
|
535
|
+
mkdir -p "$CACHE_DIR"
|
|
536
|
+
|
|
537
|
+
# 3. Detect new releases (save old cache before refresh)
|
|
538
|
+
local new_releases="[]"
|
|
539
|
+
local detect_new
|
|
540
|
+
detect_new=$(read_config "detect_new_releases" "true")
|
|
541
|
+
if [ "$detect_new" = "true" ]; then
|
|
542
|
+
new_releases=$(detect_new_releases)
|
|
543
|
+
fi
|
|
544
|
+
|
|
545
|
+
# 4. Filter relevant candidates
|
|
546
|
+
local candidates
|
|
547
|
+
candidates=$(filter_relevant)
|
|
548
|
+
if [ -z "$candidates" ] || [ "$candidates" = "[]" ]; then
|
|
549
|
+
write_install_report "[]" "[]" "$new_releases" "[]"
|
|
550
|
+
_log "info" "No new trusted plugins to install"
|
|
551
|
+
return 0
|
|
552
|
+
fi
|
|
553
|
+
|
|
554
|
+
# 5. Install each candidate
|
|
555
|
+
local installed="[]"
|
|
556
|
+
local skipped="[]"
|
|
557
|
+
local errors="[]"
|
|
558
|
+
local count
|
|
559
|
+
count=$(echo "$candidates" | jq 'length' 2>/dev/null)
|
|
560
|
+
|
|
561
|
+
local i=0
|
|
562
|
+
while [ "$i" -lt "${count:-0}" ]; do
|
|
563
|
+
local plugin_name
|
|
564
|
+
plugin_name=$(echo "$candidates" | jq -r ".[$i].name" 2>/dev/null)
|
|
565
|
+
|
|
566
|
+
if [ -z "$plugin_name" ] || [ "$plugin_name" = "null" ]; then
|
|
567
|
+
i=$((i + 1))
|
|
568
|
+
continue
|
|
569
|
+
fi
|
|
570
|
+
|
|
571
|
+
local result
|
|
572
|
+
result=$(execute_install "$plugin_name" 2>&1)
|
|
573
|
+
local exit_code=$?
|
|
574
|
+
|
|
575
|
+
if [ $exit_code -eq 0 ]; then
|
|
576
|
+
record_install "$plugin_name" "success"
|
|
577
|
+
installed=$(echo "$installed" | jq --arg p "$plugin_name" '. + [$p]' 2>/dev/null)
|
|
578
|
+
_log "info" "Installed trusted plugin: $plugin_name"
|
|
579
|
+
else
|
|
580
|
+
record_install "$plugin_name" "failure"
|
|
581
|
+
errors=$(echo "$errors" | jq --arg p "$plugin_name" --arg e "$result" '. + [{plugin: $p, error: $e}]' 2>/dev/null)
|
|
582
|
+
_log "warn" "Failed to install: $plugin_name — $result"
|
|
583
|
+
fi
|
|
584
|
+
|
|
585
|
+
i=$((i + 1))
|
|
586
|
+
done
|
|
587
|
+
|
|
588
|
+
# 6. Write report
|
|
589
|
+
write_install_report "$installed" "$skipped" "$new_releases" "$errors"
|
|
590
|
+
|
|
591
|
+
# 7. Log summary
|
|
592
|
+
local installed_count
|
|
593
|
+
installed_count=$(echo "$installed" | jq 'length' 2>/dev/null || echo "0")
|
|
594
|
+
local error_count
|
|
595
|
+
error_count=$(echo "$errors" | jq 'length' 2>/dev/null || echo "0")
|
|
596
|
+
local new_count
|
|
597
|
+
new_count=$(echo "$new_releases" | jq 'length' 2>/dev/null || echo "0")
|
|
598
|
+
|
|
599
|
+
_log "info" "Trusted auto-install complete: ${installed_count} installed, ${error_count} errors, ${new_count} new releases"
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
# Check: dry-run mode (no installation)
|
|
603
|
+
check() {
|
|
604
|
+
echo "=== Trusted Auto-Install Check (Dry Run) ==="
|
|
605
|
+
echo ""
|
|
606
|
+
|
|
607
|
+
# Status
|
|
608
|
+
if is_enabled; then
|
|
609
|
+
echo "Status: ENABLED"
|
|
610
|
+
else
|
|
611
|
+
echo "Status: DISABLED"
|
|
612
|
+
fi
|
|
613
|
+
echo ""
|
|
614
|
+
|
|
615
|
+
# Trusted authors
|
|
616
|
+
local authors
|
|
617
|
+
authors=$(get_trusted_authors)
|
|
618
|
+
echo "Trusted Authors: $(echo "$authors" | jq -r 'join(", ")' 2>/dev/null)"
|
|
619
|
+
echo ""
|
|
620
|
+
|
|
621
|
+
# Candidates
|
|
622
|
+
local candidates
|
|
623
|
+
candidates=$(filter_relevant)
|
|
624
|
+
local count
|
|
625
|
+
count=$(echo "$candidates" | jq 'length' 2>/dev/null || echo "0")
|
|
626
|
+
|
|
627
|
+
echo "Candidates for installation: $count"
|
|
628
|
+
if [ "$count" != "0" ] && [ "$count" != "" ]; then
|
|
629
|
+
echo "$candidates" | jq -r '.[] | " - \(.name) (\(.author)) — \(.description)"' 2>/dev/null
|
|
630
|
+
fi
|
|
631
|
+
echo ""
|
|
632
|
+
|
|
633
|
+
# New releases
|
|
634
|
+
local detect_new
|
|
635
|
+
detect_new=$(read_config "detect_new_releases" "true")
|
|
636
|
+
if [ "$detect_new" = "true" ]; then
|
|
637
|
+
local new_releases
|
|
638
|
+
new_releases=$(detect_new_releases)
|
|
639
|
+
local new_count
|
|
640
|
+
new_count=$(echo "$new_releases" | jq 'length' 2>/dev/null || echo "0")
|
|
641
|
+
|
|
642
|
+
echo "New releases since last check: $new_count"
|
|
643
|
+
if [ "$new_count" != "0" ] && [ "$new_count" != "" ]; then
|
|
644
|
+
echo "$new_releases" | jq -r '.[] | " - \(.name) (\(.author)) — \(.description)"' 2>/dev/null
|
|
645
|
+
fi
|
|
646
|
+
fi
|
|
647
|
+
echo ""
|
|
648
|
+
|
|
649
|
+
# Installed plugins count
|
|
650
|
+
local installed_count
|
|
651
|
+
installed_count=$(get_installed_plugins | wc -l | tr -d ' ')
|
|
652
|
+
echo "Currently installed plugins: $installed_count"
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
# Enable trusted auto-install in both config.yaml and history.json
|
|
656
|
+
enable() {
|
|
657
|
+
# Update config.yaml
|
|
658
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
659
|
+
if grep -q "^ enabled:" "$CONFIG_FILE"; then
|
|
660
|
+
sed -i.bak 's/^ enabled:.*/ enabled: true/' "$CONFIG_FILE"
|
|
661
|
+
rm -f "${CONFIG_FILE}.bak"
|
|
662
|
+
fi
|
|
663
|
+
fi
|
|
664
|
+
|
|
665
|
+
# Update history.json
|
|
666
|
+
if [ -f "$HISTORY_FILE" ] && command -v jq &> /dev/null; then
|
|
667
|
+
local timestamp
|
|
668
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
669
|
+
jq --arg ts "$timestamp" '
|
|
670
|
+
.lastUpdated = $ts |
|
|
671
|
+
.preferences.trustedAutoInstall = true
|
|
672
|
+
' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
|
|
673
|
+
fi
|
|
674
|
+
|
|
675
|
+
_log "info" "Trusted auto-install enabled"
|
|
676
|
+
_log_event "trusted_install_toggle" "{\"action\":\"enable\"}"
|
|
677
|
+
echo "Trusted auto-install: ENABLED"
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
# Disable trusted auto-install in both config.yaml and history.json
|
|
681
|
+
disable() {
|
|
682
|
+
# Update config.yaml
|
|
683
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
684
|
+
if grep -q "^ enabled:" "$CONFIG_FILE"; then
|
|
685
|
+
sed -i.bak 's/^ enabled:.*/ enabled: false/' "$CONFIG_FILE"
|
|
686
|
+
rm -f "${CONFIG_FILE}.bak"
|
|
687
|
+
fi
|
|
688
|
+
fi
|
|
689
|
+
|
|
690
|
+
# Update history.json
|
|
691
|
+
if [ -f "$HISTORY_FILE" ] && command -v jq &> /dev/null; then
|
|
692
|
+
local timestamp
|
|
693
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
694
|
+
jq --arg ts "$timestamp" '
|
|
695
|
+
.lastUpdated = $ts |
|
|
696
|
+
.preferences.trustedAutoInstall = false
|
|
697
|
+
' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
|
|
698
|
+
fi
|
|
699
|
+
|
|
700
|
+
_log "info" "Trusted auto-install disabled"
|
|
701
|
+
_log_event "trusted_install_toggle" "{\"action\":\"disable\"}"
|
|
702
|
+
echo "Trusted auto-install: DISABLED"
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
# ---------------------------------------------------------------------------
|
|
706
|
+
# CLI interface
|
|
707
|
+
# ---------------------------------------------------------------------------
|
|
708
|
+
|
|
709
|
+
case "$1" in
|
|
710
|
+
run)
|
|
711
|
+
run
|
|
712
|
+
;;
|
|
713
|
+
check)
|
|
714
|
+
check
|
|
715
|
+
;;
|
|
716
|
+
status)
|
|
717
|
+
cat "$INSTALL_LOG" 2>/dev/null || echo "No install log found"
|
|
718
|
+
;;
|
|
719
|
+
enable)
|
|
720
|
+
enable
|
|
721
|
+
;;
|
|
722
|
+
disable)
|
|
723
|
+
disable
|
|
724
|
+
;;
|
|
725
|
+
*)
|
|
726
|
+
echo "Usage: $0 {run|check|status|enable|disable}"
|
|
727
|
+
echo ""
|
|
728
|
+
echo "Commands:"
|
|
729
|
+
echo " run Execute trusted auto-install (install missing trusted plugins)"
|
|
730
|
+
echo " check Dry-run: show what would be installed (no changes)"
|
|
731
|
+
echo " status Show last auto-install report"
|
|
732
|
+
echo " enable Enable trusted auto-install (config + history)"
|
|
733
|
+
echo " disable Disable trusted auto-install (config + history)"
|
|
734
|
+
echo ""
|
|
735
|
+
echo "Config: config.yaml → auto_install section"
|
|
736
|
+
echo "Log: $INSTALL_LOG"
|
|
737
|
+
;;
|
|
738
|
+
esac
|