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 CHANGED
@@ -1,82 +1,64 @@
1
1
  # Smoothie
2
2
 
3
- <p align="center">
4
- <img src="banner-v2.svg" alt="Smoothie — multi-model review for Claude Code" width="100%">
5
- </p>
3
+ Multi-model code review for AI coding agents. Query Codex, Gemini, Grok, DeepSeek and more in parallel — get one reviewed answer.
6
4
 
7
- Multi-model review plugin for Claude Code. Sends your problem or plan to multiple AI models simultaneously, then Claude judges all responses and serves you one blended result.
8
-
9
- **Two model tracks:**
10
- - **Codex** — Codex CLI, authenticated via ChatGPT account OAuth
11
- - **OpenRouter** — single API key, models selected at install time from a live ranked list
5
+ **[Website](https://smoothiecode.com)** · **[Docs](https://smoothiecode.com/docs)** · **[Leaderboard](https://smoothiecode.com/leaderboard)** · **[@smoothie_code](https://x.com/smoothie_code)**
12
6
 
13
7
  ## Install
14
8
 
15
9
  ```bash
16
- git clone https://github.com/hotairbag/smoothie && cd smoothie && bash install.sh
10
+ npx smoothie-code
17
11
  ```
18
12
 
19
- The installer walks you through everything: dependencies, Codex auth, OpenRouter key, and model selection.
13
+ Works with **Claude Code**, **Gemini CLI**, **Codex CLI**, and **Cursor**.
20
14
 
21
- Restart Claude Code after install.
15
+ ## Features
22
16
 
23
- ## Usage
17
+ - `/smoothie <problem>` — review across all models, get one answer
18
+ - `/smoothie-pr` — multi-model PR review
19
+ - `/smoothie --deep` — full context mode with cost estimate
20
+ - **Auto-review** — plans and PRs reviewed automatically before you approve
21
+ - **Leaderboard** — weekly token rankings at [smoothiecode.com/leaderboard](https://smoothiecode.com/leaderboard)
22
+ - **Stats & sharing** — `smoothie stats`, `smoothie share`
24
23
 
25
- ### Slash command
26
- ```
27
- /smoothie <your problem or question>
28
- ```
24
+ ## Platform support
29
25
 
30
- ### Auto-blend (plans)
31
- When enabled, every plan is automatically reviewed by all models before you see it. Claude revises the plan with their feedback, then presents the improved version for approval. Zero effort.
32
-
33
- Enable during install, or toggle anytime in `config.json`:
34
- ```json
35
- { "auto_blend": true }
36
- ```
26
+ | Feature | Claude Code | Gemini CLI | Codex CLI | Cursor |
27
+ |---------|------------|-----------|-----------|--------|
28
+ | MCP server | ✓ | ✓ | ✓ (STDIO) | ✓ |
29
+ | Slash commands | | | — | — |
30
+ | Auto-review hooks | ✓ | ✓ | ⚠ experimental | Rule-based |
31
+ | `/smoothie` | ✓ | ✓ | — | — |
32
+ | `smoothie review` CLI | ✓ | ✓ | ✓ | ✓ |
37
33
 
38
- Adds 30-90s to plan approval while models respond.
34
+ ## CLI
39
35
 
40
- ### Manage models
41
36
  ```bash
42
- smoothie models # re-pick from top models
43
- smoothie models add openai/gpt-5.4
44
- smoothie models remove openai/gpt-5.4
45
- smoothie models list
37
+ smoothie models # pick models
38
+ smoothie auto on|off # toggle auto-review
39
+ smoothie review "<prompt>" # run a review
40
+ smoothie review --deep "..." # deep review with full context
41
+ smoothie stats # usage stats
42
+ smoothie share # share last report
43
+ smoothie leaderboard # view rankings
44
+ smoothie help # all commands
46
45
  ```
47
- No restart needed — config is read fresh on each blend.
48
46
 
49
47
  ## How it works
50
48
 
51
49
  ```
52
- Claude Code
50
+ Your IDE (Claude/Gemini/Cursor)
53
51
  |
54
- |-- /smoothie <context> <- manual slash command
55
- |-- PreToolUse hook <- auto-blend on ExitPlanMode
56
- \-- MCP Server
57
- \-- smoothie_blend(prompt)
58
- |-- Queries all models in parallel
59
- |-- Streams live progress to terminal
60
- \-- Returns all responses to Claude
52
+ |-- /smoothie or auto-review hook
53
+ \-- MCP Server smoothie_review(prompt)
54
+ |-- Queries all models in parallel
55
+ |-- Returns responses to the judge AI
56
+ \-- Judge gives you one reviewed answer
61
57
  ```
62
58
 
63
- **Auto-blend flow:**
64
- ```
65
- Claude presents plan → ExitPlanMode hook fires → Smoothie blend runs
66
- → Results injected as context → Claude revises plan → You approve
67
- ```
68
-
69
- Claude acts as judge. Raw model outputs are never shown. Claude absorbs everything and hands you one result.
70
-
71
- ## File overview
59
+ ## Links
72
60
 
73
- | File | Purpose |
74
- |---|---|
75
- | `src/index.ts` | MCP server exposing `smoothie_blend` tool |
76
- | `src/blend-cli.ts` | Standalone blend runner (used by hooks) |
77
- | `src/select-models.ts` | Interactive model picker (OpenRouter API) |
78
- | `auto-blend-hook.sh` | PreToolUse hook — auto-blends plans |
79
- | `plan-hook.sh` | Stop hook — plan mode hint (fallback) |
80
- | `install.sh` | One-command installer |
81
- | `config.json` | Model selection + auto_blend flag |
82
- | `.env` | API keys (gitignored) |
61
+ - [Documentation](https://smoothiecode.com/docs)
62
+ - [Leaderboard](https://smoothiecode.com/leaderboard)
63
+ - [npm](https://www.npmjs.com/package/smoothie-code)
64
+ - [OpenRouter Usage](https://openrouter.ai/apps?url=https%3A%2F%2Fsmoothiecode.com)
@@ -0,0 +1,138 @@
1
+ #!/bin/bash
2
+ #
3
+ # auto-review-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-review 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_review === 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/review-cli.js" 2>/dev/stderr)
68
+
69
+ # Generate share link (metadata only, no raw content)
70
+ SHARE_URL=""
71
+ SHARE_PARAMS=$(echo "$BLEND_RESULTS" | node -e "
72
+ const fs=require('fs');
73
+ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
74
+ try {
75
+ const r=JSON.parse(d);
76
+ const models=r.results.map(m=>m.model).join(',');
77
+ const times=r.results.map(m=>m.elapsed_s||0).join(',');
78
+ const tokens=r.results.map(m=>(m.tokens&&m.tokens.total)||0).join(',');
79
+ const responded=r.results.filter(m=>!m.response.startsWith('Error:')&&m.response!=='No response content'&&m.response!=='(empty response)').length;
80
+ let github='',judge='Claude Code';
81
+ 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{}
82
+ let params='models='+encodeURIComponent(models)+'&times='+encodeURIComponent(times)+'&tokens='+encodeURIComponent(tokens)+'&type=plan&suggestions='+responded+'&judge='+encodeURIComponent(judge);
83
+ if(github)params+='&user='+encodeURIComponent(github);
84
+ console.log(params);
85
+ } catch { console.log(''); }
86
+ });
87
+ " 2>/dev/null)
88
+
89
+ if [ -n "$SHARE_PARAMS" ]; then
90
+ SHARE_URL=$(curl -s --max-time 5 --connect-timeout 3 -X POST "https://api.smoothiecode.com/api/share" \
91
+ -H "Content-Type: application/json" \
92
+ -d "{\"type\":\"report\",\"data\":\"$SHARE_PARAMS\"}" 2>/dev/null | node -e "
93
+ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
94
+ try{console.log(JSON.parse(d).url)}catch{console.log('')}
95
+ });
96
+ " 2>/dev/null) || true
97
+ fi
98
+
99
+ if [ -z "$BLEND_RESULTS" ]; then
100
+ echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
101
+ exit 0
102
+ fi
103
+
104
+ # Build the additionalContext string
105
+ CONTEXT="🧃 Smoothie auto-review results — multiple models reviewed this plan:
106
+
107
+ $BLEND_RESULTS
108
+
109
+ IMPORTANT — You must do TWO things:
110
+
111
+ 1. FIRST, show a Smoothie report table. Use elapsed_s and tokens from each result:
112
+
113
+ 🧃 Smoothie reviewed this plan (N models, Xs total):
114
+
115
+ | Model | Time | Tokens | Verdict | Feedback |
116
+ |-------|------|--------|---------|----------|
117
+ | ModelName | 3.1s | 1,847 | useful | one-line summary |
118
+ | ModelName | 8.2s | 2,104 | generic | one-line summary |
119
+ (skip errored/empty models. Verdict = useful, generic, or irrelevant)
120
+ $([ -n "$SHARE_URL" ] && echo "
121
+ Share this report: $SHARE_URL")
122
+
123
+ 2. THEN proceed with implementation as planned, incorporating valid Smoothie suggestions as you go. Do NOT stop to revise — start coding immediately."
124
+
125
+ # Return: allow ExitPlanMode but inject blend results
126
+ node -e "
127
+ const ctx = $(echo "$CONTEXT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null);
128
+ console.log(JSON.stringify({
129
+ hookSpecificOutput: {
130
+ hookEventName: 'PreToolUse',
131
+ permissionDecision: 'allow',
132
+ permissionDecisionReason: 'Smoothie auto-review completed',
133
+ additionalContext: ctx
134
+ }
135
+ }));
136
+ "
137
+
138
+ exit 0
package/bin/smoothie CHANGED
@@ -1,15 +1,27 @@
1
1
  #!/bin/bash
2
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
3
- CONFIG="$SCRIPT_DIR/config.json"
2
+
3
+ # ~/.smoothie/ is the single source of truth for all config + data
4
+ SMOOTHIE_HOME="$HOME/.smoothie"
5
+ CONFIG="$SMOOTHIE_HOME/config.json"
6
+
7
+ # Install dir (where dist/, hooks live) — stored during install
8
+ INSTALL_DIR="$(cat "$SMOOTHIE_HOME/.install-path" 2>/dev/null)"
9
+ if [ -z "$INSTALL_DIR" ] || [ ! -d "$INSTALL_DIR" ]; then
10
+ # Fallback: resolve from script location (works for git clone installs)
11
+ INSTALL_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null || echo "${BASH_SOURCE[0]}")")/.." && pwd)"
12
+ fi
13
+
14
+ # Ensure ~/.smoothie exists
15
+ mkdir -p "$SMOOTHIE_HOME"
4
16
 
5
17
  case "$1" in
6
18
  models)
7
19
  shift
8
- node "$SCRIPT_DIR/dist/select-models.js" "$@"
20
+ node "$INSTALL_DIR/dist/select-models.js" "$@"
9
21
  ;;
10
- blend)
22
+ review)
11
23
  shift
12
- node "$SCRIPT_DIR/dist/blend-cli.js" "$@"
24
+ node "$INSTALL_DIR/dist/review-cli.js" "$@"
13
25
  ;;
14
26
  auto)
15
27
  case "$2" in
@@ -17,32 +29,216 @@ case "$1" in
17
29
  node -e "
18
30
  const fs = require('fs');
19
31
  const c = JSON.parse(fs.readFileSync('$CONFIG','utf8'));
20
- c.auto_blend = true;
32
+ c.auto_review = true;
21
33
  fs.writeFileSync('$CONFIG', JSON.stringify(c, null, 2));
22
34
  "
23
- echo " ✓ Auto-blend enabled"
35
+ echo " ✓ Auto-review enabled"
24
36
  ;;
25
37
  off)
26
38
  node -e "
27
39
  const fs = require('fs');
28
40
  const c = JSON.parse(fs.readFileSync('$CONFIG','utf8'));
29
- c.auto_blend = false;
41
+ c.auto_review = false;
30
42
  fs.writeFileSync('$CONFIG', JSON.stringify(c, null, 2));
31
43
  "
32
- echo " ✓ Auto-blend disabled"
44
+ echo " ✓ Auto-review disabled"
33
45
  ;;
34
46
  *)
35
47
  ENABLED=$(node -e "
36
48
  try {
37
49
  const c = JSON.parse(require('fs').readFileSync('$CONFIG','utf8'));
38
- console.log(c.auto_blend ? 'on' : 'off');
50
+ console.log(c.auto_review ? 'on' : 'off');
39
51
  } catch(e) { console.log('off'); }
40
52
  ")
41
- echo " Auto-blend is $ENABLED"
53
+ echo " Auto-review is $ENABLED"
42
54
  echo " Usage: smoothie auto on|off"
43
55
  ;;
44
56
  esac
45
57
  ;;
58
+ stats)
59
+ HISTORY="$SMOOTHIE_HOME/.smoothie-history.jsonl"
60
+ if [ ! -f "$HISTORY" ]; then
61
+ echo " No history yet. Run a review first."
62
+ exit 0
63
+ fi
64
+ node -e "
65
+ const fs = require('fs');
66
+ const lines = fs.readFileSync('$HISTORY', 'utf8').trim().split('\n').map(l => JSON.parse(l));
67
+
68
+ const totalBlends = lines.length;
69
+ const totalTokens = lines.reduce((s, e) => s + e.models.reduce((s2, m) => s2 + (m.tokens?.total || 0), 0), 0);
70
+ const totalTime = lines.reduce((s, e) => s + Math.max(...e.models.map(m => m.elapsed_s || 0)), 0);
71
+ const errors = lines.reduce((s, e) => s + e.models.filter(m => m.error).length, 0);
72
+ const successes = lines.reduce((s, e) => s + e.models.filter(m => !m.error).length, 0);
73
+
74
+ const modelStats = {};
75
+ for (const entry of lines) {
76
+ for (const m of entry.models) {
77
+ if (!modelStats[m.model]) modelStats[m.model] = { calls: 0, tokens: 0, errors: 0, totalTime: 0 };
78
+ modelStats[m.model].calls++;
79
+ modelStats[m.model].tokens += m.tokens?.total || 0;
80
+ modelStats[m.model].totalTime += m.elapsed_s || 0;
81
+ if (m.error) modelStats[m.model].errors++;
82
+ }
83
+ }
84
+
85
+ const sorted = Object.entries(modelStats).sort((a, b) => b[1].calls - a[1].calls);
86
+
87
+ if ('$2' === '--share') {
88
+ const rate = (successes / (successes + errors) * 100).toFixed(0);
89
+ const names = sorted.map(s => s[0]).join(',');
90
+ const mt = sorted.map(s => s[1].tokens).join(',');
91
+ const mc = sorted.map(s => s[1].calls).join(',');
92
+ const ma = sorted.map(s => (s[1].totalTime / s[1].calls).toFixed(1)).join(',');
93
+
94
+ const https = require('https');
95
+ const postData = JSON.stringify({ type: 'stats', data: 'blends=' + totalBlends + '&tokens=' + totalTokens + '&time=' + totalTime.toFixed(0) + '&rate=' + rate + '&names=' + encodeURIComponent(names) + '&mt=' + encodeURIComponent(mt) + '&mc=' + encodeURIComponent(mc) + '&ma=' + encodeURIComponent(ma) });
96
+ const longUrl = 'https://smoothiecode.com/stats?blends=' + totalBlends + '&tokens=' + totalTokens + '&time=' + totalTime.toFixed(0) + '&rate=' + rate + '&names=' + encodeURIComponent(names) + '&mt=' + encodeURIComponent(mt) + '&mc=' + encodeURIComponent(mc) + '&ma=' + encodeURIComponent(ma);
97
+
98
+ const req = https.request('https://api.smoothiecode.com/api/share', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) }, timeout: 5000 }, res => {
99
+ let d = '';
100
+ res.on('data', c => d += c);
101
+ res.on('end', () => {
102
+ try { console.log(' ' + JSON.parse(d).url); }
103
+ catch { console.log(' ' + longUrl); }
104
+ console.log('');
105
+ console.log(' Open in browser to save as image or copy tweet');
106
+ console.log('');
107
+ });
108
+ });
109
+ req.on('error', () => { console.log(' ' + longUrl); console.log(''); });
110
+ req.on('timeout', () => { req.destroy(); console.log(' ' + longUrl); console.log(''); });
111
+ req.write(postData);
112
+ req.end();
113
+ } else {
114
+ console.log('');
115
+ console.log(' 🧃 Smoothie Stats');
116
+ console.log(' ─────────────────────────────────────────');
117
+ console.log(' Reviews: ' + totalBlends + ' Tokens: ' + totalTokens.toLocaleString() + ' Time: ' + totalTime.toFixed(0) + 's');
118
+ console.log(' Success rate: ' + successes + '/' + (successes + errors) + ' (' + (successes / (successes + errors) * 100).toFixed(0) + '%)');
119
+ console.log('');
120
+ console.log(' Model Calls Tokens Avg time Errors');
121
+ console.log(' ─────────────────────────────────────────────────────────────────');
122
+ for (const [name, s] of sorted) {
123
+ const avg = (s.totalTime / s.calls).toFixed(1);
124
+ console.log(' ' + name.padEnd(30) + String(s.calls).padStart(5) + String(s.tokens.toLocaleString()).padStart(8) + (avg + 's').padStart(10) + String(s.errors).padStart(8));
125
+ }
126
+ console.log('');
127
+
128
+ const recent = lines.slice(-5).reverse();
129
+ console.log(' Recent reviews:');
130
+ for (const e of recent) {
131
+ const models = e.models.filter(m => !m.error).map(m => m.model).join(', ');
132
+ const time = Math.max(...e.models.map(m => m.elapsed_s || 0)).toFixed(1);
133
+ const date = new Date(e.ts).toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
134
+ console.log(' ' + date + ' ' + e.type.padEnd(6) + time + 's ' + models);
135
+ }
136
+ console.log('');
137
+ }
138
+ "
139
+ ;;
140
+ share)
141
+ shift
142
+ if [ -f "$SMOOTHIE_HOME/.last-review.json" ]; then
143
+ PARAMS=$(node -e "
144
+ const r = JSON.parse(require('fs').readFileSync('$SMOOTHIE_HOME/.last-review.json','utf8'));
145
+ const models = r.results.map(m => m.model).join(',');
146
+ const times = r.results.map(m => m.elapsed_s || 0).join(',');
147
+ const tokens = r.results.map(m => m.tokens?.total || 0).join(',');
148
+ console.log('models=' + encodeURIComponent(models) + '&times=' + encodeURIComponent(times) + '&tokens=' + encodeURIComponent(tokens) + '&type=plan&suggestions=0');
149
+ ")
150
+ SHORT=$(curl -s --max-time 5 --connect-timeout 3 -X POST "https://api.smoothiecode.com/api/share" \
151
+ -H "Content-Type: application/json" \
152
+ -d "{\"type\":\"report\",\"data\":\"$PARAMS\"}" 2>/dev/null | node -e "
153
+ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
154
+ try{console.log(JSON.parse(d).url)}catch{console.log('')}
155
+ })
156
+ " 2>/dev/null)
157
+ if [ -n "$SHORT" ]; then
158
+ echo " $SHORT"
159
+ else
160
+ echo " https://smoothiecode.com/report?$PARAMS"
161
+ fi
162
+ echo ""
163
+ echo " Open in browser or copy to share"
164
+ else
165
+ echo " No recent review found. Run a review first."
166
+ fi
167
+ ;;
168
+ leaderboard)
169
+ LEADERBOARD_API="https://api.smoothiecode.com"
170
+ case "$2" in
171
+ join)
172
+ GITHUB=$(git config user.name 2>/dev/null || echo "")
173
+ if [ -z "$GITHUB" ]; then
174
+ read -p " GitHub username: " GITHUB
175
+ else
176
+ read -p " GitHub username [$GITHUB]: " INPUT_GH
177
+ GITHUB="${INPUT_GH:-$GITHUB}"
178
+ fi
179
+ node -e "
180
+ const fs = require('fs');
181
+ const c = JSON.parse(fs.readFileSync('$CONFIG','utf8'));
182
+ c.leaderboard = true;
183
+ c.github = '$GITHUB';
184
+ fs.writeFileSync('$CONFIG', JSON.stringify(c, null, 2));
185
+ "
186
+ echo " ✓ Joined as $GITHUB"
187
+ echo " Your stats will appear on the leaderboard after your next review"
188
+ ;;
189
+ leave)
190
+ node -e "
191
+ const fs = require('fs');
192
+ const c = JSON.parse(fs.readFileSync('$CONFIG','utf8'));
193
+ const github = c.github || '';
194
+ c.leaderboard = false;
195
+ fs.writeFileSync('$CONFIG', JSON.stringify(c, null, 2));
196
+ console.log(github);
197
+ " | xargs -I{} curl -s -X DELETE "$LEADERBOARD_API/api/user/{}" > /dev/null 2>&1
198
+ echo " ✓ Left the leaderboard (remote data deleted)"
199
+ ;;
200
+ profile)
201
+ read -p " Twitter handle (e.g. @hotairbag): " TWITTER
202
+ GITHUB=$(node -e "const c=JSON.parse(require('fs').readFileSync('$CONFIG','utf8'));console.log(c.github||'')")
203
+ if [ -z "$GITHUB" ]; then
204
+ echo " Join the leaderboard first: smoothie leaderboard join"
205
+ else
206
+ curl -s -X POST "$LEADERBOARD_API/api/profile" \
207
+ -H "Content-Type: application/json" \
208
+ -d "{\"github\":\"$GITHUB\",\"twitter\":\"$TWITTER\"}" > /dev/null 2>&1
209
+ echo " ✓ Twitter set to $TWITTER"
210
+ fi
211
+ ;;
212
+ *)
213
+ RESULT=$(curl -s "$LEADERBOARD_API/api/leaderboard" 2>/dev/null)
214
+ if [ -z "$RESULT" ]; then
215
+ echo " Could not reach leaderboard. Check your connection."
216
+ else
217
+ node -e "
218
+ const d = JSON.parse(\`$RESULT\`);
219
+ if (!d.rankings || d.rankings.length === 0) {
220
+ console.log(' No reviews this week yet.');
221
+ process.exit(0);
222
+ }
223
+ console.log('');
224
+ console.log(' 🧃 Smoothie Leaderboard — ' + d.week);
225
+ console.log(' ──────────────────────────────────────────');
226
+ const medals = ['🥇','🥈','🥉'];
227
+ for (let i = 0; i < d.rankings.length && i < 20; i++) {
228
+ const r = d.rankings[i];
229
+ const medal = medals[i] || ' ';
230
+ const tokens = r.total_tokens >= 1000 ? (r.total_tokens/1000).toFixed(1)+'k' : r.total_tokens;
231
+ const tw = r.twitter ? ' @'+r.twitter : '';
232
+ console.log(' ' + medal + ' #' + (i+1) + ' ' + r.github.padEnd(20) + tokens.padStart(8) + ' tokens' + tw);
233
+ }
234
+ console.log('');
235
+ console.log(' https://smoothiecode.com/leaderboard');
236
+ console.log('');
237
+ "
238
+ fi
239
+ ;;
240
+ esac
241
+ ;;
46
242
  help|--help|-h|"")
47
243
  echo ""
48
244
  echo " 🧃 Smoothie — multi-model review for Claude Code"
@@ -52,12 +248,21 @@ case "$1" in
52
248
  echo " smoothie models add <id> Add by OpenRouter model ID"
53
249
  echo " smoothie models remove <id> Remove a model"
54
250
  echo ""
55
- echo " smoothie auto Show auto-blend status"
56
- echo " smoothie auto on Enable auto-blend for plans"
57
- echo " smoothie auto off Disable auto-blend"
251
+ echo " smoothie auto Show auto-review status"
252
+ echo " smoothie auto on Enable auto-review for plans"
253
+ echo " smoothie auto off Disable auto-review"
254
+ echo ""
255
+ echo " smoothie review \"<prompt>\" Run a review from terminal"
256
+ echo " smoothie review --deep \"<prompt>\" Deep review with full context"
257
+ echo ""
258
+ echo " smoothie stats Usage stats and history"
259
+ echo " smoothie stats --share Shareable stats page"
260
+ echo " smoothie share Share last review report"
58
261
  echo ""
59
- echo " smoothie blend \"<prompt>\" Run a blend from terminal"
60
- echo " smoothie blend --deep \"<prompt>\" Deep blend with full context"
262
+ echo " smoothie leaderboard View weekly rankings"
263
+ echo " smoothie leaderboard join Join the leaderboard"
264
+ echo " smoothie leaderboard leave Leave and delete data"
265
+ echo " smoothie leaderboard profile Set Twitter handle"
61
266
  echo ""
62
267
  ;;
63
268
  *)
package/config.json CHANGED
@@ -13,5 +13,7 @@
13
13
  "label": "Z.ai: GLM 5V Turbo"
14
14
  }
15
15
  ],
16
- "auto_blend": true
16
+ "auto_blend": true,
17
+ "leaderboard": true,
18
+ "github": "hotairbag"
17
19
  }