smoothie-code 1.0.0 → 1.2.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.
@@ -173,9 +173,11 @@ async function cmdPick(apiKey, configPath) {
173
173
  claude: ['anthropic'],
174
174
  codex: ['openai'],
175
175
  gemini: ['google'],
176
+ cursor: [], // Cursor uses various models, nothing to exclude
176
177
  };
177
178
  const excluded = excludePrefixes[platform] || [];
178
179
  topModels = topModels.filter(m => !excluded.some(prefix => m.id.startsWith(prefix + '/')));
180
+ console.log(' \x1b[90mSee trending: https://openrouter.ai/rankings/programming\x1b[0m');
179
181
  // Default selection: first 3
180
182
  const selected = new Set([0, 1, 2]);
181
183
  // Print list with selection markers
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=7
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}"
@@ -176,7 +178,37 @@ else
176
178
  echo -e " ${D}Skipped — toggle in config.json anytime${N}"
177
179
  fi
178
180
 
179
- # ─── Step 7: Wire up ─────────────────────────────────────────────────
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
@@ -268,7 +300,7 @@ if (!preExists) {
268
300
  hooks: [{
269
301
  type: "command",
270
302
  command: "bash $SCRIPT_DIR/auto-blend-hook.sh",
271
- timeout: 120
303
+ timeout: 600
272
304
  }]
273
305
  });
274
306
  }
@@ -281,7 +313,7 @@ if (!bashHookExists) {
281
313
  hooks: [{
282
314
  type: "command",
283
315
  command: "bash $SCRIPT_DIR/pr-blend-hook.sh",
284
- timeout: 120
316
+ timeout: 600
285
317
  }]
286
318
  });
287
319
  }
@@ -354,6 +386,76 @@ 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-blend hook for Gemini CLI
391
+ chmod +x "$SCRIPT_DIR/gemini-blend-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-blend-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-blend 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-blend (only if auto-blend 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_blend` 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 blend for trivial changes (typos, single-line fixes, config tweaks).
456
+ CURSORRULE
457
+ echo -e " ${G}✓${N} Cursor Rule created (auto-blend)"
458
+ fi
357
459
  fi
358
460
 
359
461
  # ─── Done ─────────────────────────────────────────────────────────────
@@ -365,11 +467,20 @@ console.log(['Codex', ...d.openrouter_models.map(m=>m.label)].join(' · '));
365
467
  echo ""
366
468
  echo -e " ${D}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${N}"
367
469
  echo ""
368
- echo -e " ${G}${B}Done!${N} Restart Claude Code, then:"
369
- echo ""
370
- echo -e " ${C}/smoothie${N} ${D}<your problem>${N} blend in Claude Code"
371
- if [[ "$AUTO_BLEND" =~ ^[Yy]$ ]]; then
372
- echo -e " ${C}auto-blend${N} ${G}on${N} for all plans"
470
+ if [ "$PLATFORM" = "cursor" ]; then
471
+ echo -e " ${G}${B}Done!${N} Restart Cursor, then:"
472
+ echo ""
473
+ echo -e " ${D}Ask Cursor to plan something — it calls smoothie_blend via MCP${N}"
474
+ if [[ "$AUTO_BLEND" =~ ^[Yy]$ ]]; then
475
+ echo -e " ${C}auto-blend${N} ${G}on${N} (via Cursor Rule)"
476
+ fi
477
+ else
478
+ echo -e " ${G}${B}Done!${N} Restart Claude Code, then:"
479
+ echo ""
480
+ echo -e " ${C}/smoothie${N} ${D}<your problem>${N} blend in Claude Code"
481
+ if [[ "$AUTO_BLEND" =~ ^[Yy]$ ]]; then
482
+ echo -e " ${C}auto-blend${N} ${G}on${N} for all plans"
483
+ fi
373
484
  fi
374
485
  echo -e " ${C}smoothie models${N} manage models"
375
486
  echo ""
package/package.json CHANGED
@@ -1,11 +1,21 @@
1
1
  {
2
2
  "name": "smoothie-code",
3
- "version": "1.0.0",
3
+ "version": "1.2.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-blend-hook.sh",
14
+ "plan-hook.sh",
15
+ "pr-blend-hook.sh",
16
+ "config.json",
17
+ "README.md"
18
+ ],
9
19
  "scripts": {
10
20
  "build": "tsc",
11
21
  "start": "node dist/index.js"
package/pr-blend-hook.sh CHANGED
@@ -67,17 +67,60 @@ Provide concise, actionable feedback."
67
67
 
68
68
  BLEND_RESULTS=$(echo "$REVIEW_PROMPT" | node "$SCRIPT_DIR/dist/blend-cli.js" 2>/dev/stderr)
69
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)+'&times='+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
99
+
70
100
  if [ -z "$BLEND_RESULTS" ]; then
71
101
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
72
102
  exit 0
73
103
  fi
74
104
 
75
105
  # Build the additionalContext string
76
- CONTEXT="Smoothie PR review — multiple models reviewed this diff:
106
+ CONTEXT="🧃 Smoothie PR review — multiple models reviewed this diff:
77
107
 
78
108
  $BLEND_RESULTS
79
109
 
80
- Consider this feedback. If there are valid issues, revise the PR description to note them or fix the code before creating the PR."
110
+ IMPORTANT You must do TWO things:
111
+
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):
115
+
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")
122
+
123
+ 2. THEN address any valid issues — fix the code or note them in the PR description."
81
124
 
82
125
  # Return: allow Bash but inject blend results
83
126
  node -e "
package/banner-v2.svg DELETED
@@ -1,307 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 440" fill="none">
2
- <style>
3
- /* === PERSISTENT: fade in once, stay forever === */
4
- @keyframes shineOnce {
5
- 0% { opacity: 0; filter: blur(10px) brightness(2); }
6
- 40% { opacity: 1; filter: blur(3px) brightness(1.3); }
7
- 100% { opacity: 1; filter: blur(0) brightness(1); }
8
- }
9
- @keyframes fadeOnce {
10
- from { opacity: 0; transform: translateY(8px); }
11
- to { opacity: 1; transform: translateY(0); }
12
- }
13
- .persist-title { opacity: 0; animation: shineOnce 1.2s ease 0.1s forwards; }
14
- .persist-sub { opacity: 0; animation: fadeOnce 0.7s ease 0.4s forwards; }
15
- .persist-term { opacity: 0; animation: fadeOnce 0.5s ease 0.7s forwards; }
16
- .persist-dollar{ opacity: 0; animation: fadeOnce 0.3s ease 1s forwards; }
17
-
18
- /* === LOOPING: 14s cycle === */
19
- /* fade=85-89%, pause=89-100%(1.54s)+0-1%(0.14s)=1.68s total dead */
20
-
21
- @keyframes pathLoop {
22
- 0%, 14% { opacity: 0; }
23
- 17% { opacity: 1; }
24
- 85% { opacity: 1; }
25
- 89% { opacity: 0; }
26
- 100% { opacity: 0; }
27
- }
28
- @keyframes mA { 0%,22% { opacity:0; transform:translateY(8px); } 25% { opacity:1; transform:translateY(0); } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
29
- @keyframes mB { 0%,24% { opacity:0; transform:translateY(8px); } 27% { opacity:1; transform:translateY(0); } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
30
- @keyframes mC { 0%,26% { opacity:0; transform:translateY(8px); } 29% { opacity:1; transform:translateY(0); } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
31
- @keyframes mD { 0%,28% { opacity:0; transform:translateY(8px); } 31% { opacity:1; transform:translateY(0); } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
32
-
33
- @keyframes spin { to { transform: rotate(360deg); } }
34
- @keyframes svA { 0%,22% { opacity:0; } 23% { opacity:1; } 36% { opacity:1; } 37% { opacity:0; } 100% { opacity:0; } }
35
- @keyframes svB { 0%,24% { opacity:0; } 25% { opacity:1; } 39% { opacity:1; } 40% { opacity:0; } 100% { opacity:0; } }
36
- @keyframes svC { 0%,26% { opacity:0; } 27% { opacity:1; } 42% { opacity:1; } 43% { opacity:0; } 100% { opacity:0; } }
37
- @keyframes svD { 0%,28% { opacity:0; } 29% { opacity:1; } 45% { opacity:1; } 46% { opacity:0; } 100% { opacity:0; } }
38
-
39
- @keyframes ckA { 0%,36% { opacity:0; transform:scale(0); } 38% { opacity:1; transform:scale(1.15); } 39% { transform:scale(1); } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
40
- @keyframes ckB { 0%,39% { opacity:0; transform:scale(0); } 41% { opacity:1; transform:scale(1.15); } 42% { transform:scale(1); } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
41
- @keyframes ckC { 0%,42% { opacity:0; transform:scale(0); } 44% { opacity:1; transform:scale(1.15); } 45% { transform:scale(1); } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
42
- @keyframes ckD { 0%,45% { opacity:0; transform:scale(0); } 47% { opacity:1; transform:scale(1.15); } 48% { transform:scale(1); } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
43
-
44
- @keyframes dA { 0%,36% { opacity:0; } 37% { opacity:1; } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
45
- @keyframes dB { 0%,39% { opacity:0; } 40% { opacity:1; } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
46
- @keyframes dC { 0%,42% { opacity:0; } 43% { opacity:1; } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
47
- @keyframes dD { 0%,45% { opacity:0; } 46% { opacity:1; } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
48
-
49
- /* Output: appear at 53%, hold until 85%, fade 89% → 4.5s visible */
50
- @keyframes outShine {
51
- 0%, 53% { opacity:0; filter: blur(10px) brightness(2); }
52
- 57% { opacity:1; filter: blur(3px) brightness(1.3); }
53
- 59% { opacity:1; filter: blur(0) brightness(1); }
54
- 85% { opacity:1; }
55
- 89% { opacity:0; }
56
- 100% { opacity:0; }
57
- }
58
- @keyframes olA { 0%,57% { opacity:0; transform:translateY(8px); } 60% { opacity:1; transform:translateY(0); } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
59
- @keyframes olB { 0%,59% { opacity:0; transform:translateY(8px); } 62% { opacity:1; transform:translateY(0); } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
60
- @keyframes olC { 0%,61% { opacity:0; transform:translateY(8px); } 64% { opacity:1; transform:translateY(0); } 85% { opacity:1; } 89% { opacity:0; } 100% { opacity:0; } }
61
- @keyframes labelFade {
62
- 0%,14% { opacity:0; } 17% { opacity:0.7; } 85% { opacity:0.7; } 89% { opacity:0; } 100% { opacity:0; }
63
- }
64
- @keyframes judgeLabelFade {
65
- 0%,50% { opacity:0; } 53% { opacity:0.7; } 85% { opacity:0.7; } 89% { opacity:0; } 100% { opacity:0; }
66
- }
67
- @keyframes gentleFloat {
68
- 0%, 100% { transform: translateY(0); }
69
- 50% { transform: translateY(-4px); }
70
- }
71
-
72
- /* Class assignments */
73
- .paths { animation: pathLoop 14s ease infinite; }
74
- .label-par { animation: labelFade 14s ease infinite; }
75
- .label-judge { animation: judgeLabelFade 14s ease infinite; }
76
- .mA { animation: mA 14s ease infinite; }
77
- .mB { animation: mB 14s ease infinite; }
78
- .mC { animation: mC 14s ease infinite; }
79
- .mD { animation: mD 14s ease infinite; }
80
- .spA { transform-origin: 448px 165px; animation: spin 0.7s linear infinite, svA 14s ease infinite; }
81
- .spB { transform-origin: 448px 225px; animation: spin 0.7s linear infinite, svB 14s ease infinite; }
82
- .spC { transform-origin: 448px 285px; animation: spin 0.7s linear infinite, svC 14s ease infinite; }
83
- .spD { transform-origin: 448px 345px; animation: spin 0.7s linear infinite, svD 14s ease infinite; }
84
- .ckA { transform-origin: 448px 165px; animation: ckA 14s ease infinite; }
85
- .ckB { transform-origin: 448px 225px; animation: ckB 14s ease infinite; }
86
- .ckC { transform-origin: 448px 285px; animation: ckC 14s ease infinite; }
87
- .ckD { transform-origin: 448px 345px; animation: ckD 14s ease infinite; }
88
- .dnA { animation: dA 14s ease infinite; }
89
- .dnB { animation: dB 14s ease infinite; }
90
- .dnC { animation: dC 14s ease infinite; }
91
- .dnD { animation: dD 14s ease infinite; }
92
- .output-card { animation: outShine 14s ease infinite; }
93
- .output-float { animation: gentleFloat 3s ease-in-out infinite; }
94
- .olA { animation: olA 14s ease infinite; }
95
- .olB { animation: olB 14s ease infinite; }
96
- .olC { animation: olC 14s ease infinite; }
97
- </style>
98
-
99
- <defs>
100
- <linearGradient id="og" x1="0" y1="0" x2="1" y2="0">
101
- <stop offset="0%" stop-color="#f78166" stop-opacity="0.2"/>
102
- <stop offset="100%" stop-color="#f78166"/>
103
- </linearGradient>
104
- <filter id="sh" x="-8%" y="-8%" width="116%" height="120%">
105
- <feDropShadow dx="0" dy="2" stdDeviation="8" flood-color="#000" flood-opacity="0.35"/>
106
- </filter>
107
-
108
- <!-- Typewriter clip: grows to reveal text, shrinks on loop reset -->
109
- <clipPath id="type-clip">
110
- <rect x="72" y="250" width="0" height="30">
111
- <animate attributeName="width" values="0;0;80;80;0;0" keyTimes="0;0.01;0.10;0.82;0.85;1" dur="14s" repeatCount="indefinite"/>
112
- </rect>
113
- </clipPath>
114
- </defs>
115
-
116
- <!-- ======= BACKGROUND (always visible) ======= -->
117
- <rect width="1200" height="440" fill="#0d1117"/>
118
- <pattern id="dots" width="24" height="24" patternUnits="userSpaceOnUse">
119
- <circle cx="12" cy="12" r="0.5" fill="#f0f6fc" opacity="0.06"/>
120
- </pattern>
121
- <rect width="1200" height="440" fill="url(#dots)"/>
122
- <line x1="100" y1="105" x2="1100" y2="105" stroke="#21262d" stroke-width="0.5"/>
123
- <line x1="100" y1="418" x2="1100" y2="418" stroke="#21262d" stroke-width="0.5"/>
124
-
125
- <!-- ======= PERSISTENT: Title + logo (fade in once, stay) ======= -->
126
- <g class="persist-title">
127
- <!-- Smoothie cup icon -->
128
- <g transform="translate(480, 24)" fill="none" stroke="#f0f6fc" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
129
- <!-- Cup body (trapezoid) -->
130
- <path d="M 4 14 L 7 34 L 25 34 L 28 14"/>
131
- <!-- Lid -->
132
- <path d="M 1 11 L 31 11 C 31 11 31 14 16 14 C 1 14 1 11 1 11"/>
133
- <!-- Dome top -->
134
- <path d="M 5 11 C 5 5 11 1 16 1 C 21 1 27 5 27 11"/>
135
- <!-- Straw -->
136
- <line x1="20" y1="1" x2="27" y2="-8"/>
137
- <line x1="27" y1="-8" x2="30" y2="-8"/>
138
- <!-- Drip details -->
139
- <path d="M 9 14 C 9 17 12 17 12 14" opacity="0.5"/>
140
- <path d="M 18 14 C 18 17 21 17 21 14" opacity="0.5"/>
141
- </g>
142
- <text x="620" y="55" text-anchor="middle" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="40" font-weight="700" fill="#f0f6fc" letter-spacing="-0.5">Smoothie</text>
143
- </g>
144
- <text class="persist-sub" x="600" y="85" text-anchor="middle" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="14" fill="#7d8590" letter-spacing="0.5">multi-model review for Claude Code</text>
145
-
146
- <!-- ======= PERSISTENT: Terminal box (fade in once, stay) ======= -->
147
- <g class="persist-term">
148
- <rect x="42" y="225" width="195" height="65" rx="10" fill="#161b22" stroke="#30363d" stroke-width="1" filter="url(#sh)"/>
149
- <circle cx="60" cy="240" r="4" fill="#f85149" fill-opacity="0.7"/>
150
- <circle cx="73" cy="240" r="4" fill="#d29922" fill-opacity="0.7"/>
151
- <circle cx="86" cy="240" r="4" fill="#3fb950" fill-opacity="0.7"/>
152
- </g>
153
- <g class="persist-dollar">
154
- <text x="58" y="272" font-family="'SF Mono', 'Fira Code', monospace" font-size="13" fill="#7d8590">$</text>
155
- </g>
156
-
157
- <!-- ======= LOOPING: Typewriter "/smoothie" ======= -->
158
- <g clip-path="url(#type-clip)">
159
- <text x="72" y="272" font-family="'SF Mono', 'Fira Code', monospace" font-size="13" fill="#f0f6fc">/smoothie</text>
160
- </g>
161
-
162
- <!-- Cursor: follows typing, blinks after -->
163
- <rect y="260" width="7" height="14" rx="1" fill="#f78166">
164
- <animate attributeName="x" values="72;72;148;148;148;148;72;72" keyTimes="0;0.01;0.10;0.11;0.13;0.82;0.85;1" dur="14s" repeatCount="indefinite"/>
165
- <animate attributeName="opacity" values="0;1;1;0;1;0;1;1;0;0" keyTimes="0;0.01;0.10;0.105;0.11;0.115;0.13;0.82;0.85;1" dur="14s" repeatCount="indefinite"/>
166
- </rect>
167
-
168
- <!-- ======= LOOPING: Branching paths ======= -->
169
- <g class="paths" stroke="#30363d" stroke-width="1.5" fill="none" stroke-dasharray="4 4">
170
- <path d="M 250 258 L 320 258 L 320 165 L 430 165"/>
171
- <path d="M 250 258 L 320 258 L 320 225 L 430 225"/>
172
- <path d="M 250 258 L 320 258 L 320 285 L 430 285"/>
173
- <path d="M 250 258 L 320 258 L 320 345 L 430 345"/>
174
- <path d="M 680 165 L 790 165 L 790 258 L 860 258"/>
175
- <path d="M 680 225 L 790 225 L 790 258 L 860 258"/>
176
- <path d="M 680 285 L 790 285 L 790 258 L 860 258"/>
177
- <path d="M 680 345 L 790 345 L 790 258 L 860 258"/>
178
- </g>
179
- <g class="paths">
180
- <rect x="318" y="256" width="4" height="4" rx="1" fill="#484f58"/>
181
- <rect x="788" y="256" width="4" height="4" rx="1" fill="#484f58"/>
182
- </g>
183
- <text class="label-par" x="285" y="240" text-anchor="middle" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="10" fill="#f78166">parallel query</text>
184
- <text class="label-judge" x="825" y="240" text-anchor="middle" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="10" fill="#3fb950">Claude judges</text>
185
-
186
- <!-- ======= LOOPING: Orange data packets (after typing, 14-19%) ======= -->
187
- <circle r="3.5" fill="#f78166" opacity="0">
188
- <animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.14;0.145;0.23;0.24;1" dur="14s" repeatCount="indefinite"/>
189
- <animateMotion dur="14s" repeatCount="indefinite" keyPoints="0;0;1;1" keyTimes="0;0.14;0.24;1" calcMode="linear" path="M 250 258 L 320 258 L 320 165 L 430 165"/>
190
- </circle>
191
- <circle r="3.5" fill="#f78166" opacity="0">
192
- <animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.14;0.145;0.23;0.24;1" dur="14s" repeatCount="indefinite"/>
193
- <animateMotion dur="14s" repeatCount="indefinite" keyPoints="0;0;1;1" keyTimes="0;0.14;0.24;1" calcMode="linear" path="M 250 258 L 320 258 L 320 225 L 430 225"/>
194
- </circle>
195
- <circle r="3.5" fill="#f78166" opacity="0">
196
- <animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.14;0.145;0.23;0.24;1" dur="14s" repeatCount="indefinite"/>
197
- <animateMotion dur="14s" repeatCount="indefinite" keyPoints="0;0;1;1" keyTimes="0;0.14;0.24;1" calcMode="linear" path="M 250 258 L 320 258 L 320 285 L 430 285"/>
198
- </circle>
199
- <circle r="3.5" fill="#f78166" opacity="0">
200
- <animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.14;0.145;0.23;0.24;1" dur="14s" repeatCount="indefinite"/>
201
- <animateMotion dur="14s" repeatCount="indefinite" keyPoints="0;0;1;1" keyTimes="0;0.14;0.24;1" calcMode="linear" path="M 250 258 L 320 258 L 320 345 L 430 345"/>
202
- </circle>
203
-
204
- <!-- ======= LOOPING: Model cards ======= -->
205
-
206
- <!-- Opus -->
207
- <g class="mA">
208
- <rect x="430" y="143" width="250" height="44" rx="8" fill="#161b22" stroke="#30363d" stroke-width="1"/>
209
- <text x="472" y="170" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="13.5" fill="#f0f6fc">Opus 4.6</text>
210
- <rect x="440" y="178" width="230" height="3" rx="1.5" fill="#21262d"/>
211
- </g>
212
- <g class="spA"><circle cx="448" cy="165" r="5" fill="none" stroke="#f78166" stroke-width="1.5" stroke-dasharray="20 12" stroke-linecap="round"/></g>
213
- <g class="ckA">
214
- <circle cx="448" cy="165" r="6" fill="#3fb950" fill-opacity="0.15"/>
215
- <path d="M 443 165 L 446.5 168.5 L 453 162" stroke="#3fb950" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
216
- </g>
217
- <text class="dnA" x="670" y="170" text-anchor="end" font-family="'SF Mono', monospace" font-size="10" fill="#3fb950">2.1s</text>
218
-
219
- <!-- GPT Codex -->
220
- <g class="mB">
221
- <rect x="430" y="203" width="250" height="44" rx="8" fill="#161b22" stroke="#30363d" stroke-width="1"/>
222
- <text x="472" y="230" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="13.5" fill="#f0f6fc">GPT 5.4 Codex</text>
223
- <rect x="440" y="238" width="230" height="3" rx="1.5" fill="#21262d"/>
224
- </g>
225
- <g class="spB"><circle cx="448" cy="225" r="5" fill="none" stroke="#f78166" stroke-width="1.5" stroke-dasharray="20 12" stroke-linecap="round"/></g>
226
- <g class="ckB">
227
- <circle cx="448" cy="225" r="6" fill="#3fb950" fill-opacity="0.15"/>
228
- <path d="M 443 225 L 446.5 228.5 L 453 222" stroke="#3fb950" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
229
- </g>
230
- <text class="dnB" x="670" y="230" text-anchor="end" font-family="'SF Mono', monospace" font-size="10" fill="#3fb950">3.8s</text>
231
-
232
- <!-- Gemini -->
233
- <g class="mC">
234
- <rect x="430" y="263" width="250" height="44" rx="8" fill="#161b22" stroke="#30363d" stroke-width="1"/>
235
- <text x="472" y="290" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="13.5" fill="#f0f6fc">Gemini 3.1 Pro</text>
236
- <rect x="440" y="298" width="230" height="3" rx="1.5" fill="#21262d"/>
237
- </g>
238
- <g class="spC"><circle cx="448" cy="285" r="5" fill="none" stroke="#f78166" stroke-width="1.5" stroke-dasharray="20 12" stroke-linecap="round"/></g>
239
- <g class="ckC">
240
- <circle cx="448" cy="285" r="6" fill="#3fb950" fill-opacity="0.15"/>
241
- <path d="M 443 285 L 446.5 288.5 L 453 282" stroke="#3fb950" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
242
- </g>
243
- <text class="dnC" x="670" y="290" text-anchor="end" font-family="'SF Mono', monospace" font-size="10" fill="#3fb950">4.2s</text>
244
-
245
- <!-- Grok -->
246
- <g class="mD">
247
- <rect x="430" y="323" width="250" height="44" rx="8" fill="#161b22" stroke="#30363d" stroke-width="1"/>
248
- <text x="472" y="350" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="13.5" fill="#f0f6fc">Grok 4.20</text>
249
- <rect x="440" y="358" width="230" height="3" rx="1.5" fill="#21262d"/>
250
- </g>
251
- <g class="spD"><circle cx="448" cy="345" r="5" fill="none" stroke="#f78166" stroke-width="1.5" stroke-dasharray="20 12" stroke-linecap="round"/></g>
252
- <g class="ckD">
253
- <circle cx="448" cy="345" r="6" fill="#3fb950" fill-opacity="0.15"/>
254
- <path d="M 443 345 L 446.5 348.5 L 453 342" stroke="#3fb950" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
255
- </g>
256
- <text class="dnD" x="670" y="350" text-anchor="end" font-family="'SF Mono', monospace" font-size="10" fill="#3fb950">5.1s</text>
257
-
258
- <!-- Per-model progress bars -->
259
- <rect x="440" y="178" width="0" height="3" rx="1.5" fill="url(#og)">
260
- <animate attributeName="width" values="0;0;230;230;0;0" keyTimes="0;0.23;0.36;0.37;0.38;1" dur="14s" repeatCount="indefinite"/>
261
- </rect>
262
- <rect x="440" y="238" width="0" height="3" rx="1.5" fill="url(#og)">
263
- <animate attributeName="width" values="0;0;230;230;0;0" keyTimes="0;0.25;0.39;0.40;0.41;1" dur="14s" repeatCount="indefinite"/>
264
- </rect>
265
- <rect x="440" y="298" width="0" height="3" rx="1.5" fill="url(#og)">
266
- <animate attributeName="width" values="0;0;230;230;0;0" keyTimes="0;0.27;0.42;0.43;0.44;1" dur="14s" repeatCount="indefinite"/>
267
- </rect>
268
- <rect x="440" y="358" width="0" height="3" rx="1.5" fill="url(#og)">
269
- <animate attributeName="width" values="0;0;230;230;0;0" keyTimes="0;0.29;0.45;0.46;0.47;1" dur="14s" repeatCount="indefinite"/>
270
- </rect>
271
-
272
- <!-- ======= LOOPING: Green dots (staggered return, collect at junction) ======= -->
273
- <!-- Opus: leaves at 38%, arrives junction at 42%, holds, goes to output at 51% -->
274
- <circle r="3.5" fill="#3fb950" opacity="0">
275
- <animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.38;0.385;0.52;0.53;1" dur="14s" repeatCount="indefinite"/>
276
- <animateMotion dur="14s" repeatCount="indefinite" keyPoints="0;0;0.6;0.6;1;1" keyTimes="0;0.38;0.42;0.48;0.53;1" calcMode="linear" path="M 680 165 L 790 165 L 790 258 L 860 258"/>
277
- </circle>
278
- <!-- GPT: leaves at 41%, arrives junction at 44%, holds, goes to output at 51% -->
279
- <circle r="3.5" fill="#3fb950" opacity="0">
280
- <animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.41;0.415;0.52;0.53;1" dur="14s" repeatCount="indefinite"/>
281
- <animateMotion dur="14s" repeatCount="indefinite" keyPoints="0;0;0.6;0.6;1;1" keyTimes="0;0.41;0.44;0.48;0.53;1" calcMode="linear" path="M 680 225 L 790 225 L 790 258 L 860 258"/>
282
- </circle>
283
- <!-- Gemini: leaves at 44%, arrives junction at 47%, holds, goes to output at 51% -->
284
- <circle r="3.5" fill="#3fb950" opacity="0">
285
- <animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.44;0.445;0.52;0.53;1" dur="14s" repeatCount="indefinite"/>
286
- <animateMotion dur="14s" repeatCount="indefinite" keyPoints="0;0;0.6;0.6;1;1" keyTimes="0;0.44;0.47;0.49;0.53;1" calcMode="linear" path="M 680 285 L 790 285 L 790 258 L 860 258"/>
287
- </circle>
288
- <!-- Grok: leaves at 47%, arrives junction at 49%, holds briefly, goes to output at 51% -->
289
- <circle r="3.5" fill="#3fb950" opacity="0">
290
- <animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.47;0.475;0.52;0.53;1" dur="14s" repeatCount="indefinite"/>
291
- <animateMotion dur="14s" repeatCount="indefinite" keyPoints="0;0;0.6;0.6;1;1" keyTimes="0;0.47;0.49;0.50;0.53;1" calcMode="linear" path="M 680 345 L 790 345 L 790 258 L 860 258"/>
292
- </circle>
293
-
294
- <!-- ======= LOOPING: Output card ======= -->
295
- <g class="output-card">
296
- <g class="output-float">
297
- <rect x="870" y="210" width="210" height="100" rx="10" fill="#161b22" stroke="#3fb950" stroke-opacity="0.3" stroke-width="1" filter="url(#sh)"/>
298
- <text x="975" y="236" text-anchor="middle" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="10" font-weight="600" fill="#3fb950" letter-spacing="2.5" opacity="0.8">OUTPUT</text>
299
- <line x1="893" y1="246" x2="1057" y2="246" stroke="#21262d" stroke-width="1"/>
300
- </g>
301
- </g>
302
- <g class="output-float">
303
- <g class="olA"><text x="893" y="266" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="12" fill="#8b949e">Judged by Claude</text></g>
304
- <g class="olB"><text x="893" y="284" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="12" fill="#8b949e">Conflicts resolved</text></g>
305
- <g class="olC"><text x="893" y="302" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="12.5" fill="#3fb950" font-weight="600">One clean answer</text></g>
306
- </g>
307
- </svg>