smoothie-code 1.1.0 → 2.0.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/README.md +39 -57
- package/auto-review-hook.sh +138 -0
- package/bin/smoothie +221 -16
- package/config.json +3 -1
- package/dist/blend-cli.js +69 -10
- package/dist/index.js +98 -14
- package/dist/review-cli.d.ts +12 -0
- package/dist/review-cli.js +244 -0
- package/dist/select-models.js +3 -1
- package/gemini-review-hook.sh +149 -0
- package/install.sh +142 -23
- package/package.json +12 -1
- package/plan-hook.sh +1 -1
- package/{pr-blend-hook.sh → pr-review-hook.sh} +43 -9
- package/auto-blend-hook.sh +0 -103
- package/banner-v2.svg +0 -307
- package/docs/banner.svg +0 -307
- package/docs/favicon.png +0 -0
- package/docs/favicon.svg +0 -17
- package/docs/index.html +0 -319
- package/icon.png +0 -0
- package/icon.svg +0 -17
- package/src/blend-cli.ts +0 -219
- package/src/index.ts +0 -367
- package/src/select-models.ts +0 -318
- package/tsconfig.json +0 -14
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# gemini-review-hook.sh — BeforeTool hook for exit_plan_mode (Gemini CLI)
|
|
4
|
+
#
|
|
5
|
+
# Same as auto-review-hook.sh but adapted for Gemini CLI:
|
|
6
|
+
# - BeforeTool instead of PreToolUse
|
|
7
|
+
# - exit_plan_mode instead of ExitPlanMode
|
|
8
|
+
# - Reads plan from tool_input.plan_path instead of tailing transcript
|
|
9
|
+
#
|
|
10
|
+
# Caveat: only fires when the agent calls exit_plan_mode itself,
|
|
11
|
+
# not when user manually toggles via /plan or Shift+Tab.
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
|
|
16
|
+
# Read hook input from stdin
|
|
17
|
+
INPUT=$(cat)
|
|
18
|
+
|
|
19
|
+
# Check if auto-review is enabled in config
|
|
20
|
+
if [ -f "$SCRIPT_DIR/config.json" ]; then
|
|
21
|
+
AUTO_ENABLED=$(node -e "
|
|
22
|
+
try {
|
|
23
|
+
const c = JSON.parse(require('fs').readFileSync('$SCRIPT_DIR/config.json','utf8'));
|
|
24
|
+
console.log(c.auto_review === true ? 'true' : 'false');
|
|
25
|
+
} catch(e) { console.log('false'); }
|
|
26
|
+
" 2>/dev/null)
|
|
27
|
+
|
|
28
|
+
if [ "$AUTO_ENABLED" != "true" ]; then
|
|
29
|
+
echo '{"hookSpecificOutput":{"hookEventName":"BeforeTool","permissionDecision":"allow"}}'
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Try to read plan from tool_input.plan_path (Gemini provides this)
|
|
35
|
+
PLAN_PATH=$(echo "$INPUT" | python3 -c "
|
|
36
|
+
import sys, json
|
|
37
|
+
try:
|
|
38
|
+
d = json.load(sys.stdin)
|
|
39
|
+
print(d.get('tool_input', {}).get('plan_path', ''))
|
|
40
|
+
except:
|
|
41
|
+
print('')
|
|
42
|
+
" 2>/dev/null)
|
|
43
|
+
|
|
44
|
+
PLAN_CONTEXT=""
|
|
45
|
+
if [ -n "$PLAN_PATH" ] && [ -f "$PLAN_PATH" ]; then
|
|
46
|
+
PLAN_CONTEXT=$(cat "$PLAN_PATH" 2>/dev/null | head -c 16000)
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Fallback: read from transcript if plan_path not available
|
|
50
|
+
if [ -z "$PLAN_CONTEXT" ]; then
|
|
51
|
+
TRANSCRIPT_PATH=$(echo "$INPUT" | python3 -c "
|
|
52
|
+
import sys, json
|
|
53
|
+
try:
|
|
54
|
+
d = json.load(sys.stdin)
|
|
55
|
+
print(d.get('transcript_path', ''))
|
|
56
|
+
except:
|
|
57
|
+
print('')
|
|
58
|
+
" 2>/dev/null)
|
|
59
|
+
|
|
60
|
+
if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
|
|
61
|
+
PLAN_CONTEXT=$(tail -c 4000 "$TRANSCRIPT_PATH" 2>/dev/null)
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
if [ -z "$PLAN_CONTEXT" ]; then
|
|
66
|
+
echo '{"hookSpecificOutput":{"hookEventName":"BeforeTool","permissionDecision":"allow"}}'
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# Build the review prompt
|
|
71
|
+
REVIEW_PROMPT="You are reviewing a plan that Gemini CLI generated. Analyze it for:
|
|
72
|
+
- Missing steps or edge cases
|
|
73
|
+
- Better approaches or optimizations
|
|
74
|
+
- Potential bugs or issues
|
|
75
|
+
- Security concerns
|
|
76
|
+
|
|
77
|
+
Here is the plan:
|
|
78
|
+
|
|
79
|
+
$PLAN_CONTEXT
|
|
80
|
+
|
|
81
|
+
Provide concise, actionable feedback. Focus only on things that should change."
|
|
82
|
+
|
|
83
|
+
# Run the blend (progress shows on stderr, results on stdout)
|
|
84
|
+
BLEND_RESULTS=$(echo "$REVIEW_PROMPT" | node "$SCRIPT_DIR/dist/review-cli.js" 2>/dev/stderr)
|
|
85
|
+
|
|
86
|
+
# Generate share link (metadata only, no raw content)
|
|
87
|
+
SHARE_URL=""
|
|
88
|
+
SHARE_PARAMS=$(echo "$BLEND_RESULTS" | node -e "
|
|
89
|
+
let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
|
|
90
|
+
try {
|
|
91
|
+
const r=JSON.parse(d);
|
|
92
|
+
const models=r.results.map(m=>m.model).join(',');
|
|
93
|
+
const times=r.results.map(m=>m.elapsed_s||0).join(',');
|
|
94
|
+
const tokens=r.results.map(m=>(m.tokens&&m.tokens.total)||0).join(',');
|
|
95
|
+
console.log('models='+encodeURIComponent(models)+'×='+encodeURIComponent(times)+'&tokens='+encodeURIComponent(tokens)+'&type=plan');
|
|
96
|
+
} catch { console.log(''); }
|
|
97
|
+
});
|
|
98
|
+
" 2>/dev/null)
|
|
99
|
+
|
|
100
|
+
if [ -n "$SHARE_PARAMS" ]; then
|
|
101
|
+
SHARE_URL=$(curl -s --max-time 5 --connect-timeout 3 -X POST "https://api.smoothiecode.com/api/share" \
|
|
102
|
+
-H "Content-Type: application/json" \
|
|
103
|
+
-d "{\"type\":\"report\",\"data\":\"$SHARE_PARAMS\"}" 2>/dev/null | node -e "
|
|
104
|
+
let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
|
|
105
|
+
try{console.log(JSON.parse(d).url)}catch{console.log('')}
|
|
106
|
+
});
|
|
107
|
+
" 2>/dev/null) || true
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
if [ -z "$BLEND_RESULTS" ]; then
|
|
111
|
+
echo '{"hookSpecificOutput":{"hookEventName":"BeforeTool","permissionDecision":"allow"}}'
|
|
112
|
+
exit 0
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Build the additionalContext string
|
|
116
|
+
CONTEXT="🧃 Smoothie auto-review results — multiple models reviewed this plan:
|
|
117
|
+
|
|
118
|
+
$BLEND_RESULTS
|
|
119
|
+
|
|
120
|
+
IMPORTANT — You must do TWO things:
|
|
121
|
+
|
|
122
|
+
1. FIRST, show a Smoothie report table. Use elapsed_s and tokens from each result:
|
|
123
|
+
|
|
124
|
+
🧃 Smoothie reviewed this plan (N models, Xs total):
|
|
125
|
+
|
|
126
|
+
| Model | Time | Tokens | Verdict | Feedback |
|
|
127
|
+
|-------|------|--------|---------|----------|
|
|
128
|
+
| ModelName | 3.1s | 1,847 | useful | one-line summary |
|
|
129
|
+
| ModelName | 8.2s | 2,104 | generic | one-line summary |
|
|
130
|
+
(skip errored/empty models. Verdict = useful, generic, or irrelevant)
|
|
131
|
+
$([ -n "$SHARE_URL" ] && echo "
|
|
132
|
+
Share this report: $SHARE_URL")
|
|
133
|
+
|
|
134
|
+
2. THEN proceed with implementation as planned, incorporating valid Smoothie suggestions as you go. Do NOT stop to revise — start coding immediately."
|
|
135
|
+
|
|
136
|
+
# Return: allow exit_plan_mode but inject blend results
|
|
137
|
+
node -e "
|
|
138
|
+
const ctx = $(echo "$CONTEXT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null);
|
|
139
|
+
console.log(JSON.stringify({
|
|
140
|
+
hookSpecificOutput: {
|
|
141
|
+
hookEventName: 'BeforeTool',
|
|
142
|
+
permissionDecision: 'allow',
|
|
143
|
+
permissionDecisionReason: 'Smoothie auto-review completed',
|
|
144
|
+
additionalContext: ctx
|
|
145
|
+
}
|
|
146
|
+
}));
|
|
147
|
+
"
|
|
148
|
+
|
|
149
|
+
exit 0
|
package/install.sh
CHANGED
|
@@ -11,7 +11,7 @@ B='\033[1m' # bold
|
|
|
11
11
|
N='\033[0m' # reset
|
|
12
12
|
|
|
13
13
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
-
TOTAL_STEPS=
|
|
14
|
+
TOTAL_STEPS=8
|
|
15
15
|
STEP=0
|
|
16
16
|
|
|
17
17
|
step() {
|
|
@@ -71,11 +71,13 @@ step "Detecting platform"
|
|
|
71
71
|
HAS_CLAUDE=$(command -v claude &>/dev/null && echo "yes" || echo "no")
|
|
72
72
|
HAS_CODEX=$(command -v codex &>/dev/null && echo "yes" || echo "no")
|
|
73
73
|
HAS_GEMINI=$(command -v gemini &>/dev/null && echo "yes" || echo "no")
|
|
74
|
+
HAS_CURSOR=$([ -d "$HOME/.cursor" ] && echo "yes" || echo "no")
|
|
74
75
|
|
|
75
76
|
DETECTED=()
|
|
76
77
|
[ "$HAS_CLAUDE" = "yes" ] && DETECTED+=("claude:Claude Code")
|
|
77
78
|
[ "$HAS_CODEX" = "yes" ] && DETECTED+=("codex:Codex CLI")
|
|
78
79
|
[ "$HAS_GEMINI" = "yes" ] && DETECTED+=("gemini:Gemini CLI")
|
|
80
|
+
[ "$HAS_CURSOR" = "yes" ] && DETECTED+=("cursor:Cursor")
|
|
79
81
|
|
|
80
82
|
if [ ${#DETECTED[@]} -eq 0 ]; then
|
|
81
83
|
echo -e " ${Y}No AI CLI detected. Defaulting to Claude Code.${N}"
|
|
@@ -102,13 +104,13 @@ if [ "$PLATFORM" != "codex" ]; then
|
|
|
102
104
|
step "Setting up Codex ${D}(optional)${N}"
|
|
103
105
|
|
|
104
106
|
echo ""
|
|
105
|
-
echo -e " ${D}Codex adds OpenAI's coding model to the
|
|
107
|
+
echo -e " ${D}Codex adds OpenAI's coding model to the review.${N}"
|
|
106
108
|
echo -e " ${D}Requires a ChatGPT account. Skip if you only want OpenRouter.${N}"
|
|
107
109
|
echo ""
|
|
108
110
|
read -p " Set up Codex? [Y/n]: " SETUP_CODEX
|
|
109
111
|
|
|
110
112
|
if [[ "$SETUP_CODEX" =~ ^[Nn]$ ]]; then
|
|
111
|
-
echo -e " ${D}Skipped —
|
|
113
|
+
echo -e " ${D}Skipped — review will use OpenRouter models only${N}"
|
|
112
114
|
else
|
|
113
115
|
if ! command -v codex &>/dev/null; then
|
|
114
116
|
npm install -g @openai/codex 2>/dev/null &
|
|
@@ -154,29 +156,59 @@ step "Choosing models"
|
|
|
154
156
|
echo ""
|
|
155
157
|
node "$SCRIPT_DIR/dist/select-models.js" "$OPENROUTER_KEY" "$SCRIPT_DIR/config.json"
|
|
156
158
|
|
|
157
|
-
# ─── Step 6: Auto-
|
|
159
|
+
# ─── Step 6: Auto-review ──────────────────────────────────────────────
|
|
158
160
|
step "Configuring hooks"
|
|
159
161
|
|
|
160
|
-
chmod +x "$SCRIPT_DIR/plan-hook.sh" "$SCRIPT_DIR/auto-
|
|
162
|
+
chmod +x "$SCRIPT_DIR/plan-hook.sh" "$SCRIPT_DIR/auto-review-hook.sh" "$SCRIPT_DIR/pr-review-hook.sh"
|
|
161
163
|
|
|
162
164
|
echo ""
|
|
163
|
-
echo -e " ${B}Auto-
|
|
165
|
+
echo -e " ${B}Auto-review${N} reviews every plan with all models before"
|
|
164
166
|
echo -e " you approve. Adds 30-90s per plan."
|
|
165
167
|
echo ""
|
|
166
|
-
read -p " Enable auto-
|
|
168
|
+
read -p " Enable auto-review? [y/N]: " AUTO_BLEND
|
|
167
169
|
if [[ "$AUTO_BLEND" =~ ^[Yy]$ ]]; then
|
|
168
170
|
node -e "
|
|
169
171
|
const fs = require('fs');
|
|
170
172
|
const c = JSON.parse(fs.readFileSync('$SCRIPT_DIR/config.json','utf8'));
|
|
171
|
-
c.
|
|
173
|
+
c.auto_review = true;
|
|
172
174
|
fs.writeFileSync('$SCRIPT_DIR/config.json', JSON.stringify(c, null, 2));
|
|
173
175
|
"
|
|
174
|
-
echo -e " ${G}✓${N} Auto-
|
|
176
|
+
echo -e " ${G}✓${N} Auto-review on"
|
|
175
177
|
else
|
|
176
178
|
echo -e " ${D}Skipped — toggle in config.json anytime${N}"
|
|
177
179
|
fi
|
|
178
180
|
|
|
179
|
-
# ─── Step 7:
|
|
181
|
+
# ─── Step 7: Leaderboard ─────────────────────────────────────────────
|
|
182
|
+
step "Leaderboard"
|
|
183
|
+
|
|
184
|
+
# Leaderboard opt-in
|
|
185
|
+
echo ""
|
|
186
|
+
echo -e " ${B}Leaderboard${N} — your GitHub username and token usage"
|
|
187
|
+
echo -e " appear on the public Smoothie rankings."
|
|
188
|
+
echo -e " ${D}https://smoothiecode.com/leaderboard${N}"
|
|
189
|
+
echo ""
|
|
190
|
+
read -p " Join the leaderboard? [Y/n]: " JOIN_LB
|
|
191
|
+
if [[ ! "$JOIN_LB" =~ ^[Nn]$ ]]; then
|
|
192
|
+
LB_GITHUB=$(git config user.name 2>/dev/null || echo "")
|
|
193
|
+
if [ -z "$LB_GITHUB" ]; then
|
|
194
|
+
read -p " GitHub username: " LB_GITHUB
|
|
195
|
+
else
|
|
196
|
+
read -p " GitHub username [$LB_GITHUB]: " INPUT_LB
|
|
197
|
+
LB_GITHUB="${INPUT_LB:-$LB_GITHUB}"
|
|
198
|
+
fi
|
|
199
|
+
node -e "
|
|
200
|
+
const fs = require('fs');
|
|
201
|
+
const c = JSON.parse(fs.readFileSync('$SCRIPT_DIR/config.json','utf8'));
|
|
202
|
+
c.leaderboard = true;
|
|
203
|
+
c.github = '$LB_GITHUB';
|
|
204
|
+
fs.writeFileSync('$SCRIPT_DIR/config.json', JSON.stringify(c, null, 2));
|
|
205
|
+
"
|
|
206
|
+
echo -e " ${G}✓${N} Joined as $LB_GITHUB"
|
|
207
|
+
else
|
|
208
|
+
echo -e " ${D}Skipped — join anytime: smoothie leaderboard join${N}"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# ─── Step 8: Wire up ─────────────────────────────────────────────────
|
|
180
212
|
step "Wiring up"
|
|
181
213
|
|
|
182
214
|
if [ "$PLATFORM" = "claude" ]; then
|
|
@@ -204,7 +236,7 @@ The user has provided this context/problem:
|
|
|
204
236
|
$ARGUMENTS
|
|
205
237
|
|
|
206
238
|
**Step 1 — Blend**
|
|
207
|
-
Call `
|
|
239
|
+
Call `smoothie_review` with the user's prompt. The MCP server queries all
|
|
208
240
|
models in parallel and shows live progress in the terminal. Wait for it to return.
|
|
209
241
|
|
|
210
242
|
**Step 2 — Judge and respond**
|
|
@@ -229,7 +261,7 @@ $ARGUMENTS
|
|
|
229
261
|
Run `git diff main...HEAD` to get the full branch diff.
|
|
230
262
|
|
|
231
263
|
**Step 2 — Blend**
|
|
232
|
-
Call `
|
|
264
|
+
Call `smoothie_review` with a prompt asking models to review the diff for:
|
|
233
265
|
- Bugs, logic errors, edge cases
|
|
234
266
|
- Security vulnerabilities
|
|
235
267
|
- Performance issues
|
|
@@ -267,20 +299,20 @@ if (!preExists) {
|
|
|
267
299
|
matcher: "ExitPlanMode",
|
|
268
300
|
hooks: [{
|
|
269
301
|
type: "command",
|
|
270
|
-
command: "bash $SCRIPT_DIR/auto-
|
|
302
|
+
command: "bash $SCRIPT_DIR/auto-review-hook.sh",
|
|
271
303
|
timeout: 600
|
|
272
304
|
}]
|
|
273
305
|
});
|
|
274
306
|
}
|
|
275
307
|
|
|
276
308
|
// Add PR review hook for Bash commands
|
|
277
|
-
const bashHookExists = s.hooks.PreToolUse.some(h => h.matcher === 'Bash' && h.hooks?.[0]?.command?.includes('pr-
|
|
309
|
+
const bashHookExists = s.hooks.PreToolUse.some(h => h.matcher === 'Bash' && h.hooks?.[0]?.command?.includes('pr-review-hook'));
|
|
278
310
|
if (!bashHookExists) {
|
|
279
311
|
s.hooks.PreToolUse.push({
|
|
280
312
|
matcher: "Bash",
|
|
281
313
|
hooks: [{
|
|
282
314
|
type: "command",
|
|
283
|
-
command: "bash $SCRIPT_DIR/pr-
|
|
315
|
+
command: "bash $SCRIPT_DIR/pr-review-hook.sh",
|
|
284
316
|
timeout: 600
|
|
285
317
|
}]
|
|
286
318
|
});
|
|
@@ -322,14 +354,14 @@ if [ "$PLATFORM" = "gemini" ]; then
|
|
|
322
354
|
|
|
323
355
|
# Gemini slash commands (.toml)
|
|
324
356
|
cat > "$HOME/.gemini/commands/smoothie.toml" << 'TOML'
|
|
325
|
-
description = "
|
|
357
|
+
description = "Review this problem across multiple AI models. Gemini judges."
|
|
326
358
|
|
|
327
359
|
prompt = """
|
|
328
360
|
You are running Smoothie — a multi-model review session.
|
|
329
361
|
|
|
330
362
|
{{args}}
|
|
331
363
|
|
|
332
|
-
Step 1 — Call
|
|
364
|
+
Step 1 — Call smoothie_review with the problem text. Wait for results.
|
|
333
365
|
|
|
334
366
|
Step 2 — You have responses from all models. Do NOT show raw outputs.
|
|
335
367
|
- If reviewing a problem: give the answer. Mention conflicts in one sentence.
|
|
@@ -349,13 +381,91 @@ You are running Smoothie PR Review.
|
|
|
349
381
|
{{args}}
|
|
350
382
|
|
|
351
383
|
Step 1 — Run git diff main...HEAD to get the branch diff.
|
|
352
|
-
Step 2 — Call
|
|
384
|
+
Step 2 — Call smoothie_review asking models to review the diff for bugs, security, performance.
|
|
353
385
|
Step 3 — Summarize findings with file:line references. Be direct.
|
|
354
386
|
"""
|
|
355
387
|
TOML
|
|
356
388
|
echo -e " ${G}✓${N} Slash command /smoothie-pr (Gemini)"
|
|
389
|
+
|
|
390
|
+
# Register auto-review hook for Gemini CLI
|
|
391
|
+
chmod +x "$SCRIPT_DIR/gemini-review-hook.sh"
|
|
392
|
+
GEMINI_SETTINGS="$HOME/.gemini/settings.json"
|
|
393
|
+
GEMINI_EXISTING="{}"
|
|
394
|
+
[ -f "$GEMINI_SETTINGS" ] && GEMINI_EXISTING=$(cat "$GEMINI_SETTINGS")
|
|
395
|
+
|
|
396
|
+
node -e "
|
|
397
|
+
const fs = require('fs');
|
|
398
|
+
let s;
|
|
399
|
+
try { s = JSON.parse(\`$GEMINI_EXISTING\`); } catch(e) { s = {}; }
|
|
400
|
+
s.hooks = s.hooks || {};
|
|
401
|
+
s.hooks.BeforeTool = s.hooks.BeforeTool || [];
|
|
402
|
+
const exists = s.hooks.BeforeTool.some(h => h.matcher === 'exit_plan_mode');
|
|
403
|
+
if (!exists) {
|
|
404
|
+
s.hooks.BeforeTool.push({
|
|
405
|
+
matcher: 'exit_plan_mode',
|
|
406
|
+
hooks: [{
|
|
407
|
+
type: 'command',
|
|
408
|
+
command: 'bash $SCRIPT_DIR/gemini-review-hook.sh',
|
|
409
|
+
timeout: 600
|
|
410
|
+
}]
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
fs.writeFileSync('$GEMINI_SETTINGS', JSON.stringify(s, null, 2));
|
|
414
|
+
"
|
|
415
|
+
echo -e " ${G}✓${N} Auto-review hook registered (BeforeTool/exit_plan_mode)"
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
if [ "$PLATFORM" = "cursor" ]; then
|
|
419
|
+
# Register MCP server in Cursor
|
|
420
|
+
CURSOR_MCP="$HOME/.cursor/mcp.json"
|
|
421
|
+
CURSOR_EXISTING="{}"
|
|
422
|
+
[ -f "$CURSOR_MCP" ] && CURSOR_EXISTING=$(cat "$CURSOR_MCP")
|
|
423
|
+
|
|
424
|
+
node -e "
|
|
425
|
+
const fs = require('fs');
|
|
426
|
+
let s;
|
|
427
|
+
try { s = JSON.parse(\`$CURSOR_EXISTING\`); } catch(e) { s = {}; }
|
|
428
|
+
s.mcpServers = s.mcpServers || {};
|
|
429
|
+
s.mcpServers.smoothie = {
|
|
430
|
+
command: 'node',
|
|
431
|
+
args: ['$SCRIPT_DIR/dist/index.js'],
|
|
432
|
+
env: { SMOOTHIE_PLATFORM: 'cursor' }
|
|
433
|
+
};
|
|
434
|
+
fs.writeFileSync('$CURSOR_MCP', JSON.stringify(s, null, 2));
|
|
435
|
+
"
|
|
436
|
+
echo -e " ${G}✓${N} Cursor MCP server registered"
|
|
437
|
+
|
|
438
|
+
# Create Cursor Rule for auto-review (only if auto-review is enabled)
|
|
439
|
+
if [[ "$AUTO_BLEND" =~ ^[Yy]$ ]]; then
|
|
440
|
+
mkdir -p "$HOME/.cursor/rules"
|
|
441
|
+
cat > "$HOME/.cursor/rules/smoothie.mdc" << 'CURSORRULE'
|
|
442
|
+
---
|
|
443
|
+
description: Smoothie multi-model review — runs before significant plans
|
|
444
|
+
alwaysApply: true
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
Before proposing or implementing any plan that involves significant code changes,
|
|
448
|
+
call the `smoothie_review` MCP tool with a description of what you're about to do.
|
|
449
|
+
|
|
450
|
+
Wait for results, then:
|
|
451
|
+
1. Show a summary table: Model | Time | Tokens | Verdict | Key feedback
|
|
452
|
+
2. Incorporate valid suggestions into your plan
|
|
453
|
+
3. Proceed with implementation
|
|
454
|
+
|
|
455
|
+
Skip the review for trivial changes (typos, single-line fixes, config tweaks).
|
|
456
|
+
CURSORRULE
|
|
457
|
+
echo -e " ${G}✓${N} Cursor Rule created (auto-review)"
|
|
458
|
+
fi
|
|
357
459
|
fi
|
|
358
460
|
|
|
461
|
+
# ─── Setup ~/.smoothie/ (single source of truth for CLI) ─────────────
|
|
462
|
+
mkdir -p "$HOME/.smoothie"
|
|
463
|
+
chmod 700 "$HOME/.smoothie"
|
|
464
|
+
cp "$SCRIPT_DIR/config.json" "$HOME/.smoothie/config.json" 2>/dev/null
|
|
465
|
+
cp "$SCRIPT_DIR/.env" "$HOME/.smoothie/.env" 2>/dev/null
|
|
466
|
+
echo "$SCRIPT_DIR" > "$HOME/.smoothie/.install-path"
|
|
467
|
+
echo -e " ${G}✓${N} Config saved to ~/.smoothie/"
|
|
468
|
+
|
|
359
469
|
# ─── Done ─────────────────────────────────────────────────────────────
|
|
360
470
|
MODELS=$(node -e "
|
|
361
471
|
const d = JSON.parse(require('fs').readFileSync('$SCRIPT_DIR/config.json','utf8'));
|
|
@@ -365,11 +475,20 @@ console.log(['Codex', ...d.openrouter_models.map(m=>m.label)].join(' · '));
|
|
|
365
475
|
echo ""
|
|
366
476
|
echo -e " ${D}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${N}"
|
|
367
477
|
echo ""
|
|
368
|
-
|
|
369
|
-
echo ""
|
|
370
|
-
echo
|
|
371
|
-
|
|
372
|
-
|
|
478
|
+
if [ "$PLATFORM" = "cursor" ]; then
|
|
479
|
+
echo -e " ${G}${B}Done!${N} Restart Cursor, then:"
|
|
480
|
+
echo ""
|
|
481
|
+
echo -e " ${D}Ask Cursor to plan something — it calls smoothie_review via MCP${N}"
|
|
482
|
+
if [[ "$AUTO_BLEND" =~ ^[Yy]$ ]]; then
|
|
483
|
+
echo -e " ${C}auto-review${N} ${G}on${N} (via Cursor Rule)"
|
|
484
|
+
fi
|
|
485
|
+
else
|
|
486
|
+
echo -e " ${G}${B}Done!${N} Restart Claude Code, then:"
|
|
487
|
+
echo ""
|
|
488
|
+
echo -e " ${C}/smoothie${N} ${D}<your problem>${N} review in Claude Code"
|
|
489
|
+
if [[ "$AUTO_BLEND" =~ ^[Yy]$ ]]; then
|
|
490
|
+
echo -e " ${C}auto-review${N} ${G}on${N} for all plans"
|
|
491
|
+
fi
|
|
373
492
|
fi
|
|
374
493
|
echo -e " ${C}smoothie models${N} manage models"
|
|
375
494
|
echo ""
|
package/package.json
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smoothie-code",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"smoothie": "./bin/smoothie"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"bin/",
|
|
12
|
+
"install.sh",
|
|
13
|
+
"auto-review-hook.sh",
|
|
14
|
+
"gemini-review-hook.sh",
|
|
15
|
+
"plan-hook.sh",
|
|
16
|
+
"pr-review-hook.sh",
|
|
17
|
+
"config.json",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
9
20
|
"scripts": {
|
|
10
21
|
"build": "tsc",
|
|
11
22
|
"start": "node dist/index.js"
|
package/plan-hook.sh
CHANGED
|
@@ -32,7 +32,7 @@ IS_PLAN=$(tail -c 3000 "$TRANSCRIPT" | grep -c "Would you like to proceed\|## Pl
|
|
|
32
32
|
|
|
33
33
|
if [ "$IS_PLAN" -gt 0 ]; then
|
|
34
34
|
echo ""
|
|
35
|
-
echo "🧃 Smoothie: type 'smoothie' in option 5 to
|
|
35
|
+
echo "🧃 Smoothie: type 'smoothie' in option 5 to review this plan before approving."
|
|
36
36
|
fi
|
|
37
37
|
|
|
38
38
|
exit 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
#
|
|
3
|
-
# pr-
|
|
3
|
+
# pr-review-hook.sh — PreToolUse hook for Bash commands
|
|
4
4
|
#
|
|
5
5
|
# Intercepts `gh pr create` commands, runs Smoothie blend on the
|
|
6
6
|
# branch diff, and injects review results so Claude can revise
|
|
@@ -12,11 +12,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
12
12
|
# Read hook input from stdin
|
|
13
13
|
INPUT=$(cat)
|
|
14
14
|
|
|
15
|
-
# Check if auto-
|
|
15
|
+
# Check if auto-review is enabled in config
|
|
16
16
|
AUTO_ENABLED=$(node -e "
|
|
17
17
|
try {
|
|
18
18
|
const c = JSON.parse(require('fs').readFileSync('$SCRIPT_DIR/config.json','utf8'));
|
|
19
|
-
console.log(c.
|
|
19
|
+
console.log(c.auto_review === true ? 'true' : 'false');
|
|
20
20
|
} catch(e) { console.log('false'); }
|
|
21
21
|
" 2>/dev/null)
|
|
22
22
|
|
|
@@ -65,7 +65,37 @@ $DIFF
|
|
|
65
65
|
|
|
66
66
|
Provide concise, actionable feedback."
|
|
67
67
|
|
|
68
|
-
BLEND_RESULTS=$(echo "$REVIEW_PROMPT" | node "$SCRIPT_DIR/dist/
|
|
68
|
+
BLEND_RESULTS=$(echo "$REVIEW_PROMPT" | node "$SCRIPT_DIR/dist/review-cli.js" 2>/dev/stderr)
|
|
69
|
+
|
|
70
|
+
# Generate share link (metadata only, no raw content)
|
|
71
|
+
SHARE_URL=""
|
|
72
|
+
SHARE_PARAMS=$(echo "$BLEND_RESULTS" | node -e "
|
|
73
|
+
const fs=require('fs');
|
|
74
|
+
let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
|
|
75
|
+
try {
|
|
76
|
+
const r=JSON.parse(d);
|
|
77
|
+
const models=r.results.map(m=>m.model).join(',');
|
|
78
|
+
const times=r.results.map(m=>m.elapsed_s||0).join(',');
|
|
79
|
+
const tokens=r.results.map(m=>(m.tokens&&m.tokens.total)||0).join(',');
|
|
80
|
+
const responded=r.results.filter(m=>!m.response.startsWith('Error:')&&m.response!=='No response content'&&m.response!=='(empty response)').length;
|
|
81
|
+
let github='',judge='Claude Code';
|
|
82
|
+
try{const c=JSON.parse(fs.readFileSync('$SCRIPT_DIR/config.json','utf8'));github=c.github||'';const p=process.env.SMOOTHIE_PLATFORM||'claude';judge={claude:'Claude Code',gemini:'Gemini CLI',codex:'Codex CLI',cursor:'Cursor'}[p]||'Claude Code';}catch{}
|
|
83
|
+
let params='models='+encodeURIComponent(models)+'×='+encodeURIComponent(times)+'&tokens='+encodeURIComponent(tokens)+'&type=pr&suggestions='+responded+'&judge='+encodeURIComponent(judge);
|
|
84
|
+
if(github)params+='&user='+encodeURIComponent(github);
|
|
85
|
+
console.log(params);
|
|
86
|
+
} catch { console.log(''); }
|
|
87
|
+
});
|
|
88
|
+
" 2>/dev/null)
|
|
89
|
+
|
|
90
|
+
if [ -n "$SHARE_PARAMS" ]; then
|
|
91
|
+
SHARE_URL=$(curl -s --max-time 5 --connect-timeout 3 -X POST "https://api.smoothiecode.com/api/share" \
|
|
92
|
+
-H "Content-Type: application/json" \
|
|
93
|
+
-d "{\"type\":\"report\",\"data\":\"$SHARE_PARAMS\"}" 2>/dev/null | node -e "
|
|
94
|
+
let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
|
|
95
|
+
try{console.log(JSON.parse(d).url)}catch{console.log('')}
|
|
96
|
+
});
|
|
97
|
+
" 2>/dev/null) || true
|
|
98
|
+
fi
|
|
69
99
|
|
|
70
100
|
if [ -z "$BLEND_RESULTS" ]; then
|
|
71
101
|
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
|
|
@@ -79,12 +109,16 @@ $BLEND_RESULTS
|
|
|
79
109
|
|
|
80
110
|
IMPORTANT — You must do TWO things:
|
|
81
111
|
|
|
82
|
-
1. FIRST, show a
|
|
112
|
+
1. FIRST, show a Smoothie report table. Use elapsed_s and tokens from each result:
|
|
113
|
+
|
|
114
|
+
🧃 Smoothie reviewed this PR (N models, Xs total):
|
|
83
115
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
116
|
+
| Model | Time | Tokens | Finding |
|
|
117
|
+
|-------|------|--------|---------|
|
|
118
|
+
| ModelName | 3.1s | 1,847 | one-line key finding |
|
|
119
|
+
(skip errored/empty models)
|
|
120
|
+
$([ -n "$SHARE_URL" ] && echo "
|
|
121
|
+
Share this report: $SHARE_URL")
|
|
88
122
|
|
|
89
123
|
2. THEN address any valid issues — fix the code or note them in the PR description."
|
|
90
124
|
|
package/auto-blend-hook.sh
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
#
|
|
3
|
-
# auto-blend-hook.sh — PreToolUse hook for ExitPlanMode
|
|
4
|
-
#
|
|
5
|
-
# Intercepts plan approval, runs Smoothie blend on the plan,
|
|
6
|
-
# and injects results back so Claude revises before you see it.
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
-
|
|
11
|
-
# Read hook input from stdin
|
|
12
|
-
INPUT=$(cat)
|
|
13
|
-
|
|
14
|
-
# Check if auto-blend is enabled in config
|
|
15
|
-
if [ -f "$SCRIPT_DIR/config.json" ]; then
|
|
16
|
-
AUTO_ENABLED=$(node -e "
|
|
17
|
-
try {
|
|
18
|
-
const c = JSON.parse(require('fs').readFileSync('$SCRIPT_DIR/config.json','utf8'));
|
|
19
|
-
console.log(c.auto_blend === true ? 'true' : 'false');
|
|
20
|
-
} catch(e) { console.log('false'); }
|
|
21
|
-
" 2>/dev/null)
|
|
22
|
-
|
|
23
|
-
if [ "$AUTO_ENABLED" != "true" ]; then
|
|
24
|
-
# Auto-blend disabled — allow ExitPlanMode without intervention
|
|
25
|
-
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
|
|
26
|
-
exit 0
|
|
27
|
-
fi
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
# Extract transcript path
|
|
31
|
-
TRANSCRIPT_PATH=$(echo "$INPUT" | python3 -c "
|
|
32
|
-
import sys, json
|
|
33
|
-
try:
|
|
34
|
-
d = json.load(sys.stdin)
|
|
35
|
-
print(d.get('transcript_path', ''))
|
|
36
|
-
except:
|
|
37
|
-
print('')
|
|
38
|
-
" 2>/dev/null)
|
|
39
|
-
|
|
40
|
-
if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
|
|
41
|
-
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
|
|
42
|
-
exit 0
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
# Extract the plan from the last ~4000 chars of the transcript
|
|
46
|
-
PLAN_CONTEXT=$(tail -c 4000 "$TRANSCRIPT_PATH" 2>/dev/null)
|
|
47
|
-
|
|
48
|
-
if [ -z "$PLAN_CONTEXT" ]; then
|
|
49
|
-
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
|
|
50
|
-
exit 0
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
# Build the review prompt
|
|
54
|
-
REVIEW_PROMPT="You are reviewing a plan that Claude Code generated. Analyze it for:
|
|
55
|
-
- Missing steps or edge cases
|
|
56
|
-
- Better approaches or optimizations
|
|
57
|
-
- Potential bugs or issues
|
|
58
|
-
- Security concerns
|
|
59
|
-
|
|
60
|
-
Here is the plan context (from the conversation transcript):
|
|
61
|
-
|
|
62
|
-
$PLAN_CONTEXT
|
|
63
|
-
|
|
64
|
-
Provide concise, actionable feedback. Focus only on things that should change."
|
|
65
|
-
|
|
66
|
-
# Run the blend (progress shows on stderr, results on stdout)
|
|
67
|
-
BLEND_RESULTS=$(echo "$REVIEW_PROMPT" | node "$SCRIPT_DIR/dist/blend-cli.js" 2>/dev/stderr)
|
|
68
|
-
|
|
69
|
-
if [ -z "$BLEND_RESULTS" ]; then
|
|
70
|
-
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
|
|
71
|
-
exit 0
|
|
72
|
-
fi
|
|
73
|
-
|
|
74
|
-
# Build the additionalContext string
|
|
75
|
-
CONTEXT="🧃 Smoothie auto-blend results — multiple models reviewed this plan:
|
|
76
|
-
|
|
77
|
-
$BLEND_RESULTS
|
|
78
|
-
|
|
79
|
-
IMPORTANT — You must do TWO things:
|
|
80
|
-
|
|
81
|
-
1. FIRST, show a brief Smoothie summary to the user so they know the blend ran. Format it like:
|
|
82
|
-
|
|
83
|
-
🧃 Smoothie reviewed this plan (N models):
|
|
84
|
-
ModelName — one-line summary of their key feedback
|
|
85
|
-
ModelName — one-line summary of their key feedback
|
|
86
|
-
(skip models that returned errors or empty responses)
|
|
87
|
-
|
|
88
|
-
2. THEN revise the plan incorporating valid suggestions. Discard irrelevant ones."
|
|
89
|
-
|
|
90
|
-
# Return: allow ExitPlanMode but inject blend results
|
|
91
|
-
node -e "
|
|
92
|
-
const ctx = $(echo "$CONTEXT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null);
|
|
93
|
-
console.log(JSON.stringify({
|
|
94
|
-
hookSpecificOutput: {
|
|
95
|
-
hookEventName: 'PreToolUse',
|
|
96
|
-
permissionDecision: 'allow',
|
|
97
|
-
permissionDecisionReason: 'Smoothie auto-blend completed',
|
|
98
|
-
additionalContext: ctx
|
|
99
|
-
}
|
|
100
|
-
}));
|
|
101
|
-
"
|
|
102
|
-
|
|
103
|
-
exit 0
|