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,281 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Plugin Scout - 프로젝트 분석기
|
|
3
|
+
# 프로젝트 구조와 의존성을 분석하여 추천에 활용
|
|
4
|
+
|
|
5
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
|
|
6
|
+
PROJECT_ROOT="${PWD}"
|
|
7
|
+
|
|
8
|
+
# 프로젝트 타입 감지
|
|
9
|
+
detect_project_type() {
|
|
10
|
+
local types=()
|
|
11
|
+
|
|
12
|
+
# Node.js/JavaScript/TypeScript
|
|
13
|
+
if [ -f "$PROJECT_ROOT/package.json" ]; then
|
|
14
|
+
types+=("nodejs")
|
|
15
|
+
if [ -f "$PROJECT_ROOT/tsconfig.json" ]; then
|
|
16
|
+
types+=("typescript")
|
|
17
|
+
fi
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Python
|
|
21
|
+
if [ -f "$PROJECT_ROOT/requirements.txt" ] || [ -f "$PROJECT_ROOT/pyproject.toml" ] || [ -f "$PROJECT_ROOT/setup.py" ]; then
|
|
22
|
+
types+=("python")
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Rust
|
|
26
|
+
if [ -f "$PROJECT_ROOT/Cargo.toml" ]; then
|
|
27
|
+
types+=("rust")
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Go
|
|
31
|
+
if [ -f "$PROJECT_ROOT/go.mod" ]; then
|
|
32
|
+
types+=("go")
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Java
|
|
36
|
+
if [ -f "$PROJECT_ROOT/pom.xml" ] || [ -f "$PROJECT_ROOT/build.gradle" ]; then
|
|
37
|
+
types+=("java")
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# PHP
|
|
41
|
+
if [ -f "$PROJECT_ROOT/composer.json" ]; then
|
|
42
|
+
types+=("php")
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Ruby
|
|
46
|
+
if [ -f "$PROJECT_ROOT/Gemfile" ]; then
|
|
47
|
+
types+=("ruby")
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Claude Code Plugin
|
|
51
|
+
if [ -f "$PROJECT_ROOT/plugin.json" ] || [ -d "$PROJECT_ROOT/.claude" ]; then
|
|
52
|
+
types+=("claude-plugin")
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# Docker
|
|
56
|
+
if [ -f "$PROJECT_ROOT/Dockerfile" ] || [ -f "$PROJECT_ROOT/docker-compose.yml" ]; then
|
|
57
|
+
types+=("docker")
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# 결과 출력 (JSON)
|
|
61
|
+
printf '%s\n' "${types[@]}" | jq -R -s -c 'split("\n") | map(select(. != ""))'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# 프레임워크 감지
|
|
65
|
+
detect_frameworks() {
|
|
66
|
+
local frameworks=()
|
|
67
|
+
|
|
68
|
+
# Node.js 프레임워크 (package.json에서)
|
|
69
|
+
if [ -f "$PROJECT_ROOT/package.json" ]; then
|
|
70
|
+
local deps=$(cat "$PROJECT_ROOT/package.json" | grep -E '"(dependencies|devDependencies)"' -A 100 | head -100)
|
|
71
|
+
|
|
72
|
+
# Frontend
|
|
73
|
+
echo "$deps" | grep -q '"react"' && frameworks+=("react")
|
|
74
|
+
echo "$deps" | grep -q '"vue"' && frameworks+=("vue")
|
|
75
|
+
echo "$deps" | grep -q '"@angular/core"' && frameworks+=("angular")
|
|
76
|
+
echo "$deps" | grep -q '"svelte"' && frameworks+=("svelte")
|
|
77
|
+
echo "$deps" | grep -q '"next"' && frameworks+=("nextjs")
|
|
78
|
+
echo "$deps" | grep -q '"nuxt"' && frameworks+=("nuxtjs")
|
|
79
|
+
echo "$deps" | grep -q '"astro"' && frameworks+=("astro")
|
|
80
|
+
|
|
81
|
+
# Backend
|
|
82
|
+
echo "$deps" | grep -q '"express"' && frameworks+=("express")
|
|
83
|
+
echo "$deps" | grep -q '"fastify"' && frameworks+=("fastify")
|
|
84
|
+
echo "$deps" | grep -q '"@nestjs/core"' && frameworks+=("nestjs")
|
|
85
|
+
echo "$deps" | grep -q '"hono"' && frameworks+=("hono")
|
|
86
|
+
echo "$deps" | grep -q '"koa"' && frameworks+=("koa")
|
|
87
|
+
|
|
88
|
+
# Testing
|
|
89
|
+
echo "$deps" | grep -q '"jest"' && frameworks+=("jest")
|
|
90
|
+
echo "$deps" | grep -q '"vitest"' && frameworks+=("vitest")
|
|
91
|
+
echo "$deps" | grep -q '"mocha"' && frameworks+=("mocha")
|
|
92
|
+
echo "$deps" | grep -q '"playwright"' && frameworks+=("playwright")
|
|
93
|
+
echo "$deps" | grep -q '"cypress"' && frameworks+=("cypress")
|
|
94
|
+
|
|
95
|
+
# Build tools
|
|
96
|
+
echo "$deps" | grep -q '"webpack"' && frameworks+=("webpack")
|
|
97
|
+
echo "$deps" | grep -q '"vite"' && frameworks+=("vite")
|
|
98
|
+
echo "$deps" | grep -q '"esbuild"' && frameworks+=("esbuild")
|
|
99
|
+
echo "$deps" | grep -q '"turbo"' && frameworks+=("turborepo")
|
|
100
|
+
|
|
101
|
+
# Styling
|
|
102
|
+
echo "$deps" | grep -q '"tailwindcss"' && frameworks+=("tailwind")
|
|
103
|
+
echo "$deps" | grep -q '"styled-components"' && frameworks+=("styled-components")
|
|
104
|
+
|
|
105
|
+
# State management
|
|
106
|
+
echo "$deps" | grep -q '"redux"' && frameworks+=("redux")
|
|
107
|
+
echo "$deps" | grep -q '"zustand"' && frameworks+=("zustand")
|
|
108
|
+
echo "$deps" | grep -q '"jotai"' && frameworks+=("jotai")
|
|
109
|
+
|
|
110
|
+
# Database/ORM
|
|
111
|
+
echo "$deps" | grep -q '"prisma"' && frameworks+=("prisma")
|
|
112
|
+
echo "$deps" | grep -q '"drizzle-orm"' && frameworks+=("drizzle")
|
|
113
|
+
echo "$deps" | grep -q '"mongoose"' && frameworks+=("mongoose")
|
|
114
|
+
echo "$deps" | grep -q '"typeorm"' && frameworks+=("typeorm")
|
|
115
|
+
|
|
116
|
+
# API
|
|
117
|
+
echo "$deps" | grep -q '"graphql"' && frameworks+=("graphql")
|
|
118
|
+
echo "$deps" | grep -q '"trpc"' && frameworks+=("trpc")
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# Python 프레임워크
|
|
122
|
+
if [ -f "$PROJECT_ROOT/requirements.txt" ]; then
|
|
123
|
+
local reqs=$(cat "$PROJECT_ROOT/requirements.txt")
|
|
124
|
+
echo "$reqs" | grep -qi "django" && frameworks+=("django")
|
|
125
|
+
echo "$reqs" | grep -qi "flask" && frameworks+=("flask")
|
|
126
|
+
echo "$reqs" | grep -qi "fastapi" && frameworks+=("fastapi")
|
|
127
|
+
echo "$reqs" | grep -qi "pytest" && frameworks+=("pytest")
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
printf '%s\n' "${frameworks[@]}" | jq -R -s -c 'split("\n") | map(select(. != ""))'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# 의존성 추출 (상위 20개)
|
|
134
|
+
get_dependencies() {
|
|
135
|
+
local deps=()
|
|
136
|
+
|
|
137
|
+
if [ -f "$PROJECT_ROOT/package.json" ] && command -v jq &> /dev/null; then
|
|
138
|
+
# dependencies
|
|
139
|
+
local prod_deps=$(jq -r '.dependencies // {} | keys[]' "$PROJECT_ROOT/package.json" 2>/dev/null | head -10)
|
|
140
|
+
# devDependencies
|
|
141
|
+
local dev_deps=$(jq -r '.devDependencies // {} | keys[]' "$PROJECT_ROOT/package.json" 2>/dev/null | head -10)
|
|
142
|
+
|
|
143
|
+
for dep in $prod_deps $dev_deps; do
|
|
144
|
+
deps+=("$dep")
|
|
145
|
+
done
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
printf '%s\n' "${deps[@]}" | jq -R -s -c 'split("\n") | map(select(. != "")) | unique | .[:20]'
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# 프로젝트 크기/복잡도 분석
|
|
152
|
+
analyze_complexity() {
|
|
153
|
+
local file_count=$(find "$PROJECT_ROOT" -type f -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.py" -o -name "*.rs" -o -name "*.go" 2>/dev/null | wc -l | tr -d ' ')
|
|
154
|
+
local dir_count=$(find "$PROJECT_ROOT" -type d 2>/dev/null | wc -l | tr -d ' ')
|
|
155
|
+
|
|
156
|
+
local size="small"
|
|
157
|
+
if [ "$file_count" -gt 100 ]; then
|
|
158
|
+
size="large"
|
|
159
|
+
elif [ "$file_count" -gt 30 ]; then
|
|
160
|
+
size="medium"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
echo "{\"files\": $file_count, \"directories\": $dir_count, \"size\": \"$size\"}"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# 추천할 플러그인 카테고리 결정
|
|
167
|
+
suggest_categories() {
|
|
168
|
+
local categories=()
|
|
169
|
+
local types=$(detect_project_type)
|
|
170
|
+
local frameworks=$(detect_frameworks)
|
|
171
|
+
|
|
172
|
+
# TypeScript 프로젝트면 development 추천
|
|
173
|
+
echo "$types" | grep -q "typescript" && categories+=("development")
|
|
174
|
+
|
|
175
|
+
# 테스트 프레임워크 있으면 testing 추천
|
|
176
|
+
echo "$frameworks" | grep -qE "(jest|vitest|pytest|playwright|cypress)" && categories+=("testing")
|
|
177
|
+
|
|
178
|
+
# Frontend 프레임워크 있으면 frontend 추천
|
|
179
|
+
echo "$frameworks" | grep -qE "(react|vue|angular|svelte)" && categories+=("frontend")
|
|
180
|
+
|
|
181
|
+
# Docker 있으면 devops 추천
|
|
182
|
+
echo "$types" | grep -q "docker" && categories+=("devops")
|
|
183
|
+
|
|
184
|
+
# Database/ORM 사용하면 database 추천
|
|
185
|
+
echo "$frameworks" | grep -qE "(prisma|drizzle|mongoose|typeorm)" && categories+=("database")
|
|
186
|
+
|
|
187
|
+
# GraphQL 사용하면 api 추천
|
|
188
|
+
echo "$frameworks" | grep -qE "(graphql|trpc)" && categories+=("api")
|
|
189
|
+
|
|
190
|
+
# AI/ML 관련이면 ai 추천
|
|
191
|
+
echo "$frameworks" | grep -qE "(openai|langchain|huggingface)" && categories+=("ai")
|
|
192
|
+
|
|
193
|
+
# 기본적으로 productivity 추천
|
|
194
|
+
categories+=("productivity")
|
|
195
|
+
|
|
196
|
+
printf '%s\n' "${categories[@]}" | jq -R -s -c 'split("\n") | map(select(. != "")) | unique'
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# 의존성 기반 플러그인 추천
|
|
200
|
+
recommend_by_deps() {
|
|
201
|
+
local deps=$(get_dependencies)
|
|
202
|
+
|
|
203
|
+
# 의존성 → 플러그인 매핑
|
|
204
|
+
local recommendations=()
|
|
205
|
+
|
|
206
|
+
echo "$deps" | jq -r '.[]' 2>/dev/null | while read dep; do
|
|
207
|
+
case "$dep" in
|
|
208
|
+
typescript|@types/*) echo "typescript-lsp" ;;
|
|
209
|
+
react|react-dom) echo "react-devtools" ;;
|
|
210
|
+
eslint) echo "eslint-fix" ;;
|
|
211
|
+
prettier) echo "prettier-format" ;;
|
|
212
|
+
jest|vitest) echo "test-runner" ;;
|
|
213
|
+
prisma) echo "prisma-helper" ;;
|
|
214
|
+
docker*) echo "docker-helper" ;;
|
|
215
|
+
@sentry/*|sentry) echo "sentry" ;;
|
|
216
|
+
@slack/*|slack*) echo "slack" ;;
|
|
217
|
+
graphql) echo "graphql-helper" ;;
|
|
218
|
+
tailwindcss) echo "tailwind-assist" ;;
|
|
219
|
+
esac
|
|
220
|
+
done | sort -u | jq -R -s -c 'split("\n") | map(select(. != ""))'
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# 전체 프로젝트 분석 (JSON 출력)
|
|
224
|
+
full_analysis() {
|
|
225
|
+
local types=$(detect_project_type)
|
|
226
|
+
local frameworks=$(detect_frameworks)
|
|
227
|
+
local deps=$(get_dependencies)
|
|
228
|
+
local complexity=$(analyze_complexity)
|
|
229
|
+
local suggested=$(suggest_categories)
|
|
230
|
+
local recommended=$(recommend_by_deps)
|
|
231
|
+
|
|
232
|
+
cat <<EOF
|
|
233
|
+
{
|
|
234
|
+
"projectRoot": "$PROJECT_ROOT",
|
|
235
|
+
"analyzedAt": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
|
236
|
+
"projectTypes": $types,
|
|
237
|
+
"frameworks": $frameworks,
|
|
238
|
+
"dependencies": $deps,
|
|
239
|
+
"complexity": $complexity,
|
|
240
|
+
"suggestedCategories": $suggested,
|
|
241
|
+
"recommendedPlugins": $recommended
|
|
242
|
+
}
|
|
243
|
+
EOF
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# CLI 인터페이스
|
|
247
|
+
case "$1" in
|
|
248
|
+
types)
|
|
249
|
+
detect_project_type
|
|
250
|
+
;;
|
|
251
|
+
frameworks)
|
|
252
|
+
detect_frameworks
|
|
253
|
+
;;
|
|
254
|
+
deps|dependencies)
|
|
255
|
+
get_dependencies
|
|
256
|
+
;;
|
|
257
|
+
complexity)
|
|
258
|
+
analyze_complexity
|
|
259
|
+
;;
|
|
260
|
+
suggest)
|
|
261
|
+
suggest_categories
|
|
262
|
+
;;
|
|
263
|
+
recommend)
|
|
264
|
+
recommend_by_deps
|
|
265
|
+
;;
|
|
266
|
+
full|analyze)
|
|
267
|
+
full_analysis
|
|
268
|
+
;;
|
|
269
|
+
*)
|
|
270
|
+
echo "Usage: $0 {types|frameworks|deps|complexity|suggest|recommend|full}"
|
|
271
|
+
echo ""
|
|
272
|
+
echo "Commands:"
|
|
273
|
+
echo " types - Detect project types (nodejs, typescript, python, etc.)"
|
|
274
|
+
echo " frameworks - Detect frameworks (react, express, jest, etc.)"
|
|
275
|
+
echo " deps - Extract dependencies"
|
|
276
|
+
echo " complexity - Analyze project size/complexity"
|
|
277
|
+
echo " suggest - Suggest plugin categories"
|
|
278
|
+
echo " recommend - Recommend plugins based on dependencies"
|
|
279
|
+
echo " full - Full analysis (JSON)"
|
|
280
|
+
;;
|
|
281
|
+
esac
|
|
@@ -0,0 +1,290 @@
|
|
|
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
|
+
SESSION_FILE="$DATA_DIR/.session"
|
|
9
|
+
RECOMMENDATION_LOG="$DATA_DIR/.recommendations"
|
|
10
|
+
|
|
11
|
+
# 무음 모드 설정
|
|
12
|
+
set_quiet_mode() {
|
|
13
|
+
local mode="$1" # on, off, toggle
|
|
14
|
+
|
|
15
|
+
if [ ! -f "$HISTORY_FILE" ]; then
|
|
16
|
+
echo "History file not found"
|
|
17
|
+
return 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
if ! command -v jq &> /dev/null; then
|
|
21
|
+
echo "jq required"
|
|
22
|
+
return 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
local current=$(jq -r '.preferences.quietMode // false' "$HISTORY_FILE")
|
|
26
|
+
|
|
27
|
+
case "$mode" in
|
|
28
|
+
on)
|
|
29
|
+
jq '.preferences.quietMode = true' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
|
|
30
|
+
echo "Quiet mode enabled. No recommendations will be shown."
|
|
31
|
+
;;
|
|
32
|
+
off)
|
|
33
|
+
jq '.preferences.quietMode = false' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
|
|
34
|
+
echo "Quiet mode disabled. Recommendations will be shown."
|
|
35
|
+
;;
|
|
36
|
+
toggle)
|
|
37
|
+
if [ "$current" = "true" ]; then
|
|
38
|
+
jq '.preferences.quietMode = false' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
|
|
39
|
+
echo "Quiet mode disabled."
|
|
40
|
+
else
|
|
41
|
+
jq '.preferences.quietMode = true' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
|
|
42
|
+
echo "Quiet mode enabled."
|
|
43
|
+
fi
|
|
44
|
+
;;
|
|
45
|
+
status)
|
|
46
|
+
if [ "$current" = "true" ]; then
|
|
47
|
+
echo "on"
|
|
48
|
+
else
|
|
49
|
+
echo "off"
|
|
50
|
+
fi
|
|
51
|
+
;;
|
|
52
|
+
esac
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# 추천 가능 여부 확인 (빈도 제한)
|
|
56
|
+
can_show_recommendation() {
|
|
57
|
+
local limit_type="${1:-session}" # session, daily, weekly
|
|
58
|
+
|
|
59
|
+
# 무음 모드 확인
|
|
60
|
+
if [ -f "$HISTORY_FILE" ] && command -v jq &> /dev/null; then
|
|
61
|
+
local quiet=$(jq -r '.preferences.quietMode // false' "$HISTORY_FILE")
|
|
62
|
+
if [ "$quiet" = "true" ]; then
|
|
63
|
+
echo "blocked:quiet_mode"
|
|
64
|
+
return
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# 추천 로그 확인
|
|
69
|
+
if [ ! -f "$RECOMMENDATION_LOG" ]; then
|
|
70
|
+
echo "true"
|
|
71
|
+
return
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
local today=$(date +"%Y-%m-%d")
|
|
75
|
+
local session_id=""
|
|
76
|
+
if [ -f "$SESSION_FILE" ]; then
|
|
77
|
+
session_id=$(jq -r '.sessionId // ""' "$SESSION_FILE" 2>/dev/null)
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
case "$limit_type" in
|
|
81
|
+
session)
|
|
82
|
+
# 세션당 1회 제한
|
|
83
|
+
local session_count=$(grep -c "session:$session_id" "$RECOMMENDATION_LOG" 2>/dev/null || echo "0")
|
|
84
|
+
local max_per_session=$(jq -r '.preferences.maxRecommendationsPerSession // 1' "$HISTORY_FILE" 2>/dev/null || echo "1")
|
|
85
|
+
if [ "$session_count" -ge "$max_per_session" ]; then
|
|
86
|
+
echo "blocked:session_limit"
|
|
87
|
+
return
|
|
88
|
+
fi
|
|
89
|
+
;;
|
|
90
|
+
daily)
|
|
91
|
+
# 하루 N회 제한
|
|
92
|
+
local daily_count=$(grep -c "date:$today" "$RECOMMENDATION_LOG" 2>/dev/null || echo "0")
|
|
93
|
+
local max_per_day=$(jq -r '.preferences.maxRecommendationsPerDay // 3' "$HISTORY_FILE" 2>/dev/null || echo "3")
|
|
94
|
+
if [ "$daily_count" -ge "$max_per_day" ]; then
|
|
95
|
+
echo "blocked:daily_limit"
|
|
96
|
+
return
|
|
97
|
+
fi
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
100
|
+
|
|
101
|
+
echo "true"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# 추천 기록
|
|
105
|
+
record_recommendation() {
|
|
106
|
+
local plugin_count="$1"
|
|
107
|
+
local trigger="$2" # post-task, manual, auto
|
|
108
|
+
|
|
109
|
+
local today=$(date +"%Y-%m-%d")
|
|
110
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
111
|
+
local session_id=""
|
|
112
|
+
|
|
113
|
+
if [ -f "$SESSION_FILE" ]; then
|
|
114
|
+
session_id=$(jq -r '.sessionId // ""' "$SESSION_FILE" 2>/dev/null)
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
echo "date:$today session:$session_id time:$timestamp count:$plugin_count trigger:$trigger" >> "$RECOMMENDATION_LOG"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# 스마트 타이밍 체크
|
|
121
|
+
check_timing() {
|
|
122
|
+
local event="$1" # commit, pr, task-complete
|
|
123
|
+
|
|
124
|
+
if [ ! -f "$HISTORY_FILE" ] || ! command -v jq &> /dev/null; then
|
|
125
|
+
echo "true"
|
|
126
|
+
return
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
local smart_timing=$(jq -r '.preferences.smartTiming // {}' "$HISTORY_FILE")
|
|
130
|
+
local only_after_commit=$(echo "$smart_timing" | jq -r '.onlyAfterCommit // false')
|
|
131
|
+
local only_after_pr=$(echo "$smart_timing" | jq -r '.onlyAfterPR // false')
|
|
132
|
+
|
|
133
|
+
# 특정 이벤트 후에만 추천하도록 설정된 경우
|
|
134
|
+
if [ "$only_after_commit" = "true" ] && [ "$event" != "commit" ]; then
|
|
135
|
+
echo "blocked:not_after_commit"
|
|
136
|
+
return
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
if [ "$only_after_pr" = "true" ] && [ "$event" != "pr" ]; then
|
|
140
|
+
echo "blocked:not_after_pr"
|
|
141
|
+
return
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
echo "true"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# 미니 모드 출력
|
|
148
|
+
show_mini_notification() {
|
|
149
|
+
local count="$1"
|
|
150
|
+
|
|
151
|
+
if [ "$count" -eq 0 ]; then
|
|
152
|
+
return
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
echo "💡 $count개의 플러그인 추천이 있습니다. \`/scout\`로 확인하세요."
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# 추천 빈도 설정
|
|
159
|
+
set_frequency() {
|
|
160
|
+
local key="$1"
|
|
161
|
+
local value="$2"
|
|
162
|
+
|
|
163
|
+
if [ ! -f "$HISTORY_FILE" ] || ! command -v jq &> /dev/null; then
|
|
164
|
+
echo "Error: history file or jq not found"
|
|
165
|
+
return 1
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
case "$key" in
|
|
169
|
+
session)
|
|
170
|
+
jq --argjson val "$value" '.preferences.maxRecommendationsPerSession = $val' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
|
|
171
|
+
echo "Max recommendations per session: $value"
|
|
172
|
+
;;
|
|
173
|
+
daily)
|
|
174
|
+
jq --argjson val "$value" '.preferences.maxRecommendationsPerDay = $val' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
|
|
175
|
+
echo "Max recommendations per day: $value"
|
|
176
|
+
;;
|
|
177
|
+
cooldown)
|
|
178
|
+
jq --argjson val "$value" '.preferences.recommendationCooldown = $val' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
|
|
179
|
+
echo "Recommendation cooldown: $value minutes"
|
|
180
|
+
;;
|
|
181
|
+
esac
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# 스마트 타이밍 설정
|
|
185
|
+
set_smart_timing() {
|
|
186
|
+
local key="$1"
|
|
187
|
+
local value="$2"
|
|
188
|
+
|
|
189
|
+
if [ ! -f "$HISTORY_FILE" ] || ! command -v jq &> /dev/null; then
|
|
190
|
+
echo "Error: history file or jq not found"
|
|
191
|
+
return 1
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
case "$key" in
|
|
195
|
+
after-commit)
|
|
196
|
+
jq --argjson val "$value" '.preferences.smartTiming.onlyAfterCommit = $val' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
|
|
197
|
+
echo "Only recommend after commit: $value"
|
|
198
|
+
;;
|
|
199
|
+
after-pr)
|
|
200
|
+
jq --argjson val "$value" '.preferences.smartTiming.onlyAfterPR = $val' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
|
|
201
|
+
echo "Only recommend after PR: $value"
|
|
202
|
+
;;
|
|
203
|
+
esac
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
# 추천 로그 정리 (7일 이상 된 기록 삭제)
|
|
207
|
+
cleanup_logs() {
|
|
208
|
+
if [ ! -f "$RECOMMENDATION_LOG" ]; then
|
|
209
|
+
return
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
local week_ago=$(date -v-7d +"%Y-%m-%d" 2>/dev/null || date -d "7 days ago" +"%Y-%m-%d" 2>/dev/null)
|
|
213
|
+
|
|
214
|
+
if [ -n "$week_ago" ]; then
|
|
215
|
+
grep -v "date:$week_ago" "$RECOMMENDATION_LOG" | grep -v "date:$(date -v-8d +%Y-%m-%d 2>/dev/null)" > "$RECOMMENDATION_LOG.tmp" 2>/dev/null
|
|
216
|
+
mv "$RECOMMENDATION_LOG.tmp" "$RECOMMENDATION_LOG" 2>/dev/null
|
|
217
|
+
fi
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# 상태 요약
|
|
221
|
+
status() {
|
|
222
|
+
echo "=== Recommendation Controller Status ==="
|
|
223
|
+
echo ""
|
|
224
|
+
|
|
225
|
+
if [ -f "$HISTORY_FILE" ] && command -v jq &> /dev/null; then
|
|
226
|
+
local quiet=$(jq -r '.preferences.quietMode // false' "$HISTORY_FILE")
|
|
227
|
+
local max_session=$(jq -r '.preferences.maxRecommendationsPerSession // 1' "$HISTORY_FILE")
|
|
228
|
+
local max_daily=$(jq -r '.preferences.maxRecommendationsPerDay // 3' "$HISTORY_FILE")
|
|
229
|
+
local cooldown=$(jq -r '.preferences.recommendationCooldown // 30' "$HISTORY_FILE")
|
|
230
|
+
|
|
231
|
+
echo "Quiet Mode: $quiet"
|
|
232
|
+
echo "Max per Session: $max_session"
|
|
233
|
+
echo "Max per Day: $max_daily"
|
|
234
|
+
echo "Cooldown: $cooldown min"
|
|
235
|
+
echo ""
|
|
236
|
+
|
|
237
|
+
if [ -f "$RECOMMENDATION_LOG" ]; then
|
|
238
|
+
local today=$(date +"%Y-%m-%d")
|
|
239
|
+
local today_count=$(grep -c "date:$today" "$RECOMMENDATION_LOG" 2>/dev/null || echo "0")
|
|
240
|
+
echo "Today's Recommendations: $today_count"
|
|
241
|
+
fi
|
|
242
|
+
else
|
|
243
|
+
echo "Configuration not available"
|
|
244
|
+
fi
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# CLI 인터페이스
|
|
248
|
+
case "$1" in
|
|
249
|
+
quiet)
|
|
250
|
+
set_quiet_mode "${2:-status}"
|
|
251
|
+
;;
|
|
252
|
+
can-recommend)
|
|
253
|
+
can_show_recommendation "${2:-session}"
|
|
254
|
+
;;
|
|
255
|
+
record)
|
|
256
|
+
record_recommendation "$2" "$3"
|
|
257
|
+
;;
|
|
258
|
+
timing)
|
|
259
|
+
check_timing "$2"
|
|
260
|
+
;;
|
|
261
|
+
mini)
|
|
262
|
+
show_mini_notification "$2"
|
|
263
|
+
;;
|
|
264
|
+
frequency)
|
|
265
|
+
set_frequency "$2" "$3"
|
|
266
|
+
;;
|
|
267
|
+
smart-timing)
|
|
268
|
+
set_smart_timing "$2" "$3"
|
|
269
|
+
;;
|
|
270
|
+
cleanup)
|
|
271
|
+
cleanup_logs
|
|
272
|
+
;;
|
|
273
|
+
status)
|
|
274
|
+
status
|
|
275
|
+
;;
|
|
276
|
+
*)
|
|
277
|
+
echo "Usage: $0 {quiet|can-recommend|record|timing|mini|frequency|smart-timing|cleanup|status}"
|
|
278
|
+
echo ""
|
|
279
|
+
echo "Commands:"
|
|
280
|
+
echo " quiet [on|off|toggle|status] - Set quiet mode"
|
|
281
|
+
echo " can-recommend [session|daily] - Check if can show recommendation"
|
|
282
|
+
echo " record <count> <trigger> - Record a recommendation"
|
|
283
|
+
echo " timing <event> - Check smart timing"
|
|
284
|
+
echo " mini <count> - Show mini notification"
|
|
285
|
+
echo " frequency <session|daily> <value> - Set frequency limit"
|
|
286
|
+
echo " smart-timing <key> <value> - Set smart timing"
|
|
287
|
+
echo " cleanup - Clean old logs"
|
|
288
|
+
echo " status - Show status"
|
|
289
|
+
;;
|
|
290
|
+
esac
|